Статус выхода Bash, используемый с PIPE

Статус выхода Bash, используемый с PIPE

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

which lss
echo $?
1

Так как whichне удалось найти, lssя получил статус выхода 1. Это нормально. Однако, когда я пробую следующее:

which lss | echo $?
0

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

решение1

Статус выхода канала — это статус выхода правой команды. Статус выхода левой команды игнорируется.

(Обратите внимание, что which lss | echo $?это не отображается. which lss | true; echo $?Чтобы это отобразить, вам нужно запустить . В which lss | echo $?, echo $?сообщается о состоянии последней команды перед этим конвейером.)

Причина, по которой оболочки ведут себя таким образом, заключается в том, что существует довольно распространенный сценарий, когда ошибка в левой части должна игнорироваться. Если правая часть завершается (или, в более общем смысле, закрывает свой стандартный ввод), пока левая часть все еще пишет, то левая часть получает сигнал SIGPIPE. В этом случае обычно нет ничего неправильного: правая часть не заботится о данных; если роль левой части заключается исключительно в создании этих данных, то ее можно остановить.

Однако если левая часть выходит из строя по какой-либо причине, не связанной с SIGPIPE, или если задача левой части заключается не только в создании данных на стандартном выводе, то ошибка в левой части является реальной ошибкой, о которой следует сообщить.

В простом sh единственным решением является использование именованного канала.

set -e
mkfifo p
command1 >p & pid1=$!
command2 <p
wait $pid1

В ksh, bash и zsh вы можете указать оболочке сделать выход из конвейера с ненулевым статусом, если любой компонент конвейера выходит с ненулевым статусом. Вам нужно установить опцию pipefail:

  • кш:set -o pipefail
  • Баш:shopt -s pipefail
  • зш: setopt pipefail(или setopt pipe_fail)

В mksh, bash и zsh можно получить состояние каждого компонента конвейера, используя переменную PIPESTATUS(bash, mksh) или pipestatus(zsh), которая представляет собой массив, содержащий состояние всех команд в последнем конвейере (обобщение $?).

решение2

Статусом выхода конвейера является статус выхода последней команды в конвейере (если только опция оболочки не pipefailустановлена ​​в оболочках, которые ее поддерживают; в этом случае статусом выхода будет статус последней команды в конвейере, которая завершается с ненулевым статусом).

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

Трубопровод

which lss | echo $?

бессмысленно, так как echoне читает свой стандартный ввод (конвейеры используются для передачи данных между выводом одной команды и вводом следующей). echoТакже не будет выведен статус выхода конвейера, но статус выхода команды будет запущен немедленнодотрубопровод.

$ false
$ which hello | echo $?
1
which: hello: Command not found.

$ true
$ which hello | echo $?
0
which: hello: Command not found.

Вот лучший пример:

$ echo hello | read a
$ echo $?
0

$ echo nonexistent | { read filename && rm $filename; }
rm: nonexistent: No such file or directory
$ echo $?
1

Это означает, что трубопровод также может использоваться с if:

if gzip -dc file.gz | grep -q 'something'; then
  echo 'something was found'
fi

решение3

Да, PIPE выдает код завершения; если вам нужен код завершения команды перед PIPE, вы можете использовать$PIPESTATUS

В соответствии сэтот Stack Overflow Вопросы и ответыЧтобы вывести статус завершения команды при использовании конвейера, можно сделать следующее:

your_command | echo $PIPESTATUS

Или установите pipefail с помощью команды set -o pipefail(чтобы отменить ее, выполните set +o pipefail), и используйте $?вместо $PIPESTATUS.

your_command | echo $?

Эти команды работают только после того, как вы выполните их хотя бы один раз. Это значит, PIPESTATUSчто они работают только тогда, когда вы запускаете их после команды с символом конвейера, например:

command_which_exit_10 | command_which_exit_1
echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"

Вы получите 10 за ${PIPESTATUS[0]}и 1 за ${PIPESTATUS[1]}. Смотретьэтот U&L Q&AЧтобы получить больше информации.

решение4

Оболочка оценивает командную строку до выполнения конвейера. Так же как $?и значение последней команды, выполненной до конвейера. echoЗапускается ли она сама до или после, whichне имеет значения.

Теперь, если ваш вопрос касался конкретно распространения статуса выхода, вы можете изучить поведение по умолчанию следующим образом:

% false | true
% echo $?
0

% true | false
% echo $?
1

Как видите, статус выхода конвейера — это статус выхода его последней команды.

Связанный контент