Чому "bash -x" порушує цей сценарій?


13

У мене є сценарій, який вимірює, як довго виконується деяка команда.

Для цього потрібна команда "real" time, тобто двійковий файл, наприклад у /usr/bin/time(оскільки вбудований bash не має -fпрапора).

Нижче спрощений сценарій, який можна налагодити:

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

Збережіть як "test.sh" і виконайте:

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

Так воно спрацювало.

Тепер спробуємо налагодити це, додавши "-x" до командного рядка 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

Чому цей сценарій ламається, коли ми використовуємо "-x", і працює без нього?


1
Хе. Схоже -x, що на, $()конструкт отримує -xвихідний результат як частину отриманого значення. Не знаю, чи це "очікувана" поведінка чи помилка ... А може, саме нижня оболонка ()всередині насправді дає -xвихід.
Jeff Y

Убік: налаштування BASH_XTRACEFDдозволяє перенаправляти set -xвисновки кудись, це менше проблем.
Чарльз Даффі

Відповіді:


21

Проблема полягає в цій лінії:

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

де ви переспрямовуєте стандартну помилку, щоб відповідати стандартному виводу. bash записує свої трас-повідомлення до стандартної помилки і використовується (наприклад), використовуючи вбудовані echoразом з іншими конструкціями оболонки всі в процесі bash.

Якщо ви поміняєте це на щось подібне

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

він вирішить цю проблему і, можливо, стане прийнятним компромісом між відстеженням та роботою:

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

ви також можете просто скинути нижню частину корпусу. мабуть, саме вкладені снаряди засмучують один одного:

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

Якщо ти зробиш:


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

... Ви закінчуєте підпусковий корпус, запущений для обробки тієї секції трубопроводу, що розміщує в ньому. Оскільки оболонка без переадресації навіть налагодження виводу підклітини всередині (як це було б і для будь-якого іншого типу {складеної команди, ; } >redirectяку ви можете вибрати), до її розділу трубопроводу, ви заводите змішування потоків. Це стосується порядку перенаправлення.

Натомість, якщо ви спершу просто переспрямовуєте лише вихід помилок команд, які ви намагаєтеся оцінити, і дозволите висновку оболонки хоста зробити його більш жорстким, ви не закінчитесь із тією ж проблемою.

і так ...


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

... оболонка хоста вільна продовжувати писати свій stderr там, де він забажається, лише перенаправляючи вихід команд, які він викликає, у трубу.


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!

В цьому відношенні...


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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.