Информировать правую часть трубопровода о левосторонней неисправности? - PullRequest
23 голосов
/ 04 июля 2011

Я полюбил использовать генераторный паттерн между функциями в моих скриптах оболочки. Примерно так:

parse_commands /da/cmd/file | process_commands

Однако основная проблема с этим шаблоном заключается в том, что если parse_command обнаруживает ошибку, я нашел единственный способ уведомить process_command о том, что она завершилась неудачей, путем явного сообщения об этом (например, echo "FILE_NOT_FOUND"). Это означает, что каждая потенциально ошибочная операция в parse_command должна быть ограждена.

Нет ли способа, чтобы process_command мог обнаружить, что левая сторона вышла с ненулевым кодом выхода?

Ответы [ 9 ]

29 голосов
/ 12 июля 2013

Используйте set -o pipefail в верхней части скрипта bash, чтобы при выходе из строя левой стороны канала (состояние выхода! = 0) правая сторона не выполнялась.

14 голосов
/ 04 июля 2011

Продолжается ли конвейерный процесс, даже если первый процесс завершился, или у вас нет возможности узнать, что первый процесс завершился неудачей?

Если это последнее, вы можете посмотреть на переменную PIPESTATUS (которая на самом деле является массивом BASH). Это даст вам код выхода первой команды:

parse_commands /da/cmd/file | process_commands
temp=("${PIPESTATUS[@]}")
if [ ${temp[0]} -ne 0 ]
then
    echo 'parse_commands failed'
elif [ ${temp[1]} -ne 0 ]
then
    echo 'parse_commands worked, but process_commands failed'
fi

В противном случае вам придется использовать совместные процессы.

5 голосов
/ 04 июля 2011

В отличие от оператора and (&&), оператор pipe (|) работает, порождая оба процесса одновременно, поэтому первый процесс может направить свой вывод во второй процесс без необходимости буферизации промежуточных данных.Это позволяет обрабатывать большие объемы данных с небольшим использованием памяти или диска.

Таким образом, состояние завершения первого процесса не будет доступно для второго до его завершения.

4 голосов
/ 04 июля 2011

Вы можете попробовать немного поработать, используя fifo:

mkfifo /tmp/a
cat /tmp/a | process_commands &

parse_cmd /da/cmd/file > /tmp/a || (echo "error"; # kill process_commands)
2 голосов
/ 09 февраля 2015

У меня недостаточно репутации, чтобы комментировать, но в принятом ответе пропущен закрывающий } в строке 5.

После исправления код выдаст -ne: unary operator expected ошибка, которая указывает на проблему: PIPESTATUS перезаписывается условным условием после команды if, поэтому возвращаемое значение process_commands никогда не будет проверяться!

Это потому, что [ ${PIPESTATUS[0]} -ne 0 ] эквивалентно test ${PIPESTATUS[0]} -ne 0, что меняет $PIPESTATUS, как и любая другая команда.Например:

return0 () { return 0;}
return3 () { return 3;}

return0 | return3
echo "PIPESTATUS: ${PIPESTATUS[@]}"

Возвращает PIPESTATUS: 0 3, как и ожидалось.Но что, если мы введем условные выражения?

return0 | return3
if [ ${PIPESTATUS[0]} -ne 0 ]; then
    echo "1st command error: ${PIPESTATUS[0]}"
elif [ ${PIPESTATUS[1]} -ne 0 ]; then
    echo "2nd command error: ${PIPESTATUS[1]}"
else
    echo "PIPESTATUS: ${PIPESTATUS[@]}"
    echo "Both return codes = 0."
fi

Мы получаем ошибку [: -ne: unary operator expected, и это:

PIPESTATUS: 2
Both return codes = 0.

Чтобы исправить это, $PIPESTATUS должен храниться в другом массивепеременная, например:

return0 | return3
TEMP=("${PIPESTATUS[@]}")
echo "TEMP: ${TEMP[@]}"
if [ ${TEMP[0]} -ne 0 ]; then
    echo "1st command error: ${TEMP[0]}"
elif [ ${TEMP[1]} -ne 0 ]; then
    echo "2nd command error: ${TEMP[1]}"
else
    echo "TEMP: ${TEMP[@]}"
    echo "All return codes = 0."
fi

, которая печатает:

TEMP: 0 3
2nd command error: 3

, как и предполагалось.

Редактировать: я исправил принятый ответ, но я оставляю это объяснениедля потомков.

0 голосов
/ 13 мая 2013

А как же:

parse_commands /da/cmd/file > >(process_commands)
0 голосов
/ 04 июля 2011

Вы можете запустить parse_commands /da/cmd/file в явной подоболочке и echo состояние выхода этой подоболочки через канал к process_commands, который также запускается в явной подоболочке для обработки переданных по конвейеру данных, содержащихся в /dev/stdin.

Далеко не элегантно, но, кажется, выполняет свою работу:)

Простой пример:

(
( ls -l ~/.bashrcxyz; echo $? ) | 
( 
piped="$(</dev/stdin)"; 
[[ "$(tail -n 1 <<<"$piped")" -eq 0 ]] && printf '%s\n' "$piped" | sed '$d' || exit 77 
); 
echo $?
)
0 голосов
/ 04 июля 2011

Есть способ сделать это в bash 4.0, который добавляет встроенную coproc из пепла.Это средство сопроцессирования заимствовано из ksh, который использует другой синтаксис.Единственная оболочка, к которой у меня есть доступ в моей системе, которая поддерживает сопроцессы, это ksh.Вот решение, написанное с помощью ksh:

parse_commands  /da/cmd/file |&
parser=$!

process_commands <&p &
processor=$!

if wait $parser
then
    wait $processor
    exit $?
else
    kill $processor
    exit 1
fi

Идея состоит в том, чтобы запустить parse_commands в фоновом режиме с трубами, соединяющими его с основной оболочкой.Pid сохраняется в parser.Затем process_commands запускается с выходом parse_commands в качестве его входа.(Это то, что делает <&p.) Это также помещается в фоновый режим с его pid, сохраненным в processor.

С обоими из них в фоновом режиме, соединенными трубой, наша основная оболочка свободна длядождитесь окончания парсера.Если он завершается без ошибки, мы ждем, пока процессор завершит работу и выйдет с кодом возврата.Если он завершает с ошибкой, мы убиваем процессор и завершаем работу с ненулевым статусом.

Было бы довольно просто перевести это на использование встроенного bash 4.0 / ash coproc, но у меня нет ни хорошей документации, ни способа проверить это.

0 голосов
/ 04 июля 2011

Если у вас есть command1 && command2, то команда2 будет выполняться только тогда, когда первая команда будет успешной - в противном случае возникнет логическое короткое замыкание. Одним из способов использования этого было бы создание первой команды (вашей parse_commands...), которая выводит дампво временную, а затем попросите вторую команду взять из этого файла.

Редактировать: При разумном использовании ; вы можете привести в порядок временный файл, например,

(command1 && command2) ; rm temporaryfile
...