Jak wysłać wszystkie dane wyjściowe do `logger` w powłoce POSIX?

10

Chciałbym, aby zalogować standardowego wyjścia i standardowego błędu oddzielnie w .xprofileużyciu logger. Myślę, że w Bash wyglądałoby to mniej więcej tak:

exec 1> >(logger --priority user.notice --tag $(basename $0)) \
     2> >(logger --priority user.error --tag $(basename $0))

Jak mam to zrobić w sposób zgodny z POSIX/bin/sh ?

l0b0
źródło

Odpowiedzi:

10

Nie ma odpowiednika POSIX. Przekierowanie można wykonać tylko za execpomocą widelca. Rura wymaga widelca, a skorupa czeka na dziecko do końca.

Jednym z rozwiązań jest umieszczenie całego kodu w funkcji.

all_my_code () {
  
}
{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")"; } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")"

(Spowoduje to również zapisanie każdego błędu z instancji stdout programu rejestrującego do instancji stderr. Można tego uniknąć dzięki dodatkowemu tasowaniu deskryptorów plików.)

Jeśli chcesz, aby powłoka nadrzędna zakończyła działanie, nawet jeśli loggerprocesy nadal działają, umieść &na końcu loggerwywołań.

{ all_my_code |
  logger --priority user.notice --tag "$(basename "$0")" & } 2>&1 | 
  logger --priority user.error --tag "$(basename "$0")" &

Alternatywnie możesz użyć nazwanych potoków.

pipe_dir=$(mktemp -d)
mkfifo "$pipe_dir/out" "$pipe_dir/err"
<"$pipe_dir/out" logger --priority user.notice --tag "$(basename "$0")" &
<"$pipe_dir/err" logger --priority user.error --tag "$(basename "$0")" &
exec >"$pipe_dir/out" 2>"$pipe_dir/err" 

rm -r "$pipe_dir"
Gilles
źródło
To pojawi się w syslog jako dwa wpisy (out & err) dla całego skryptu. Przykład w pytaniu będzie miał jeden (lub dwa) wpisy na polecenie.
DarkHeart
@DarkHeart Nie rozumiem twojego komentarza. Kod w pytaniu i oba proponowane przeze mnie rozwiązania uruchamiają tę samą liczbę instancji loggeri przekazują te same dane wejściowe każdemu z nich.
Gilles
przepraszam, mój błąd
DarkHeart
1
Wydaje się to prostsze , więc idę z twoim nazwanym rozwiązaniem do rur.
l0b0
9

Zastępowanie komend / procesów POSIX


_log()( x=0
    while  [ -e "${TMPDIR:=/tmp}/$$.$((x+=1))" ]
    do     continue; done        &&
    mkfifo -- "$TMPDIR/$$.$x"    &&
    printf %s\\n "$TMPDIR/$$.$x" || exit
    exec >&- >/dev/null
    {  rm -- "$TMPDIR/$$.$x"
       logger --priority user."$1" --tag "${0##*/}"
    }  <"$TMPDIR/$$.$x" &
)   <&- </dev/null

Powinieneś być w stanie używać takich jak:

exec >"$(_log notice)" 2>"$(_log error)"

Oto wersja, która korzysta z mktemppolecenia:

_log()( p=
    mkfifo "${p:=$(mktemp -u)}"    &&
    printf %s "$p"                 &&
    exec  <&- >&- <>/dev/null >&0  &&
    {   rm "$p"
        logger --priority user."$1" --tag "${0##*/}"
    }   <"$p" &
)

... co robi to samo, tyle że pozwala mktempwybrać nazwę pliku dla ciebie. Działa to, ponieważ podstawianie procesów w żadnym wypadku nie jest magiczne i działa w bardzo podobny sposób jak zastępowanie poleceń . Zamiast wymieniać ekspansję z wartością polecenia uruchomienia w nim jako podstawienie komenda robi, podstawienie proces zastępuje go z nazwą łącza systemu plików, gdzie można znaleźć wyjście.

Podczas gdy powłoka POSIX nie zapewnia bezpośredniego następstwa takiej rzeczy, emulowanie jej jest bardzo proste. Wszystko, co musisz zrobić, to utworzyć plik, wydrukować jego nazwę do standardu z podstawienia polecenia, a w tle tego samego polecenia uruchomić polecenie, które wyświetli się w tym pliku. Teraz możesz po prostu przekierować na wartość tego rozszerzenia - dokładnie tak, jak w przypadku zastępowania procesów. I tak powłoka POSIX zapewnia wszystkie narzędzia, których potrzebujesz, oczywiście - wystarczy, abyś użył ich w sposób, który ci odpowiada.

Obie powyższe wersje zapewniają, że niszczą łącze systemu plików do tworzonych / używanych przez siebie potoków, zanim jeszcze z nich skorzystają. Oznacza to, że po tym fakcie nie jest wymagane czyszczenie, a co ważniejsze, ich strumienie są dostępne tylko dla procesów, które je początkowo otwierają - a zatem ich łącza do systemu plików nie mogą być użyte jako sposób na szpiegowanie / przejęcie kontroli nad logowaniem. Pozostawienie linków fs w systemie plików stanowi potencjalną lukę w zabezpieczeniach.


Innym sposobem jest owinięcie go. Można to zrobić z poziomu skryptu.

x=${x##*[!0-9]*}
_log(){ 
    logger --priority user."$1" --tag "${0##*/}"
}   2>/dev/null >&2

cd ../"$PPID.$x" 2>/dev/null &&
trap 'rm -rf -- "${TMPDIR:-/tmp}/$PPID.$x"' 0 || 
{   until cd -- "${TMPDIR:=/tmp}/$$.$x"
    do    mkdir -- "$TMPDIR/$$.$((x+=1))"
    done  && 
    x=$x "$0" "$@" | _log notice
    exit
}   2>&1 | _log error

To w zasadzie pozwoliłoby twojemu skryptowi wywoływać się, jeśli jeszcze tego nie zrobił, i uruchomić katalog roboczy w temp.

mikeserv
źródło
1
Gotowe , dziękuję!
l0b0
@ l0b0 - Właśnie zamierzam zaktualizować inny tego typu ... Jest to bardziej ogólny cel i wreszcie udało mi się dać mu możliwość obsługi niektórych opcji związanych z deskryptorami / plikami we / wy.
mikeserv
@ l0b0 - jeśli chciał użyć mktempi innych niestandardowych poleceń, może to być: _log()( p=; mkfifo "${p:=$(mktemp -u)}"; printf %s "$p"; exec <&- >&- <>/dev/null >&0; { rm "$p"; logger --priority user."$1" --tag "${0##*/}"; } <"$p" &). Napisałem go używając w pełni przenośnego języka poleceń na wszystkie sposoby - z wyjątkiem twojego własnego. Ponadto basenamenie jest bardzo użytecznym narzędziem. "${0##*/}"jest bardziej solidny i szybszy.
mikeserv
Zmieniłem to, ponieważ potrzebuję go tylko do pracy na nowoczesnych platformach; Głównym problemem było to, że .xprofile ma być wykonany z sh.
l0b0
1
@Wildcard - za chwilę, a ja postaram się rozwiązać pierwszą część pytania, ale odpowiedź na drugą odpowiedź brzmi tak: przypadek na krawędzi jest zshwłączony w / multios. Co do pierwszej części: { this; can\'t; happen; before; } < this. To pomaga?
mikeserv