Dlaczego „bash -x” psuje ten skrypt?

13

Mam skrypt mierzący czas wykonywania niektórych poleceń.

Potrzebuje polecenia „rzeczywistego” time, czyli binarnego na przykład w /usr/bin/time(ponieważ wbudowany bash nie ma -fflagi).

Poniżej uproszczony skrypt, który można debugować:

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

Zapisz jako „test.sh” i wykonaj:

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

Więc zadziałało.

Teraz spróbujmy to debugować, dodając „-x” do wiersza poleceń 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

Dlaczego ten skrypt się psuje, gdy używamy „-x” i działa bez niego dobrze?

Tomasz Chmielewski
źródło
1
Heh Wygląda -xna to, że z $()konstrukcją -xwłącza się wynik jako część jego wynikowej wartości. Nie wiem jednak, czy jest to „oczekiwane” zachowanie, czy błąd… A może to właśnie ()wewnętrzna powłoka daje -xwynik.
Jeff Y
Na bok: Ustawienie BASH_XTRACEFDpozwala przekierować set -xwyjście do miejsca, w którym jest mniej problemów.
Charles Duffy,

Odpowiedzi:

21

Problemem jest ta linia:

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

gdzie przekierowujesz standardowy błąd, aby dopasować go do standardowego wyjścia. bash zapisuje swoje komunikaty śledzenia do standardowego błędu i używa (na przykład) swojej wbudowanej aplikacji echowraz z innymi konstrukcjami powłoki w procesie bash.

Jeśli zmienisz to na coś takiego

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

obejdzie ten problem i być może będzie akceptowalnym kompromisem między śledzeniem a działaniem:

++ 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
źródło
7

możesz także po prostu upuścić podpowłokę. najwyraźniej są to zagnieżdżone muszle, które wzajemnie się denerwują:

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

Jeśli zrobisz:


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

... kończysz z uruchomioną podpowłoką, aby obsłużyć tę sekcję potoku, w której znajduje się podpowłoka. Ponieważ powłoka bez przekierowuje nawet dane wyjściowe debugowania podpowłoki wewnątrz (tak jak w przypadku każdego innego {polecenia złożonego, ; } >redirectktórego możesz użyć) do swojej części potoku, kończysz mieszanie strumieni. Ma to związek z kolejnością przekierowań.

Zamiast tego, jeśli po prostu najpierw przekierujesz tylko wyjście błędu poleceń, które próbujesz zmierzyć, i pozwolisz, aby wyjście powłoki hosta osiągnęło stderr, nie skończysz z tym samym problemem.

a więc...


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

... powłoka hosta może kontynuować zapisywanie swojego stderr tam, gdzie chce, a jedynie przekierowywać wyjście poleceń, które wywołuje do potoku.


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!

Z tego powodu...


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
źródło