¿Por qué "bash -x" rompe este script?

13

Tengo un script que mide cuánto tiempo se ejecuta un comando.

Necesita el timecomando "real" , es decir, un binario, por ejemplo, en /usr/bin/time(ya que bash-built-in no tiene la -fbandera).

A continuación, un script simplificado que se puede depurar:

#!/bin/bash

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

echo ABC--$TIMESEC--DEF

if [ "$TIMESEC" -eq 0 ] ; then
   echo "we are here!"
fi

Guardar como "test.sh" y ejecutar:

$ bash test.sh
ABC--0--DEF
we are here!

Entonces funcionó.

Ahora, intentemos depurar esto agregando "-x" a la línea de comando bash:

$ bash -x test.sh
++ echo blah
++ awk -F. '{print $1}'
+ TIMESEC='++ /usr/bin/time -f %e grep blah
0'
+ echo ABC--++ /usr/bin/time -f %e grep blah 0--DEF
ABC--++ /usr/bin/time -f %e grep blah 0--DEF
+ '[' '++ /usr/bin/time -f %e grep blah
0' -eq 0 ']'
test.sh: line 10: [: ++ /usr/bin/time -f %e grep blah
0: integer expression expected

¿Por qué este script se rompe cuando usamos "-x" y funciona bien sin él?

Tomasz Chmielewski
fuente
1
Je Parece que con -xon, la $()construcción está obteniendo la -xsalida incluida como parte de su valor resultante. Sin embargo, no sé si ese es el comportamiento "esperado" o un error ... O tal vez es la subshell ()que realmente está dando la -xsalida.
Jeff Y
Aparte: la configuración le BASH_XTRACEFDpermite redirigir la set -xsalida a un lugar donde sea menos problemático.
Charles Duffy

Respuestas:

21

El problema es esta línea:

TIMESEC=$(echo blah | ( /usr/bin/time -f %e grep blah >/dev/null ) 2>&1 | awk -F. '{print $1}')

donde está redirigiendo el error estándar para que coincida con la salida estándar. bash está escribiendo sus mensajes de rastreo en el error estándar, y está (por ejemplo) usando su construcción integrada echojunto con otras construcciones de shell, todo en el proceso bash.

Si lo cambias a algo como

TIMESEC=$(echo blah | sh -c "( /usr/bin/time -f %e grep blah >/dev/null )" 2>&1 | awk -F. '{print $1}')

solucionará ese problema y quizás sea un compromiso aceptable entre el rastreo y el trabajo:

++ awk -F. '{print $1}'
++ sh -c '( /usr/bin/time -f %e grep blah >/dev/null )'
++ echo blah
+ TIMESEC=0                 
+ echo ABC--0--DEF
ABC--0--DEF
+ '[' 0 -eq 0 ']'
+ echo 'we are here!'
we are here!
Thomas Dickey
fuente
7

También puede simplemente soltar la subshell. aparentemente son las conchas anidadas las que terminan molestándose unas a otras:

TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null |
    awk -F. '{print $1}'
)

Si lo haces:


...| ( subshell ) 2>pipe | ...

... terminas con el subshell lanzado para manejar esa sección de la tubería que aloja el subshell. Debido a que el shell sin redirecciona incluso la salida de depuración del subshell dentro (como también lo haría para cualquier otro tipo de {comando compuesto ; } >redirectque pueda elegir usar) a su sección de la tubería, termina mezclando flujos. Tiene que ver con el orden de redireccionamiento.

En cambio, si simplemente primera redirección solamente la salida de error de los comandos que se está tratando de calibre, y dejar que marca la salida de la cáscara de acogida a stderr, que no terminan con el mismo problema.

y entonces...


... | command 2>pipe 1>/dev/null | ...

... el shell del host es libre de continuar escribiendo su stderr donde le plazca mientras solo redirige la salida de los comandos que llama a la tubería.


bash -x time.sh
+++ echo blah
+++ /usr/bin/time -f %e grep blah
+++ awk -F. '{print $1}'
++ TIMESEC=0
++ echo ABC--0--DEF
ABC--0--DEF
++ '[' 0 -eq 0 ']'
++ echo 'we are here!'
we are here!

Para esa materia...


TIMESEC=$(
    echo blah |
    /usr/bin/time -f %e grep blah 2>&1 >/dev/null
)
printf %s\\n "ABC--${TIMESEC%%.*}--DEF"
if [ "${TIMESEC%%.*}" -eq 0 ] ; then
   echo "we are here!"
fi
mikeserv
fuente