Подтвердить что ты не робот

Shell pipe: выходить немедленно, когда одна команда не работает

Я использую трубку из нескольких команд в 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 не влияет на это явление.

4b9b3361

Ответ 1

Документация bash содержится в разделе о конвейерах:

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

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

Это объясняет, почему вся труба может быть успешно настроена, даже если одна из команд - глупость. bash не проверяет, может ли выполняться каждая команда, она делегирует это в подоболочки. Это также объясняет, почему, например, команда nonexisting-command | touch hello будет вызывать ошибку "command not found", но файл hello будет создан тем не менее.

В этом же разделе он также говорит:

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

(sleep 0.1; echo; command1) | command2

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

Ответ 4

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

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

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