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
не влияет на это явление.
Ответы
Ответ 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
. Независимо от того, завершает ли эта ошибка первая часть в этом случае или нет, это не важно для вопроса.