¿Cómo enviar toda la salida a `logger` en el shell POSIX?

10

Me gustaría registrar la salida estándar y el error estándar por separado en el .xprofileuso logger. En Bash, creo que se vería así:

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

¿Cómo haría eso de una manera POSIX /bin/sh compatible?

l0b0
fuente

Respuestas:

10

No hay equivalente POSIX. Solo puede realizar una redirección con exec, no una bifurcación. Una tubería requiere un tenedor, y la carcasa espera a que el niño termine.

Una solución es poner todo su código en una función.

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

(Esto también registra cualquier error desde la instancia stdout del registrador a la instancia stderr. Puede evitar esto con más barajado de descriptores de archivo).

Si desea que el shell principal salga incluso si los loggerprocesos aún se están ejecutando, colóquelo &al final de las loggerinvocaciones.

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

Alternativamente, puede usar tuberías con nombre.

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 'SO- deja de ser malvado'
fuente
Eso aparecerá en syslog como dos entradas (out & err) para todo el script. El ejemplo en la pregunta tendrá una (o dos) entradas por comando.
DarkHeart
@DarkHeart No entiendo tu comentario. El código en la pregunta y las dos soluciones que propongo ejecutan el mismo número de instancias loggery pasan la misma entrada a cada una de ellas.
Gilles 'SO- deja de ser malvado'
lo siento, mi error
DarkHeart
1
Esto parece más simple , así que voy con su solución de tuberías con nombre.
l0b0
9

Comando POSIX / sustitución de procesos


_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

Deberías poder usar eso como:

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

Aquí hay una versión que hace uso del mktempcomando:

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

... que hace casi lo mismo, excepto que permite mktempseleccionar el nombre de archivo por usted. Esto funciona porque la sustitución del proceso no es mágica y funciona de manera muy similar a la sustitución de comandos . En lugar de reemplazar la expansión con el valor del comando ejecutado dentro de ella como lo hace la sustitución de comando , la sustitución del proceso la reemplaza con el nombre de un enlace del sistema de archivos donde se puede encontrar la salida.

Si bien el shell POSIX no proporciona un corolario directo a tal cosa, emularlo es muy simple. Todo lo que necesita hacer es crear un archivo, imprimir su nombre al estándar de una sustitución de comando, y en el fondo del mismo ejecutar su comando que se enviará a ese archivo. Ahora puede redirigir al valor de esa expansión, exactamente como lo hace con la sustitución de procesos. Y, por lo tanto, el shell POSIX proporciona todas las herramientas que necesita, por supuesto, todo lo que se requiere es que las use de la manera que más le convenga.

Ambas versiones anteriores aseguran que destruyan el enlace del sistema de archivos a las tuberías que crean / usan antes de usarlas. Esto significa que no se requiere una limpieza después del hecho y, lo que es más importante, sus transmisiones solo están disponibles para los procesos que inicialmente las abren, por lo que sus enlaces del sistema de archivos no pueden usarse como un medio para espiar / secuestrar su actividad de registro. Dejar sus enlaces fs en el sistema de archivos es un agujero de seguridad potencial.


Otra forma es envolverlo. Se puede hacer desde el script.

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

Básicamente, eso permitiría que su script se llame a sí mismo si aún no lo ha hecho y le obtenga un directorio de trabajo en temp para arrancar.

mikeserv
fuente
1
Listo , gracias!
l0b0
@ l0b0: estoy a punto de actualizar otro tipo de este tipo ... Tiene un propósito más general, y finalmente pude darle la capacidad de manejar algunas opciones relacionadas con los descriptores / archivos de E / S.
mikeserv
@ l0b0 - si desea utilizar mktempy otros comandos no estándar, podría ser: _log()( p=; mkfifo "${p:=$(mktemp -u)}"; printf %s "$p"; exec <&- >&- <>/dev/null >&0; { rm "$p"; logger --priority user."$1" --tag "${0##*/}"; } <"$p" &). Lo escribí usando un lenguaje de comandos totalmente portátil en todos los sentidos, con la excepción del suyo. Además, basenameno es una utilidad muy útil. "${0##*/}"es a la vez más robusto y más rápido.
mikeserv
Lo cambié porque solo lo necesito para trabajar en plataformas modernas; el problema principal era que .xprofile tiene que ser ejecutada con sh.
l0b0
1
@Wildcard: solo un minuto e intentaré resolver la primera parte de la pregunta, pero la respuesta a la segunda es sí: el caso límite es un zshw / multios habilitado. En cuanto a la primera parte: { this; can\'t; happen; before; } < this. ¿Eso ayuda?
mikeserv