Почему «bash -x» ломает этот скрипт?

13

У меня есть скрипт, который измеряет, как долго выполняется какая-то команда.

Ему нужна «настоящая» 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
Хех. Похоже с -xon, что $()конструкция получает -xвывод, включенный как часть его результирующего значения. Не знаю, является ли это "ожидаемым" поведением или ошибкой ... Или, может быть, именно ()внутренняя оболочка фактически дает -xрезультат.
Джефф У
В стороне: Настройка 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вы могли бы выбрать) для своей секции конвейера, вы смешиваете потоки. Это связано с порядком перенаправления.

Вместо этого, если вы просто сначала перенаправите только вывод ошибок команд, которые вы пытаетесь выполнить, и позволите выводу оболочки хоста перейти в stderr, вы не столкнетесь с такой же проблемой.

и так...


... | 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
mikeserv
источник