2009-06-12 13 views
14

existe una alternativa a "tee" que captura STDOUT/STDERR del comando que se está ejecutando y sale con el mismo estado de salida que el comando procesado. Algo de la siguiente manera:T y estado de salida

eet -a some.log -- mycommand --foo --bar 

Donde "flota" es una alternativa imaginaria de "golpe" :) (-a medios anexan, - se separa el comando capturado) No debería ser difícil de piratear un comando tal, sino tal vez ya existe y no soy consciente de eso?

Gracias.

+1

Supongo que la verdadera pregunta aquí es: cómo dar salida T y capturar el estado de salida. Si es así: posible duplicado de [bash: salida T Y estado de salida de captura] (http://stackoverflow.com/questions/1221833/bash-tee-output-and-capture-exit-status) – lesmana

Respuesta

5

Aquí hay eet. Funciona con cada Bash que puedo tener en mis manos, desde 2.05b hasta 4.0.

#!/bin/bash 
tee_args=() 
while [[ $# > 0 && $1 != -- ]]; do 
    tee_args=("${tee_args[@]}" "$1") 
    shift 
done 
shift 
# now ${tee_args[*]} has the arguments before --, 
# and $* has the arguments after -- 

# redirect standard out through a pipe to tee 
exec | tee "${tee_args[@]}" 

# do the *real* exec of the desired program 
exec "[email protected]" 

(pipefail$PIPESTATUS y son agradables, pero recuerdo que se están introduciendo en 3,1 o ​​menos.)

+0

Es extraño, pero no funciona para mí: jirka @ debian: ~/monitor $ exec | wc -c \\ 0 \\ jirka @ debian: ~/monitor $ exec echo a \\ a (\\ significa nueva línea) – jpalecek

0

G'day,

Suponiendo bash o zsh,

my_command >>my_log 2>&1 

N.B. ¡La secuencia de redirección y duplicación de STDERR en STDOUT es significativa!

Editar: Vaya. No me di cuenta de que quería ver la salida en la pantalla también. Por supuesto, esto dirigirá toda la salida al archivo my_log.

HTH

aplausos,

+1

Esto envía todos los resultados a my_log , pero a diferencia de tee, tampoco aparece en la consola. –

+0

@Steve, tienes razón. Mi error.) -: –

9

topé con un par de soluciones interesantes aquí http://www.perlmonks.org/?node_id=597613.

1) No es variable $ PIPESTATUS disponibles en bash:

false | tee /dev/null 
    [ $PIPESTATUS -eq 0 ] || exit $PIPESTATUS 

2) y el prototipo más simple de "flota" en Perl puede ser como sigue:

open MAKE, "command 2>&1 |" or die; 
    open (LOGFILE, ">>some.log") or die; 
    while (<MAKE>) { print LOGFILE $_; print } 
    close MAKE; # to get $? 
    my $exit = $? >> 8; 
    close LOGFILE; 
-1
#!/bin/sh 
logfile="$1" 
shift 
exec 2>&1 
exec "[email protected]" | tee "$logfile" 

suerte esto funciona para ti.

+0

Ejecuta esto con argumentos "foo falso" - debería regresar con el código de salida 1 (de falso), pero obtengo 0 (presumiblemente desde tee). –

+0

Mi mal. Tienes que hacerlo a la manera de la tubería antigua. PIPE =/tmp/$$. Pipe; mkfifo "$ PIPE"; logfile = "$ 1"; turno; tee "$ logfile" <"$ PIPE" &; "$ @" 2> & 1> "$ PIPE"; estado = $ ?; rm "$ PIPE"; exit $ status –

0
{ mycommand --foo --bar 2>&1; ret=$?; } | tee -a some.log; (exit $ret) 
+3

Al menos en bash, $ ret no está disponible fuera de {list}, por lo que esto no funciona. –

20

Esto funciona con bash:

(
    set -o pipefail 
    mycommand --foo --bar | tee some.log 
) 

Los paréntesis son ahí para limitar el efecto de pipefail a sólo el comando de uno.

Desde el (1) página de manual de bash:

El estado de retorno de un oleoducto es el estado de salida del último comando, a menos que la opción está activada pipefail. Si pipefail está habilitado, el estado de retorno de la tubería es el valor del último comando (más a la derecha) para salir con un estado distinto de cero, o cero si todos los comandos salen exitosamente.
1

Korn, todo en 1 línea:

foo; RET_VAL=$?; if test ${RET_VAL} != 0;then echo $RET_VAL; echo Error occurred!>/tmp/out.err;exit 2;fi |tee >>/tmp/out.err ; if test ${RET_VAL} != 0;then exit $RET_VAL;fi 
1

Esto es lo que considero que es la mejor solución de Bourne-Shell puro para ser usado como la base sobre la que se podía construir su "flota":

# You want to pipe command1 through command2: 
exec 4>&1 
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1` 
# $exitstatus now has command1's exit status. 

Creo que esto se explica mejor desde adentro hacia afuera - comando1 ejecutará e imprimir su producción regular en la salida estándar (descriptor de archivo 1), a continuación, una vez que se hace, printf ejecutará y el código de salida de comando1 impresión en su salida estándar , pero ese stdout se redirige al descriptor de archivo 3.

Mientras se ejecuta el comando1, su salida estándar se canaliza a command2 (la salida de printf nunca llega a command2 porque la enviamos al descriptor de archivo 3 en lugar de 1, que es lo que lee el conducto). Luego, redirigimos la salida de command2 al descriptor de archivo 4, para que también permanezca fuera del descriptor de archivo 1 - porque queremos el descriptor 1 de archivo gratis un poco más tarde, porque llevaremos la salida printf en el descriptor 3 de archivo al descriptor de archivo 1 - porque eso es lo que capturará el comando (los backticks), y eso es lo que se colocará en la variable.

El último truco de la magia es que el primer exec 4>&1 lo hicimos como un comando separado: abre el descriptor de archivo 4 como una copia de la salida estándar del shell externo. La sustitución de comandos capturará lo que está escrito en estándar desde la perspectiva de los comandos que contiene, pero dado que la salida de command2 va a presentar el descriptor 4 en lo que respecta a la sustitución del comando, la sustitución del comando no lo captura, sin embargo, una vez que se "sale" de la sustitución del comando, de hecho sigue yendo al descriptor de archivo general del script 1.

(El tiene que ser un comando separado porque a muchos shells comunes no les gusta cuando intenta escriba en un descriptor de archivo dentro de una sustitución de comando, que se abre en el comando "externo" que está utilizando la sustitución. Por lo tanto, esta es la manera más simple de hacerlo).

Puede verlo de una forma menos técnica y más playf ul way, como si las salidas de los comandos se saltaran entre sí: command1 pipes a command2, luego la salida de printf salta sobre el comando 2 para que command2 no la atrape, y luego la salida del comando 2 salta y sale de la sustitución de comando justo cuando printf aterriza justo a tiempo para ser capturado por la sustitución para que termine en la variable, y la salida de command2 se transmite de manera feliz a la salida estándar, como en una tubería normal.

Además, según tengo entendido, $? aún contendrá el código de retorno del segundo comando en la tubería, porque las asignaciones de variables, sustituciones de comandos y comandos compuestos son efectivamente transparentes para el código de retorno del comando dentro de ellos, por lo que el estado de retorno de command2 debería propagarse.

Una advertencia es que es posible que command1 en algún momento termine usando los descriptores de archivos 3 o 4, o que command2 o cualquiera de los comandos posteriores utilizará el descriptor de archivo 4, por lo que para ser más robusto, haría:

exec 4>&1 
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1` 
exec 4>&- 

Nota que utilizo comandos compuestos en mi ejemplo, pero subniveles (utilizando () en lugar de { } también funcionará, aunque tal vez puede ser menos eficiente.)

comandos heredan descriptores de archivo del proceso que se inicia ellos, entonces toda la segunda línea heredará el descriptor de archivo cuatro, y el comando compuesto seguido de 3>&1 heredará el descriptor de archivo tres.Entonces, el 4>&- se asegura de que el comando compuesto interno no herede el descriptor de archivo cuatro, y el 3>&- no heredará el descriptor de archivo tres, por lo que command1 obtiene un entorno más limpio y más estándar. También puede mover el 4>&- interno junto al 3>&-, pero me imagino por qué no solo limita su alcance tanto como sea posible.

No estoy seguro de la frecuencia con que las cosas usan el descriptor de archivo tres y cuatro directamente - Creo que la mayoría de los programas usan syscalls que devuelven descriptores de archivos no utilizados en el momento, pero a veces escriben códigos en el descriptor de archivo 3 directamente, supongo (podría imaginarme un programa que verifica un descriptor de archivo para ver si está abierto, y si lo está usando, o si se comporta de manera diferente si no lo está). Por lo tanto, es probable que lo último sea mejor tenerlo en cuenta y usarlo para casos de propósito general.

--- contenido obsoleto DEBAJO DE ESTA LÍNEA ---

Por razones históricas, aquí es mi original, no-portátil-a-toda-conchas responden:

[EDIT] Mi mal, esta no funciona con bash porque bash necesita mímica extra cuando juega con los descriptores de archivos, lo actualizaré tan pronto como pueda. [/ EDIT]

puro solución shell Bourne:

exitstatus=`{ 3>&- command1; } 1>&3; printf $?` 3>&1 | command2 
# $exitstatus now has command1's exit status. 

Ésta es la base sobre la que se podía construir su "flota". Bofetada en un argumento de línea de comandos de análisis y todo lo que, a su vez comando2 en "T" con las opciones pertinentes, etc.

La explicación muy detallada es la siguiente:

En el nivel superior, la declaración es sólo una tubería entre dos comandos:

commandA | command2 

Commanda a su vez se descompone en un solo comando con una reorientación de descriptor de archivo 3 al descriptor de archivo 1 (salida estándar):

commandB 3>&1 

Esto significa que el shell esperará que el comando B escriba algo en el descriptor de archivo 3 - si el descriptor de archivo 3 nunca se abre, sería un error. También significa que va a conseguir lo comando2 commandB salidas en ambos descriptores de archivos 1 (stdout) y 3.

commandB a su vez es una asignación de variables con el comando substitución:

VAR_FOO=`commandC` 

Sabemos que las asignaciones de variables no lo hacen imprima cualquier cosa en cualquier descriptor de archivo (y la salida estándar del comandoC se captura para la sustitución), así que sabemos que el comandoB como un todo no generará nada en stdout. comando2 será por lo tanto sólo ver lo que commandC escribe en el descriptor de archivo 3.

Y es commandC dos comandos, donde el segundo comando imprime el estado de salida de la primera:

commandD ; printf $? 

Así que por ahora sabemos la asignación de variables en el último paso contendrá el estado de salida de comandoD.

Ahora, commandD descompone a otra redirección básica, de la salida estándar de un comman para presentar el descriptor 3:

commandE 1>&3 

Así que ahora sabemos que la escritura que debe presentar el descriptor 3, y por lo tanto en última instancia a command2, es commandé de stdout.

Por último: commandé es un "comando compuesto" (también se puede utilizar un subnivel aquí, pero no es tan eficiente), envolviendo alrededor de otro tipo menos comúnmente visto de "redirección":

{ 3>&- command1; } 

(Eso 3>&- es un poco complicado, así que volveremos al final.) Así que los comandos compuestos hacen que ese punto y coma sea obligatorio cuando el último comando y el último par están en la misma línea, por eso está ahí. Así que sabemos que los comandos compuestos devuelven el código de salida de su último comando y heredan descriptores de archivos como todo lo demás, por lo que ahora sabemos que el comando stdout fluye fuera del comando compuesto, redirige al descriptor de archivo 3 para evitar ser atrapado por la sustitución del comando , mientras tanto, la sustitución de comando captura el stdout restante de printf, que hace eco del estado de salida de command1 una vez que finaliza.

Y ahora para el truco: 3>&- dice "close file descriptor 3". Puedes pensar, "¿por qué lo cierras cuando redirigiste la salida de command1 a él?" Bueno, si miras detenidamente, verás que los efectos cercanos solo ordenan1 dentro del comando compuesto (dentro de las llaves) específicamente, mientras que la redirección afecta todo el comando compuesto.

Así que esto es lo que sucede: cuando los comandos individuales del comando compuesto se ejecutan, el shell abre el descriptor de archivo 3. Los procesos heredan descriptores de archivos, por lo que command1 se ejecuta de forma predeterminada con el descriptor de archivo abierto y 3 el mismo lugar también Esto es malo porque ocasionalmente, los programas realmente esperan que las descripciones de archivos específicos signifiquen cosas especiales: pueden comportarse de manera diferente cuando se abren con el descriptor de archivo 3 abierto. La solución más robusta es simplemente cerrar el descriptor de archivo 3 (o el número que use) solo para command1, de modo que se ejecute como lo haría si el descriptor de archivo 3 nunca se abriera.

Cuestiones relacionadas