11

Почему команда ниже не выходит? Вместо выхода цикл выполняется бесконечно.

В то время как я обнаружил это поведение с помощью более сложной настройки, простейшая форма команды сводится к следующему.

Не выходит:

while /usr/bin/true ; do echo "ok" | cat ; done | exit 1

Там нет опечаток выше. Каждый '|' это труба. «Выход 1» означает другой процесс, который запущен и завершен.

Я ожидаю, что "выход 1" вызовет SIGPIPE в цикле while (запись в канал без читателя) и разрыв цикла. Но цикл продолжает работать.

Почему команда не останавливается?

2 ответа2

13

Это связано с выбором в реализации.

Выполнение того же скрипта в Solaris с ksh93 приводит к другому поведению:

$ while /usr/bin/true ; do echo "ok" | cat ; done | exit 1
cat: write error [Broken pipe]

Причиной проблемы является внутренний конвейер, без которого цикл завершается независимо от оболочки / ОС:

$ while /usr/bin/true ; do echo "ok" ; done | exit 1
$

cat получает сигнал SIGPIPE под bash, но оболочка все равно повторяет цикл.

Process 5659 suspended
[pid 28801] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28801] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28801 detached
Process 28800 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28802 attached
Process 28803 attached
[pid 28803] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
Process 5659 suspended
[pid 28803] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28803 detached
Process 28802 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
Process 28804 attached
Process 28805 attached (waiting for parent)
Process 28805 resumed (parent 5659 ready)
Process 5659 suspended
[pid 28805] execve("/bin/cat", ["cat"], [/* 63 vars */]) = 0
[pid 28805] --- SIGPIPE (Broken pipe) @ 0 (0) ---
Process 5659 resumed
Process 28805 detached
Process 28804 detached
--- SIGCHLD (Child exited) @ 0 (0) ---

Документация Bash гласит:

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

Кш документация гласит:

Каждая команда, за исключением, возможно, последней, запускается как отдельный процесс; оболочка ожидает завершения последней команды .

POSIX заявляет:

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

0

Эта проблема мучила меня годами. Спасибо jilliagre за толчок в правильном направлении.

Немного переформулировав вопрос, на моем linux box это выходит как положено:

while true ; do echo "ok"; done | head

Но если я добавлю канал, он не выйдет, как ожидалось:

while true ; do echo "ok" | cat; done | head

Это расстраивало меня годами. Рассматривая ответ, написанный jilliagre, я придумал это замечательное исправление:

while true ; do echo "ok" | cat || exit; done | head

QED ...

Ну, не совсем. Вот что-то более сложное:

i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' || exit
done | head

Это не работает правильно. Я добавил || exit чтобы он знал, как завершить работу рано, но самое первое echo не соответствует grep поэтому цикл сразу же завершается. В этом случае вас действительно не интересует состояние выхода grep . Мой обходной путь - добавить еще одного cat . Итак, вот надуманный сценарий под названием "десятки":

#!/bin/bash
i=0
while true; do
    i=`expr $i + 1`
    echo "$i" | grep '0$' | cat || exit
done

Это правильно завершается при запуске как tens | head Слава Богу.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .