Состояние выхода Bash используется с PIPE

10

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

which lss
echo $?
1

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

which lss | echo $?
0

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

sshekhar1980
источник
2
Все компоненты трубопровода работают одновременно . Таким образом, в which lss | echo $?, echoначинается до whichвыхода; оболочка, генерирующая командную строку для, echoне имеет возможности узнать, каким будет состояние выхода whichпозже.
Чарльз Даффи
Они не бегают одновременно. Вся цель канала - перенаправить вывод одной команды в другую. Последняя команда, которую выполнила оболочка (т.е. последняя в вашем выражении), будет той, которая возвращает код возврата.
Тим С.
3
@TimS. Но они работают в одно и то же время, вот как вы можете получить первые биты вывода до завершения первой команды, если это очень длительная команда.
Random832
Я думаю, я думал о начале выполнения - есть определенное совпадение, но процессы не запускаются вместе. Я понимаю, что вы имеете в виду, хотя моя ошибка. (Канал перенаправляет вывод, а не выполнение ...) Я испортил свои комментарии, но, надеюсь, вы понимаете, о чем я. :)
Тим С.
да, но начало исполнения не имеет значения, важен конец; последняя команда начинается до окончания первой
user371366

Ответы:

14

Состояние выхода конвейера - это состояние выхода последней команды в конвейере (если в оболочках, 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
Кусалананда
источник
10

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

(Обратите внимание, что which lss | echo $?это не показано. Вы должны запустить, which lss | true; echo $?чтобы показать это. In 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), которая является массивом, содержащим статус всех команд в последнем конвейере (обобщение $?).

Жиль "ТАК - перестань быть злым"
источник
Спасибо, Жиль, я не знал об этих сложностях. Это действительно прояснило это для меня.
sshekhar1980
4

Да, PIPE выдает статус выхода; если вы хотите код выхода команды перед ТРУБОЙ, вы можете использовать$PIPESTATUS

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

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 для получения дополнительной информации.

jeremy.Snidaro
источник
2
Несмотря PIPESTATUSна то, что содержит состояния выхода команд в последнем конвейере, ваш первый пример не имеет больше смысла, чем тот somecmd | echo $?, который Кусалананда рассмотрел в своем ответе. false; true | echo $PIPESTATUSпечать 1для состояния выхода false.
ilkkachu
Upvote для PIPESTATUS и pipefiail, но |exhoэто действительно сбивает с толку
eckes
0

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

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

% false | true
% echo $?
0

% true | false
% echo $?
1

Как вы можете видеть, состояние выхода конвейера - это состояние выхода его последней команды.

Alexis
источник