Perché "bash -x" rompe questo script?


13

Ho uno script che misura per quanto tempo viene eseguito un comando.

Ha bisogno del timecomando "reale" , vale a dire un binario per esempio in /usr/bin/time(poiché il built-in bash non ha il -fflag).

Di seguito, uno script semplificato che può essere eseguito il debug:

#!/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

Salva come "test.sh" ed esegui:

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

Quindi ha funzionato.

Ora proviamo a eseguire il debug aggiungendo "-x" alla riga di 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

Perché questo script si interrompe quando stiamo usando "-x" e funziona bene senza di essa?


1
Eh. Sembra che con -xon il $()costrutto stia -xincludendo l' output come parte del valore risultante. Non so se si tratta di un comportamento "previsto" o di un bug ... O forse è la subshell ()all'interno che sta effettivamente dando l' -xoutput.
Jeff Y,

A parte: l'impostazione BASH_XTRACEFDconsente di reindirizzare l' set -xoutput da qualche parte è meno problematico.
Charles Duffy,

Risposte:


21

Il problema è questa linea:

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

dove stai reindirizzando l'errore standard in modo che corrisponda all'output standard. bash sta scrivendo i suoi messaggi di traccia sull'errore standard e (per esempio) sta usando il suo built-in echoinsieme ad altri costrutti di shell tutti nel processo bash.

Se lo cambi in qualcosa del genere

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

aggirerà quel problema, e forse sarà un compromesso accettabile tra traccia e lavoro:

++ 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!

7

puoi anche semplicemente rilasciare la subshell. a quanto pare sono i gusci nidificati che finiscono per sconvolgersi l'un l'altro:

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

Se fate:


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

... finisci con la subshell lanciata per gestire quella sezione della pipeline che ospita la subshell all'interno. Poiché la shell senza reindirizzare anche l'output di debug della subshell all'interno (come farebbe anche per qualsiasi altro tipo di {comando composto ; } >redirectche potresti scegliere di utilizzare) alla sua sezione della pipeline, finisci per mescolare i flussi. Ha a che fare con l'ordine di reindirizzamento.

Invece, se per prima cosa reindirizzi solo solo l'output di errore dei comandi che stai provando a misurare e lasci che l'output della shell host lo faccia diventare stderr, non finisci con lo stesso problema.

e così...


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

... la shell host è libera di continuare a scrivere il suo stderr dove preferisce reindirizzando solo l'output dei comandi che chiama nella pipe.


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!

Per questo motivo...


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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.