Warum unterbricht "bash -x" dieses Skript?

13

Ich habe ein Skript, das misst, wie lange ein Befehl ausgeführt wird.

Es benötigt den timeBefehl "real" , was bedeutet, dass beispielsweise eine Binärdatei enthalten ist /usr/bin/time(da die eingebaute Bash nicht über das -fFlag verfügt).

Unten ein vereinfachtes Skript, das debuggt werden kann:

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

Speichern Sie als "test.sh" und führen Sie Folgendes aus:

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

Also hat es geklappt.

Versuchen wir nun, dies zu debuggen, indem wir "-x" in die Bash-Befehlszeile einfügen:

$ 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

Warum bricht dieses Skript ab, wenn wir "-x" verwenden und es ohne funktioniert?

Tomasz Chmielewski
quelle
1
Heh. Es sieht so aus, als würde mit -xon das $()Konstrukt die -xAusgabe als Teil seines resultierenden Wertes erhalten. Ich weiß nicht, ob das ein "erwartetes" Verhalten oder ein Fehler ist ... Oder vielleicht ist es die Subshell (), die tatsächlich die -xAusgabe liefert .
Jeff Y
Nebenbei: Mit der Einstellung BASH_XTRACEFDkönnen Sie die set -xAusgabe an einen Ort umleiten, an dem es weniger Probleme gibt.
Charles Duffy

Antworten:

21

Das Problem ist diese Zeile:

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

Hier leiten Sie den Standardfehler um, damit er mit der Standardausgabe übereinstimmt. Bash schreibt seine Trace-Meldungen in den Standardfehler und verwendet (zum Beispiel) seine eingebauten echozusammen mit anderen Shell-Konstrukten alle im Bash-Prozess.

Wenn du es in so etwas wie änderst

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

Es wird dieses Problem umgehen und möglicherweise ein akzeptabler Kompromiss zwischen Nachverfolgung und Arbeit sein:

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

Sie können auch einfach die Unterschale fallen lassen. Anscheinend sind es die verschachtelten Muscheln, die sich gegenseitig stören:

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

Wenn Sie tun:


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

... wickeln Sie mit dem Sub - Shell auf dem Markt gebracht, Abschnitt der Pipeline zu behandeln Hosting das Subshell innerhalb. Da die Shell ohne Weiterleitung sogar die Debug-Ausgabe der Subshell in den Abschnitt der Pipeline, in dem Sie Streams mischen, umleitet (wie dies auch für alle anderen Arten von {zusammengesetzten; } >redirect Befehlen der Fall wäre ) . Es hat mit der Reihenfolge der Umleitung zu tun.

Wenn Sie stattdessen einfach zuerst nur die Fehlerausgabe der Befehle umleiten , die Sie testen möchten, und die Ausgabe der Host-Shell auf stderr setzen, tritt nicht dasselbe Problem auf.

und so...


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

... kann die Host-Shell ihren stderr beliebig weiterschreiben und dabei nur die Ausgabe der von ihr aufgerufenen Befehle in die Pipe umleiten.


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!

Übrigens ...


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
quelle