¿Suprimir la traza de ejecución para el comando echo?

29

Estoy ejecutando scripts de shell de Jenkins, que inicia los scripts de shell con las opciones shebang #!/bin/sh -ex.

De acuerdo con Bash Shebang para tontos? , -x"hace que el shell imprima un seguimiento de ejecución", lo cual es excelente para la mayoría de los propósitos, excepto para los ecos:

echo "Message"

produce la salida

+ echo "Message"
Message

lo cual es un poco redundante y se ve un poco extraño. ¿Hay alguna manera de dejar -xhabilitado, pero solo salida

Message

en lugar de las dos líneas anteriores, p. ej., anteponiendo el comando echo con un carácter de comando especial o redirigiendo la salida?

cristiano
fuente

Respuestas:

20

Cuando estás cerca de tu cuello en caimanes, es fácil olvidar que el objetivo era drenar el pantano.                   - dicho popular

La pregunta es sobre echo, y sin embargo, la mayoría de las respuestas hasta ahora se han centrado en cómo introducir un set +xcomando. Hay una solución mucho más simple y directa:

{ echo "Message"; } 2> /dev/null

(Reconozco que podría no haber pensado en { …; } 2> /dev/null eso si no lo hubiera visto en las respuestas anteriores).

Esto es algo engorroso, pero, si tiene un bloque de echocomandos consecutivos , no necesita hacerlo en cada uno individualmente:

{
  echo "The quick brown fox"
  echo "jumps over the lazy dog."
} 2> /dev/null

Tenga en cuenta que no necesita punto y coma cuando tiene líneas nuevas.

Puede reducir la carga de escritura utilizando la idea de kenorb de abrir /dev/nullpermanentemente en un descriptor de archivo no estándar (por ejemplo, 3) y luego decir en 2>&3lugar de 2> /dev/nulltodo el tiempo.


Las primeras cuatro respuestas al momento de escribir esto requieren hacer algo especial (y, en la mayoría de los casos, engorroso) cada vez que haces una echo. Si realmente desea que todos los echo comandos supriman la traza de ejecución (¿y por qué no lo haría?), Puede hacerlo globalmente, sin mezclar mucho código. Primero, noté que no se rastrean los alias:

$ myfunc()
> {
>     date
> }
$ alias myalias="date"
$ set -x
$ date
+ date
Mon, Oct 31, 2016  0:00:00 AM           # Happy Halloween!
$ myfunc
+ myfunc                                # Note that function call is traced.
+ date
Mon, Oct 31, 2016  0:00:01 AM
$ myalias
+ date                                  # Note that it doesn’t say  + myalias
Mon, Oct 31, 2016  0:00:02 AM

(Tenga en cuenta que los siguientes fragmentos de script funcionan si shebang es #!/bin/sh, incluso si /bin/shes un enlace a bash. Pero, si es shebang #!/bin/bash, debe agregar un shopt -s expand_aliasescomando para que los alias funcionen en un script).

Entonces, para mi primer truco:

alias echo='{ set +x; } 2> /dev/null; builtin echo'

Ahora, cuando decimos echo "Message", estamos llamando al alias, que no se rastrea. El alias desactiva la opción de rastreo, mientras suprime el mensaje de rastreo del setcomando (utilizando la técnica presentada primero en la respuesta del usuario 5071535 ) y luego ejecuta el echocomando real . Esto nos permite obtener un efecto similar al de la respuesta del usuario 5071535 sin necesidad de editar el código en cada echo comando. Sin embargo, esto deja el modo de rastreo desactivado. No podemos poner un set -xen el alias (o al menos no fácilmente) porque un alias solo permite que una cadena sea sustituida por una palabra; ninguna parte de la cadena de alias se puede inyectar en el comando después de los argumentos (por ejemplo, "Message"). Entonces, por ejemplo, si el script contiene

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
date

la salida sería

+ date
Mon, Oct 31, 2016  0:00:03 AM
The quick brown fox
jumps over the lazy dog.
Mon, Oct 31, 2016  0:00:04 AM           # Note that it doesn’t say  + date

así que aún necesita volver a activar la opción de rastreo después de mostrar los mensajes, pero solo una vez después de cada bloque de echocomandos consecutivos :

date
echo "The quick brown fox"
echo "jumps over the lazy dog."
set -x
date


Sería bueno si pudiéramos hacer la set -xautomática después de un echo- y podemos hacerlo , con un poco más de trucos. Pero antes de presentar eso, considere esto. El OP comienza con scripts que usan #!/bin/sh -exshebang. Implícitamente, el usuario podría eliminar el xarchivo shebang y tener un script que funcione normalmente, sin rastreo de ejecución. Sería bueno si pudiéramos desarrollar una solución que conserve esa propiedad. Las primeras respuestas aquí fallan esa propiedad porque activan el rastreo "de regreso" después de las echodeclaraciones, incondicionalmente, sin tener en cuenta si ya estaba activado.  Esta respuesta no reconoce ese problema, ya que reemplaza echosalida con salida de rastreo; por lo tanto, todos los mensajes desaparecen si el rastreo está desactivado. Ahora presentaré una solución que activa el rastreo después de una echodeclaración condicionalmente , solo si ya estaba activada. Reducir esto a una solución que activa el rastreo "incondicionalmente" es trivial y se deja como un ejercicio.

alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'
echo_and_restore() {
        builtin echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}

$-es la lista de opciones; una concatenación de las letras correspondientes a todas las opciones que se configuran. Por ejemplo, si las opciones ey xestán configuradas, $-habrá un revoltijo de letras que incluye ey x. Mi nuevo alias (arriba) guarda el valor de $-antes de desactivar el rastreo. Luego, con el rastreo desactivado, arroja el control a una función de shell. Esa función hace lo real echo y luego verifica si la xopción estaba activada cuando se invocó el alias. Si la opción estaba activada, la función la vuelve a activar; si estaba apagado, la función lo deja.

Puede insertar las siete líneas anteriores (ocho, si incluye una shopt) al comienzo del guión y dejar el resto solo.

Esto te permitiría

  1. para usar cualquiera de las siguientes líneas shebang:
    #! / bin / sh -ex
    #! / bin / sh -e
    #! / bin / sh –x
    o simplemente
    #! / bin / sh
    y debería funcionar como se esperaba.
  2. tener un código como
    (shebang) 
    comando 1
     comando 2
     comando 3
    set -x
    comando 4
     comando 5
     comando 6
    conjunto + x
    comando 7
     comando 8
     comando 9
    y
    • Los comandos 4, 5 y 6 se rastrearán, a menos que uno de ellos sea un echo, en cuyo caso se ejecutará pero no se rastreará. (Pero incluso si el comando 5 es un echo, el comando 6 aún se rastreará).
    • Los comandos 7, 8 y 9 no se rastrearán. Incluso si el comando 8 es un echo, el comando 9 todavía no se rastreará.
    • Los comandos 1, 2 y 3 se rastrearán (como 4, 5 y 6) o no (como 7, 8 y 9) dependiendo de si el shebang incluye x.

PD: He descubierto que, en mi sistema, puedo omitir la builtinpalabra clave en mi respuesta intermedia (la que es solo un alias echo). Esto no es sorprendente; bash (1) dice que, durante la expansión de alias, ...

... una palabra que es idéntica a un alias que se expande no se expande por segunda vez. Esto significa que uno puede alias lsde ls -F, por ejemplo, y bash no intenta expandir recursivamente el texto de reemplazo.

No es sorprendente que la última respuesta (la que tiene echo_and_restore) falle si builtinse omite la palabra clave 1 . Pero, curiosamente, funciona si borro builtiny cambio el orden:

echo_and_restore() {
        echo "$*"
        case "$save_flags" in
         (*x*)  set -x
        esac
}
alias echo='{ save_flags="$-"; set +x;} 2> /dev/null; echo_and_restore'

__________
1  Parece dar lugar a un comportamiento indefinido. He visto

  • un bucle infinito (probablemente debido a una recursión ilimitada),
  • un /dev/null: Bad addressmensaje de error y
  • un basurero
G-Man dice 'restablecer a Mónica'
fuente
2
He visto algunos trucos de magia increíbles hechos con alias, así que sé que mi conocimiento de los mismos es incompleto. Si alguien puede presentar una forma de hacer el equivalente de echo +x; echo "$*"; echo -xun alias, me gustaría verlo.
G-Man dice 'Restablecer a Monica' el
11

Encontré una solución parcial en InformIT :

#!/bin/bash -ex
set +x; 
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"

salidas

set +x; 
shell tracing is disabled here 
+ echo "but is enabled here"
but is enabled here

Desafortunadamente, eso todavía resuena set +x, pero al menos está tranquilo después de eso. entonces es al menos una solución parcial al problema.

¿Pero hay quizás una mejor manera de hacer esto? :)

cristiano
fuente
2

De esta manera mejora su propia solución al deshacerse de la set +xsalida:

#!/bin/bash -ex
{ set +x; } 2>/dev/null
echo "shell tracing is disabled here"; set -x;
echo "but is enabled here"
usuario5071535
fuente
2

Poner set +xdentro de los corchetes, por lo que se aplicaría solo para el ámbito local.

Por ejemplo:

#!/bin/bash -x
exec 3<> /dev/null
(echo foo1 $(set +x)) 2>&3
($(set +x) echo foo2) 2>&3
( set +x; echo foo3 ) 2>&3
true

daría salida:

$ ./foo.sh 
+ exec
foo1
foo2
foo3
+ true
kenorb
fuente
Corrígeme si me equivoco, pero no creo que el set +xinterior del subshell (o el uso de subshells en absoluto) esté haciendo algo útil. Puede eliminarlo y obtener el mismo resultado. Es el redireccionador stderr a / dev / null el que está haciendo el trabajo de "deshabilitar" el rastreo temporalmente ... Parece que echo foo1 2>/dev/null, etc., sería igual de efectivo y más legible.
Tyler Rick
Tener seguimiento en su secuencia de comandos podría afectar el rendimiento. En segundo lugar, redirigir & 2 a NULL podría no ser lo mismo, cuando espera otros errores.
kenorb
Corríjame si me equivoco, pero en su ejemplo ya tiene habilitado el rastreo en su script (con bash -x) y ya está redirigiendo &2a nulo (ya que &3fue redirigido a nulo), por lo que no estoy seguro de cómo ese comentario es relevante. Tal vez solo necesitemos un mejor ejemplo que ilustre su punto, pero en el ejemplo dado al menos, todavía parece que podría simplificarse sin perder ningún beneficio.
Tyler Rick
1

Me encanta la respuesta exhaustiva y bien explicada de g-man , y considero que es la mejor que se ha proporcionado hasta ahora. Se preocupa por el contexto del script y no fuerza las configuraciones cuando no son necesarias. Entonces, si está leyendo esta respuesta primero, continúe y verifique esa, todo el mérito está ahí.

Sin embargo, en esa respuesta falta una pieza importante: el método propuesto no funcionará para un caso de uso típico, es decir, informar errores:

COMMAND || echo "Command failed!"

Debido a cómo se construye el alias, esto se expandirá a

COMMAND || { save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore "Command failed!"

y lo adivinaste, echo_and_restorese ejecuta siempre , incondicionalmente. Dado que la set +xparte no se ejecutó, significa que el contenido de esa función también se imprimirá.

Cambiar el último ;a &&tampoco funcionaría, porque en Bash, ||y &&son asociativos a la izquierda .

Encontré una modificación que funciona para este caso de uso:

echo_and_restore() {
    echo "$(cat -)"
    case "$save_flags" in
        (*x*) set -x
    esac
}
alias echo='({ save_flags="$-"; set +x; } 2>/dev/null; echo_and_restore) <<<'

Utiliza una subshell (la (...)parte) para agrupar todos los comandos, y luego pasa la cadena de entrada a través de stdin como Here String (la <<<cosa) que luego imprime cat -. El -es opcional, pero ya sabes, " explícito es mejor que implícito ".

También podría usar cat -directamente sin el eco , pero me gusta de esta manera porque me permite agregar cualquier otra cadena a la salida. Por ejemplo, tiendo a usarlo así:

BASENAME="$(basename "$0")"  # Complete file name
...
echo "[${BASENAME}] $(cat -)"

Y ahora funciona de maravilla:

false || echo "Command failed"
> [test.sh] Command failed
j1elo
fuente
0

El seguimiento de ejecución va a stderr, filtre de esta manera:

./script.sh 2> >(grep -v "^+ echo " >&2)

Alguna explicación, paso a paso:

  • stderr se redirige ... - 2>
  • ... a una orden. ->(…)
  • grep es el comando ...
  • ... que requiere el comienzo de la línea ... - ^
  • ... seguido de + echo...
  • ... luego grepinvierte el partido ... --v
  • ... y eso descarta todas las líneas que no quieres.
  • El resultado normalmente iría a stdout; lo redirigimos a stderrdonde pertenece. ->&2

El problema es (supongo) que esta solución puede desincronizar las transmisiones. Debido al filtrado stderrpuede ser un poco tarde en relación con stdout(donde la echosalida pertenece por defecto). Para solucionarlo, puede unirse a las transmisiones primero si no le importa tener ambas en stdout:

./script.sh > >(grep -v "^+ echo ") 2>&1

Puede crear un filtro de este tipo en el script en sí, pero este enfoque es propenso a la desincronización con seguridad (es decir, ha ocurrido en mis pruebas: el seguimiento de la ejecución de un comando puede aparecer después de la salida de seguimiento inmediato echo).

El código se ve así:

#!/bin/bash -x

{
 # original script here
 # …
} 2> >(grep -v "^+ echo " >&2)

Ejecútalo sin ningún truco:

./script.sh

Nuevamente, use > >(grep -v "^+ echo ") 2>&1para mantener la sincronización a costa de unirse a las transmisiones.


Otro enfoque. Obtiene una salida "un poco redundante" y de aspecto extraño porque su terminal se mezcla stdoutystderr . Estas dos corrientes son animales diferentes por una razón. Compruebe si analizar stderrsolo se ajusta a sus necesidades; descartar stdout:

./script.sh > /dev/null

Si tiene en su script un echomensaje de error / depuración de impresión, stderrentonces puede deshacerse de la redundancia de la manera descrita anteriormente. Comando completo:

./script.sh > /dev/null 2> >(grep -v "^+ echo " >&2)

Esta vez solo estamos trabajando stderr, por lo que la desincronización ya no es una preocupación. Desafortunadamente, de esta manera no verá un rastro ni salida de las echoimpresiones stdout(si las hay). Podríamos tratar de reconstruir nuestro filtro para detectar la redirección ( >&2), pero si nos fijamos en echo foobar >&2, echo >&2 foobary echo "foobar >&2"entonces probablemente estarán de acuerdo que las cosas se complican.

Mucho depende de los ecos que tenga en sus guiones. Piénselo dos veces antes de implementar un filtro complejo, puede ser contraproducente. Es mejor tener un poco de redundancia que perder accidentalmente alguna información crucial.


En lugar de descartar la traza de ejecución de un echo, podemos descartar su salida, y cualquier salida, excepto las trazas. Para analizar solo las trazas de ejecución, intente:

./script.sh > /dev/null 2> >(grep "^+ " >&2)

¿Infalible? No. Piensa qué sucederá si hay echo "+ rm -rf --no-preserve-root /" >&2en el guión. Alguien podría sufrir un ataque al corazón.


Y finalmente…

Afortunadamente hay BASH_XTRACEFDuna variable ambiental. De man bash:

BASH_XTRACEFD
Si se establece en un número entero correspondiente a un descriptor de archivo válido, bash escribirá la salida de rastreo generada cuando set -xesté habilitado para ese descriptor de archivo.

Podemos usarlo así:

(exec 3>trace.txt; BASH_XTRACEFD=3 ./script.sh)
less trace.txt

Tenga en cuenta que la primera línea genera una subshell. De esta forma, el descriptor de archivo no seguirá siendo válido ni la variable asignada en el shell actual después.

Gracias a BASH_XTRACEFDusted puede analizar los rastros libres de ecos y cualquier otra salida, sean cuales sean. No es exactamente lo que querías, pero mi análisis me hace pensar que es (en general) la forma correcta.

Por supuesto, puede usar otro método, especialmente cuando necesita analizar stdouty / o stderrjunto con sus trazas. Solo necesita recordar que existen ciertas limitaciones y dificultades. Traté de mostrar (algunos) de ellos.

Kamil Maciorowski
fuente
0

En un Makefile puedes usar el @símbolo.

Ejemplo de uso: @echo 'message'

De este documento de GNU:

Cuando una línea comienza con '@', se suprime el eco de esa línea. La '@' se descarta antes de pasar la línea al shell. Por lo general, usaría esto para un comando cuyo único efecto es imprimir algo, como un comando echo para indicar el progreso a través del archivo MAKE.

Derek
fuente
Hola, el documento al que hace referencia es para gnu make, no shell. ¿Estás seguro de que funciona? Me sale el error ./test.sh: line 1: @echo: command not found, pero estoy usando bash.
Wow, lo siento, leí completamente la pregunta. Sí, @solo funciona cuando haces eco en archivos MAKE.
derek
@derek Edité su respuesta original, por lo que ahora indica claramente que la solución está limitada solo para Makefiles. En realidad estaba buscando este, así que quería que tu comentario no tuviera una reputación negativa. Con suerte, la gente también lo encontrará útil.
Shakaron
-1

Editado el 29 de octubre de 2016 por moderador sugiere que el original no contenía suficiente información para comprender lo que está sucediendo.

Este "truco" produce solo una línea de salida de mensaje en el terminal cuando xtrace está activo:

La pregunta original es ¿Hay alguna manera de dejar -x habilitado, pero solo muestra el mensaje en lugar de las dos líneas ? Esta es una cita exacta de la pregunta.

Entiendo la pregunta, ¿cómo "dejar set -x habilitado Y producir una sola línea para un mensaje"?

  • En el sentido global, esta pregunta es básicamente sobre estética: el interlocutor quiere producir una sola línea en lugar de las dos líneas prácticamente duplicadas producidas mientras xtrace está activo.

En resumen, el OP requiere:

  1. Tener establecido -x en efecto
  2. Producir un mensaje, legible por humanos
  3. Produzca solo una línea de salida de mensaje.

El OP no requiere que el se use el comando echo . Lo citaron como un ejemplo de producción de mensajes, utilizando la abreviatura, por ejemplo, que significa latín exempli gratia, o "por ejemplo".

Al pensar "fuera de la caja" y abandonar el uso del eco para producir mensajes, noto que una declaración de asignación puede cumplir con todos los requisitos.

Haga una asignación de una cadena de texto (que contiene el mensaje) a una variable inactiva.

Agregar esta línea a un script no altera ninguna lógica, pero produce una sola línea cuando el rastreo está activo: (Si está utilizando la variable $ echo , simplemente cambie el nombre a otra variable no utilizada)

echo="====================== Divider line ================="

Lo que verá en la terminal es solo 1 línea:

++ echo='====================== Divider line ================='

no dos, como no le gusta al OP:

+ echo '====================== Divider line ================='
====================== Divider line =================

Aquí hay un script de muestra para demostrar. Tenga en cuenta que la sustitución de variables en un mensaje (el nombre del directorio $ HOME a 4 líneas del final) funciona, por lo que puede rastrear variables utilizando este método.

#!/bin/bash -exu
#
#  Example Script showing how, with trace active, messages can be produced
#  without producing two virtually duplicate line on the terminal as echo does.
#
dummy="====================== Entering Test Script ================="
if [[ $PWD == $HOME ]];  then
  dummy="*** "
  dummy="*** Working in home directory!"
  dummy="*** "
  ls -la *.c || :
else
  dummy="---- C Files in current directory"
  ls -la *.c || :
  dummy="----. C Files in Home directory "$HOME
  ls -la  $HOME/*.c || :
fi

Y aquí está el resultado de ejecutarlo en la raíz, luego el directorio de inicio.

$ cd /&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ / == /g/GNU-GCC/home/user ]]
+ dummy='---- C Files in current directory'
+ ls -la '*.c'
ls: *.c: No such file or directory
+ :
+ dummy='----. C Files in Home directory /g/GNU-GCC/home/user'
+ ls -la /g/GNU-GCC/home/user/HelloWorld.c /g/GNU-GCC/home/user/hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 /g/GNU-GCC/home/user/hw.c
+ dummy=---------------------------------

$ cd ~&&DemoScript
+ dummy='====================== Entering Test Script ================='
+ [[ /g/GNU-GCC/home/user == /g/GNU-GCC/home/user ]]
+ dummy='*** '
+ dummy='*** Working in home directory!'
+ dummy='*** '
+ ls -la HelloWorld.c hw.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 HelloWorld.c
-rw-r--r-- 1 user Administrators 73 Oct 10 22:21 hw.c
+ dummy=---------------------------------
HiTechHiTouch
fuente
1
Tenga en cuenta que incluso la versión editada de su respuesta eliminada (de la cual es una copia) todavía no responde la pregunta, ya que la respuesta no genera solo el mensaje sin el comando echo asociado, que fue lo que solicitó el OP.
DavidPostill
Nota, esta respuesta se está discutiendo en meta. ¿Me han penalizado por una respuesta que a 1 moderador no le gustó?
DavidPostill
@David He ampliado la explicación, según los comentarios en el meta foro.
HiTechHiTouch
@David Nuevamente, le animo a leer el OP con mucho cuidado, prestando atención al latín ".eg". El póster NO requiere que se use el comando echo. El OP solo hace referencia a echo como un ejemplo (junto con la redirección) de posibles métodos para resolver el problema. Resulta que tampoco lo necesitas,
HiTechHiTouch
¿Y qué pasa si el OP quiere hacer algo así echo "Here are the files in this directory:" *?
G-Man dice 'Restablecer a Monica' el