Shell pipe: выход сразу при сбое одной команды - PullRequest
23 голосов
/ 23 января 2012

Я использую канал нескольких команд в bash.Есть ли способ настроить bash для немедленного завершения всех команд во всем конвейере в случае сбоя одной из команд?

В моем случае первая команда, скажем command1, выполняется некоторое время, пока не получит некоторыевыход.Например, вы можете заменить command1 на (sleep 5 && echo "Hello").

Теперь command1 | false терпит неудачу через 5 секунд, но не сразу.

Это поведение, похоже, связано сколичество вывода, которое производит команда.Например, find / | false возвращается немедленно.

В общем, мне интересно, почему bash ведет себя так.Может ли кто-нибудь представить себе ситуацию, когда полезно, чтобы код, такой как command1 | non-existing-command, не выходил сразу?

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

PPS: Ни set -e, ни set -o pipefail, кажется, не влияют на это явление.

Ответы [ 4 ]

16 голосов
/ 23 января 2012

В документации по bash в разделе говорится о трубопроводах :

Каждая команда в конвейере выполняется в своей собственной оболочке [...]

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

Это объясняет, почему весь канал может быть успешно настроен, даже если одна из команд является бессмысленной. Bash не проверяет, может ли каждая команда быть запущена, он передает ее в подоболочки. Это также объясняет, почему, например, команда nonexisting-command | touch hello выдаст ошибку «команда не найдена», но файл hello будет создан.

В том же разделе также написано:

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

В sleep 5 | nonexisting-command, как указывал А.Х., sleep 5 завершается через 5 секунд, а не сразу, поэтому оболочка также будет ждать 5 секунд.

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

В любом случае, один немного уродливый способ - использовать FIFO:

mkfifo myfifo
./long-running-script.sh > myfifo &
whoops-a-typo < myfifo

Здесь запускается long-running-script.sh, а затем сценарии сразу выходят из строя на следующей строке. Используя несколько FIFO, это можно распространить на каналы с более чем двумя командами.

4 голосов
/ 23 января 2012

sleep 5 не производит никакого вывода до его завершения, в то время как find / немедленно производит вывод, который bash пытается передать в false.

3 голосов
/ 24 января 2012

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

Вы можете принудительно направить первую строку вывода сразу после запуска, например:

(sleep 0.1; echo; command1) | command2

Этот 100-миллисекундный сон предназначен для ожидания выхода возможной команды2 сразу после запуска.Конечно, если команда 2 завершится через 2 секунды, а команда 1 будет молчать в течение 60 секунд, вся команда оболочки вернется только через 60,1 секунды.

2 голосов
/ 23 января 2012

find / |false происходит быстрее, потому что первый системный вызов write(2) из find завершается с ошибкой EPIPE (Сломанный канал). Это потому, что false уже завершен и, следовательно, канал между этими двумя командами уже закрыт на одной стороне.

Если бы find проигнорировал бы эту ошибку (теоретически это могло бы произойти), он бы также "медленно работал".

(sleep 5 && echo "Hello") | false является "медленным отказом", потому что первая часть, sleep, не "проверяет" канал, записывая в него. Через 5 секунд echo также получает ошибку EPIPE. Вопрос о том, завершает ли эта ошибка первую часть в этом случае или нет, не имеет значения для вопроса.

...