Запустите выше в bashили, zshи вы получите те же результаты; только один из retval_bashи retval_zshбудет установлен. Другой будет пустым. Это позволит функции заканчиваться return $retval_bash $retval_zsh(обратите внимание на отсутствие кавычек!).
И pipestatusв зш. К сожалению, другие оболочки не имеют этой функции.
Жиль
8
Примечание: массивы в zsh начинаются нелогично с индекса 1, так что это так echo "$pipestatus[1]" "$pipestatus[2]".
Кристоф Вурм
5
Вы можете проверить весь конвейер следующим образом:if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
l0b0
7
@JanHudec: Возможно, вы должны прочитать первые пять слов моего ответа. Также любезно укажите, где вопрос требовал ответа только для POSIX.
Camh
12
@JanHudec: И при этом это не было помечено POSIX. Почему вы предполагаете, что ответ должен быть POSIX? Это не было указано, поэтому я дал квалифицированный ответ. В моем ответе нет ничего неправильного, плюс есть несколько ответов для других случаев.
camh
238
Есть 3 распространенных способа сделать это:
Pipefail
Первый способ , чтобы установить pipefailпараметр ( ksh, zshили bash). Это самое простое, и он в основном устанавливает статус $?выхода для кода завершения последней программы, чтобы он выходил из ненулевого значения (или ноля, если все выходы успешно завершены).
@ Патрик, решение pipestatus работает на bash, просто больше вопросов, если я использую сценарий ksh, как вы думаете, мы сможем найти что-то похожее на pipestatus? (Между тем я вижу, что pshestatus не поддерживается ksh)
yael
2
@yael Я не использую ksh, но, если взглянуть на справочную страницу , она не поддерживает $PIPESTATUSили что-то подобное. Это поддерживает pipefailопцию, хотя.
Патрик
1
Я решил пойти с pipefail, так как он позволяет мне получить статус LOG=$(failed_command | successful_command)
сбойной
51
Это решение работает без использования специальных функций bash или временных файлов. Бонус: в конце концов, статус выхода на самом деле является статусом выхода, а не какой-то строкой в файле.
Ситуация:
someprog | filter
Вы хотите статус выхода из someprogи выход из filter.
Результатом этой конструкции является стандартный вывод filterкак стандартный вывод конструкции и статус выхода из someprogсостояния выхода конструкции.
эта конструкция также работает с простой группировкой команд {...}вместо подоболочек (...). Подоболочки имеют некоторые последствия, в том числе затраты на производительность, которые нам здесь не нужны. прочтите подробное руководство по bash для получения более подробной информации: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html.
Примечание: дочерний процесс наследует дескрипторы открытого файла от родительского. Это означает, someprogчто унаследует дескриптор 3 и 4 открытого файла. Если someprogвыполняется запись в дескриптор 3 файла, то это станет статусом выхода. Реальный статус выхода будет проигнорирован, потому что readчитает только один раз.
Если вы беспокоитесь о том, что вы someprogможете записать в файловый дескриптор 3 или 4, то лучше всего закрыть файловые дескрипторы перед вызовом someprog.
The exec 3>&- 4>&-before someprogзакрывает файловый дескриптор перед выполнением, someprogпоэтому для someprogэтих файловых дескрипторов просто не существует.
Подоболочка создается с файловым дескриптором 4, перенаправленным на стандартный вывод. Это означает, что все, что будет напечатано в файловом дескрипторе 4 в подоболочке, в конечном итоге станет выводом всей конструкции.
Канал создается, и команды left ( #part3) и right ( #part2) выполняются. exit $xsтакже является последней командой канала, и это означает, что строка из stdin будет статусом выхода всей конструкции.
Подоболочка создается с файловым дескриптором 3, перенаправленным на стандартный вывод. Это означает, что все, что будет напечатано в файловом дескрипторе 3 в этой подоболочке, будет в конечном итоге #part2и, в свою очередь, будет выходным состоянием всей конструкции.
Канал создается, и команды слева ( #part5и #part6) и справа ( filter >&4) выполняются. Выходные данные filterперенаправляются в файловый дескриптор 4. В #part1файловом дескрипторе 4 был перенаправлен на стандартный вывод. Это означает, что выводом filterявляется стандартный вывод всей конструкции.
Статус выхода из #part6распечатывается в файловый дескриптор 3. В #part3файловом дескрипторе 3 был перенаправлен на #part2. Это означает, что статус выхода из #part6будет окончательным статусом выхода для всей конструкции.
someprogвыполнен. Статус выхода принимается #part5. Stdout берется по трубе #part4и пересылается filter. Выход из filterволи в свою очередь достигает стандартного вывода, как объяснено в#part4
set -o pipefailвнутри скрипта должен быть более устойчивым, например, в случае, если кто-то выполняет скрипт через bash foo.sh.
maxschlepzig
Как это работает? у тебя есть пример?
Йохан
2
Обратите внимание, что -o pipefailнет в POSIX.
scy
2
Это не работает в моем выпуске BASH 3.2.25 (1). Вверху / tmp / ff у меня есть #!/bin/bash -o pipefail. Ошибка:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
Фелипе Альварес
2
@FelipeAlvarez: В некоторых средах (включая Linux) не разобрать пробела на #!линию за пределами первого, и таким образом это становится /bin/bash-o pipefail/tmp/ff, вместо необходимого /bin/bash-opipefail/tmp/ff- getopt(или аналогичного) разбора использование optarg, который является следующим пунктом в ARGV, в качестве аргумента чтобы -o, так что не получается. Если бы вам нужно было сделать обертку (скажем, bash-pfчто только что сделал exec /bin/bash -o pipefail "$@"и поместить это в #!строку, это сработало бы. См. Также: en.wikipedia.org/wiki/Shebang_%28Unix%29
lindes
22
Что я делаю, когда это возможно, чтобы передать код выхода из fooв bar. Например, если я знаю, что fooникогда не выдает строку, состоящую только из цифр, я могу просто добавить код выхода:
Это всегда можно сделать, если есть какой-то способ заставить barработать все, кроме последней строки, и передать последнюю строку в качестве кода выхода.
Если barэто сложный конвейер, вывод которого вам не нужен, вы можете обойти его часть, напечатав код завершения в другом файловом дескрипторе.
exit_codes=$({{ foo; echo foo:"$?">&3;}|{ bar >/dev/null; echo bar:"$?">&3;}}3>&1)
После этого $exit_codesобычно foo:X bar:Y, но это может быть, bar:Y foo:Xесли выйдет, barпрежде чем прочитать все его входные данные или если вам не повезло. Я думаю , что пишет для труб диаметром до 512 байт атомарны на всех юниксы, так foo:$?и bar:$?части не будут смешиваться, пока строки тегов находятся в 507 байт.
Если вам нужно захватить выходные данные bar, это становится трудным. Вы можете комбинировать описанные выше приемы, организовав вывод barникогда не содержать строку, которая выглядит как указание кода выхода, но она становится неудобной.
output=$(echo;{{ foo; echo foo:"$?">&3;}|{ bar | sed 's/^/^/'; echo bar:"$?">&3;}}3>&1)
nl='
'
foo_exit_code=${output#*${nl}foo:}; foo_exit_code=${foo_exit_code%%$nl*}
bar_exit_code=${output#*${nl}bar:}; bar_exit_code=${bar_exit_code%%$nl*}
output=$(printf %s "$output"| sed -n 's/^\^//p')
И, конечно же, есть простой вариант использования временного файла для хранения статуса. Простой, но не , что простой в производстве:
Если одновременно запущено несколько сценариев или один и тот же сценарий использует этот метод в нескольких местах, необходимо убедиться, что они используют разные временные имена файлов.
Надежно создать временный файл в общей папке. Зачастую /tmpэто единственное место, где скрипт наверняка сможет писать файлы. Используйте mktemp, который не является POSIX, но в настоящее время доступен на всех серьезных устройствах.
При использовании подхода с временными файлами я предпочитаю добавлять ловушку для EXIT, которая удаляет все временные файлы, чтобы не осталось мусора, даже если скрипт умрет
miracle173
17
Начиная с конвейера:
foo | bar | baz
Вот общее решение с использованием только оболочки POSIX и без временных файлов:
$error_statuses содержит коды состояния любых сбойных процессов в произвольном порядке с индексами, указывающими, какая команда отправила каждое состояние.
# if "bar" failed, output its status:
echo "$error_statuses"| grep '1:'| cut -d:-f2
# test if all commands succeeded:
test -z "$error_statuses"# test if the last command succeeded:! echo "$error_statuses"| grep '2:'>/dev/null
Обратите внимание на цитаты $error_statusesв моих тестах; без них grepневозможно дифференцироваться, потому что переводы строк приводятся к пробелам.
Поэтому я хотел дать ответ, подобный ответу Лесманы, но я думаю, что мой, пожалуй, немного проще и немного более выгодно решение с чистой оболочкой Борна:
# You want to pipe command1 through command2:
exec 4>&1
exitstatus=`{ { command1; printf $? 1>&3; } | command2 1>&4; } 3>&1`# $exitstatus now has command1's exit status.
Я думаю, это лучше всего объяснить изнутри - command1 выполнит и напечатает свой обычный вывод на stdout (дескриптор файла 1), затем, как только это будет сделано, printf выполнит и напечатает код выхода command1 на своем stdout, но этот stdout перенаправляется на дескриптор файла 3.
Пока команда command1 выполняется, ее стандартный вывод передается в command2 (вывод printf никогда не попадает в command2, потому что мы отправляем его в файловый дескриптор 3 вместо 1, который читает канал). Затем мы перенаправляем вывод command2 в файловый дескриптор 4, чтобы он также не входил в файловый дескриптор 1 - потому что мы хотим, чтобы файловый дескриптор 1 был немного позже, потому что мы перенесем вывод printf из файлового дескриптора 3 обратно в файловый дескриптор 1 - потому что это то, что команда замещения (обратные метки), будет захватывать, и это то, что будет помещено в переменную.
Последнее волшебство заключается в том, что сначала exec 4>&1мы сделали отдельную команду - она открывает дескриптор файла 4 как копию стандартного вывода внешней оболочки. Подстановка команд будет захватывать все, что написано в стандарте, с точки зрения команд внутри него - но, поскольку выходные данные команды 2 собираются в файловом дескрипторе 4, что касается подстановки команд, подстановка команд не захватывает это - однако, как только он «выходит» из подстановки команд, он фактически все еще идет к общему файловому дескриптору скрипта 1.
(Это exec 4>&1должна быть отдельная команда, потому что многим распространенным оболочкам не нравится, когда вы пытаетесь записать в файловый дескриптор внутри подстановки команды, которая открывается во «внешней» команде, использующей подстановку. Так что это Простейший портативный способ сделать это.)
Вы можете посмотреть на это менее технически и более игриво, как если бы результаты команд перепрыгивали друг друга: команда 1 отправляет канал команде 2, затем вывод printf перепрыгивает через команду 2, так что команда 2 не перехватывает ее, а затем выходные данные команды 2 перепрыгивают из подстановки команд так же, как и printf приземляется как раз вовремя, чтобы быть захваченными подстановкой, так что она попадает в переменную, а выходные данные команды 2 продолжают работать, записываясь в стандартный вывод, так же, как в обычной трубе.
Кроме того, насколько я понимаю, он по- $?прежнему будет содержать код возврата второй команды в конвейере, поскольку назначения переменных, подстановки команд и составные команды эффективно прозрачны для кода возврата команды внутри них, поэтому статус возврата command2 должен быть распространен - это, и не нужно определять дополнительную функцию, поэтому я думаю, что это могло бы быть несколько лучшим решением, чем предложенное lesmana.
В соответствии с предостережениями, которые упоминает лесмена, вполне возможно, что в какой-то момент команда1 будет использовать файловые дескрипторы 3 или 4, поэтому для большей надежности вы должны сделать следующее:
Обратите внимание, что в моем примере я использую составные команды, но подоболочки (использование ( )вместо { }будет также работать, хотя, возможно, будет менее эффективным.)
Команды наследуют файловые дескрипторы от процесса, который их запускает, поэтому вся вторая строка наследует файловый дескриптор четыре, а составная команда, за которой следует, 3>&1наследует файловый дескриптор три. Таким образом, команда 4>&-гарантирует, что внутренняя составная команда не унаследует файловый дескриптор четыре, и 3>&-не унаследует файловый дескриптор три, поэтому команда 1 получает «более чистую», более стандартную среду. Вы также можете перемещать внутреннее 4>&-рядом с 3>&-, но я понимаю, почему бы просто не ограничить его область настолько, насколько это возможно.
Я не уверен, как часто вещи используют файловый дескриптор 3 и 4 напрямую - я думаю, что в большинстве случаев программы используют системные вызовы, которые возвращают неиспользуемые в данный момент файловые дескрипторы, но иногда код записывает код непосредственно в файловый дескриптор 3, я предположить (я мог бы представить программу, проверяющую дескриптор файла, чтобы увидеть, открыт ли он, и использующую его, если он есть, или соответствующим образом ведущий себя иначе, если это не так). Поэтому последнее, вероятно, лучше всего учитывать и использовать в случаях общего назначения.
Выглядит интересно, но я не могу понять, что вы ожидаете от этой команды, и мой компьютер тоже не может; Я получаю -bash: 3: Bad file descriptor.
G-Man
@ G-Man Хорошо, я продолжаю забывать, что bash понятия не имеет, что он делает, когда дело доходит до файловых дескрипторов, в отличие от оболочек, которые я обычно использую (пепел, который поставляется с busybox). Я дам вам знать, когда подумаю об обходном пути, который делает Bash счастливым. В то же время, если у вас есть удобный Debian Box, вы можете попробовать его в Dash, или, если у вас есть удобный busybox, вы можете попробовать его с busybox ash / sh.
mtraceur
@ G-Man То, что я ожидаю от команды и что она делает в других оболочках, это перенаправление stdout из command1, поэтому оно не попадает под замену команды, но после выхода из подстановки команды оно падает fd3 возвращается в стандартный вывод, поэтому он передается, как ожидается, command2. Когда команда1 завершается, printf запускает и печатает свой статус выхода, который фиксируется в переменной подстановкой команды. Очень подробная разбивка здесь: stackoverflow.com/questions/985876/tee-and-exit-status/… Кроме того, ваш комментарий читается так, как если бы он был своего рода оскорбительным?
mtraceur
С чего мне начать? (1) Извините, если вы почувствовали себя оскорбленным. «Выглядит интересно» подразумевалось серьезно; было бы замечательно, если бы что-нибудь компактное сработало так, как вы ожидали. Кроме того, я просто говорил, что не понимаю, что должно делать ваше решение. Я работал / играл с Unix в течение длительного времени (с тех пор, как существовал Linux), и, если я чего-то не понимаю, это красный флаг, который, может быть, другие люди тоже не поймут, и это Нужно больше объяснений (ИМНШО). … (Продолжение)
G-Man
(Продолжение)… Так как вам «… нравится думать… что [вы] понимаете почти все, чем обычный человек», возможно, вам следует помнить, что цель Stack Exchange - не быть службой написания команд, тысячи разовых решений тривиально разных вопросов; а научить людей, как решать свои проблемы. И для этого, возможно, вам нужно объяснить все достаточно хорошо, чтобы «средний человек» мог это понять. Посмотрите на ответ Lesmana для примера отличного объяснения. … (Продолжение)
G-Man
11
Если у вас установлен пакет moreutils , вы можете использовать утилиту mispipe, которая делает именно то, что вы просили.
Приведенное выше решение lesmana также может быть выполнено без дополнительных затрат на запуск вложенных подпроцессов с использованием { .. }взамен (помня, что эта форма сгруппированных команд всегда должна заканчиваться точкой с запятой). Что-то вроде этого:
Я проверил эту конструкцию с помощью dash версии 0.5.5 и bash версий 3.2.25 и 4.2.42, поэтому, даже если некоторые оболочки не поддерживают { .. }группировку, она по-прежнему совместима с POSIX.
Это работает очень хорошо с большинством оболочек, с которыми я пробовал, включая NetBSD sh, pdksh, mksh, dash, bash. Однако я не могу заставить его работать с AT & T Ksh (93s +, 93u +) или zsh (4.3.9, 5.2), даже с set -o pipefailin ksh или любым количеством набросанных waitкоманд в обоих. Я думаю, что это может быть, по крайней мере частично, проблемой синтаксического анализа для ksh, так как если я придерживаюсь использования подоболочек, то это работает нормально, но даже если ifвыбрать вариант подоболочки для ksh, но оставить составные команды для других, это не удастся ,
Грег А. Вудс
4
Это переносимо, то есть работает с любой POSIX-совместимой оболочкой, не требует записи в текущий каталог и позволяет одновременно запускать несколько сценариев, использующих один и тот же прием.
Это не работает по нескольким причинам. 1. Временный файл может быть прочитан до его записи. 2. Создание временного файла в общем каталоге с предсказуемым именем небезопасно (тривиальная DoS, гонка символических ссылок). 3. Если один и тот же скрипт использует этот трюк несколько раз, он всегда будет использовать одно и то же имя файла. Чтобы решить 1, прочитайте файл после завершения конвейера. Для решения 2 и 3 используйте временный файл со случайно сгенерированным именем или в личном каталоге.
Жиль
+1 Ну, $ {PIPESTATUS [0]} проще, но основная идея здесь работает, если знать о проблемах, о которых упоминает Жиль.
Йохан
Вы можете сэкономить несколько подоболочек: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Я согласен, что с Bash легче, но в некоторых случаях знание того, как избежать Bash, того стоит.
dubiousjim
4
Следующее подразумевается как дополнение к ответу @Patrik, если вы не можете использовать одно из распространенных решений.
Этот ответ предполагает следующее:
У вас есть оболочка, которая не знает $PIPESTATUSниset -o pipefail
Вы хотите использовать канал для параллельного выполнения, поэтому нет временных файлов.
Вы не хотите иметь дополнительный беспорядок, если прервет сценарий, возможно, из-за внезапного отключения электроэнергии.
Это решение должно быть относительно простым для понимания и понятным для чтения.
Вы не хотите вводить дополнительные субоболочки.
Вы не можете возиться с существующими файловыми дескрипторами, поэтому не следует трогать stdin / out / err (однако вы можете временно ввести новый)
Дополнительные предположения. Вы можете избавиться от всего, но этот рецепт слишком сильно забивает, поэтому здесь он не рассматривается:
Все, что вы хотите знать, это то, что все команды в ТРУБЕ имеют код выхода 0.
Вам не нужна дополнительная информация о боковой полосе.
Ваша оболочка ожидает возвращения всех команд канала.
Before: foo | bar | bazоднако это только возвращает код завершения последней команды ( baz)
Wanted: $?не должно быть 0(true), если какая-либо из команд в конвейере завершилась неудачно
После:
TMPRESULTS="`mktemp`"{
rm -f "$TMPRESULTS"{ foo || echo $?>&9;}|{ bar || echo $?>&9;}|{ baz || echo $?>&9;}#wait! read TMPRESULTS <&8}9>>"$TMPRESULTS"8<"$TMPRESULTS"# $? now is 0 only if all commands had exit code 0
Разъяснение:
Tempfile создается с mktemp. Обычно это сразу создает файл в/tmp
Этот временный файл затем перенаправляется на FD 9 для записи и FD 8 для чтения
Затем временный файл немедленно удаляется. Тем не менее, он остается открытым, пока оба FD не исчезнут.
Теперь труба запущена. Каждый шаг добавляет только к FD 9, если произошла ошибка.
waitНеобходимо для ksh, потому что kshеще не ждать , пока все трубы команды , чтобы закончить. Однако обратите внимание, что при наличии некоторых фоновых задач возможны нежелательные побочные эффекты, поэтому я закомментировал их по умолчанию. Если ожидание не помешает, вы можете оставить комментарий.
После этого содержимое файла читается. Если оно пустое (потому что все работало) readвозвращает false, значит trueуказывает на ошибку
Это может использоваться как замена плагина для одной команды и требует только следующее:
Неиспользованные FD 9 и 8
Одна переменная окружения для хранения имени временного файла
И этот рецепт может быть адаптирован к любой оболочке, которая позволяет перенаправление ввода-вывода
Кроме того, он не зависит от платформы и не нуждается в таких вещах, как /proc/fd/N
ошибки:
В этом скрипте есть ошибка на случай /tmpнехватки места. Если вам также нужна защита от этого искусственного случая, вы можете сделать это следующим образом, однако это имеет недостаток, заключающийся в том, что число 0in 000зависит от количества команд в конвейере, поэтому это немного сложнее:
kshи подобные оболочки, которые ожидают только последнюю команду канала, нуждаются в waitнезакомментированном
Последний пример использует printf "%1s" "$?"вместо того, echo -n "$?"потому что это более переносимо. Не каждая платформа интерпретирует -nправильно.
printf "$?"будет делать это также, однако printf "%1s"ловит некоторые угловые случаи в случае, если вы запускаете скрипт на какой-то действительно сломанной платформе. (Читайте: если вам случится программировать на paranoia_mode=extreme.)
FD 8 и FD 9 могут быть выше на платформах, которые поддерживают несколько цифр. AFAIR POSIX-совместимая оболочка должна поддерживать только однозначные числа.
Был протестирован с Debian 8.2 sh, bash, ksh, ash, sashи дажеcsh
Как насчет очистки, как jlliagre? Разве вы не оставляете файл с именем foo-status?
Йохан
@Johan: Если вы предпочитаете мое предложение, не стесняйтесь голосовать за него;) Помимо того, что он не оставляет файл, он имеет преимущество в том, что позволяет нескольким процессам запускать это одновременно, и текущий каталог не должен быть доступен для записи.
Jlliagre
2
Следующий блок if будет выполняться только в случае успешного выполнения команды:
if command;then# ...fi
В частности, вы можете запустить что-то вроде этого:
Который запустит haconf -makerwи сохранит свои stdout и stderr в "$ haconf_out". Если возвращаемое значение from haconfравно true, тогда блок 'if' будет выполнен и grepбудет читать «$ haconf_out», пытаясь сопоставить его с «Cluster уже доступным для записи».
Обратите внимание, что трубы автоматически очищаются; с перенаправлением вы должны быть осторожны, чтобы удалить «$ haconf_out», когда закончите.
Не так элегантно, как pipefail, но законная альтернатива, если эта функциональность недоступна.
(По крайней мере, с bash) в сочетании с set -eодним может использовать subshell для явной эмуляции pipefail и выхода при ошибке канала
set-e
foo | bar
( exit ${PIPESTATUS[0]})
rest of program
Так что, если fooпо какой-то причине происходит сбой - остальная часть программы не будет выполнена, и скрипт завершится с соответствующим кодом ошибки. (Это предполагает, что fooпечатает свою ошибку, которой достаточно, чтобы понять причину сбоя)
# =========================================================== ## Preceding a _pipe_ with ! inverts the exit status returned.
ls | bogus_command # bash: bogus_command: command not found
echo $?# 127! ls | bogus_command # bash: bogus_command: command not found
echo $?# 0# Note that the ! does not change the execution of the pipe.# Only the exit status changes.# =========================================================== #
Я думаю, что это не связано. В вашем примере я хочу знать код выхода ls, а не инвертировать код выходаbogus_command
Майкл Мрозек
2
Я предлагаю отозвать этот ответ.
maxschlepzig
3
Ну, похоже, я идиот. Я фактически использовал это в сценарии, прежде чем подумать, что он сделал то, что хотел ОП. Хорошо, что я не использовал это ни для чего важного
Ответы:
Если вы используете
bash
, вы можете использоватьPIPESTATUS
переменную массива, чтобы получить состояние выхода каждого элемента конвейера.Если вы используете
zsh
, они называют массивpipestatus
(дело имеет значение!), А индексы массива начинаются с одного:Чтобы объединить их внутри функции таким образом, чтобы значения не терялись:
Запустите выше в
bash
или,zsh
и вы получите те же результаты; только один изretval_bash
иretval_zsh
будет установлен. Другой будет пустым. Это позволит функции заканчиватьсяreturn $retval_bash $retval_zsh
(обратите внимание на отсутствие кавычек!).источник
pipestatus
в зш. К сожалению, другие оболочки не имеют этой функции.echo "$pipestatus[1]" "$pipestatus[2]"
.if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi
Есть 3 распространенных способа сделать это:
Pipefail
Первый способ , чтобы установить
pipefail
параметр (ksh
,zsh
илиbash
). Это самое простое, и он в основном устанавливает статус$?
выхода для кода завершения последней программы, чтобы он выходил из ненулевого значения (или ноля, если все выходы успешно завершены).$ PIPESTATUS
Bash также имеет переменную-массив
$PIPESTATUS
($pipestatus
inzsh
), которая содержит состояние завершения всех программ в последнем конвейере.Вы можете использовать третий пример команды, чтобы получить конкретное значение в конвейере, которое вам нужно.
Отдельные казни
Это самое громоздкое из решений. Запустите каждую команду отдельно и сохраните статус
источник
ksh
, но, если взглянуть на справочную страницу , она не поддерживает$PIPESTATUS
или что-то подобное. Это поддерживаетpipefail
опцию, хотя.LOG=$(failed_command | successful_command)
Это решение работает без использования специальных функций bash или временных файлов. Бонус: в конце концов, статус выхода на самом деле является статусом выхода, а не какой-то строкой в файле.
Ситуация:
Вы хотите статус выхода из
someprog
и выход изfilter
.Вот мое решение:
Результатом этой конструкции является стандартный вывод
filter
как стандартный вывод конструкции и статус выхода изsomeprog
состояния выхода конструкции.эта конструкция также работает с простой группировкой команд
{...}
вместо подоболочек(...)
. Подоболочки имеют некоторые последствия, в том числе затраты на производительность, которые нам здесь не нужны. прочтите подробное руководство по bash для получения более подробной информации: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html.К сожалению, грамматике bash требуются пробелы и точки с запятой для фигурных скобок, так что конструкция становится намного более просторной.
Для остальной части этого текста я буду использовать вариант subshell.
Пример
someprog
иfilter
:Пример вывода:
Примечание: дочерний процесс наследует дескрипторы открытого файла от родительского. Это означает,
someprog
что унаследует дескриптор 3 и 4 открытого файла. Еслиsomeprog
выполняется запись в дескриптор 3 файла, то это станет статусом выхода. Реальный статус выхода будет проигнорирован, потому чтоread
читает только один раз.Если вы беспокоитесь о том, что вы
someprog
можете записать в файловый дескриптор 3 или 4, то лучше всего закрыть файловые дескрипторы перед вызовомsomeprog
.The
exec 3>&- 4>&-
beforesomeprog
закрывает файловый дескриптор перед выполнением,someprog
поэтому дляsomeprog
этих файловых дескрипторов просто не существует.Это также можно записать так:
someprog 3>&- 4>&-
Пошаговое объяснение конструкции:
Снизу вверх:
#part3
) и right (#part2
) выполняются.exit $xs
также является последней командой канала, и это означает, что строка из stdin будет статусом выхода всей конструкции.#part2
и, в свою очередь, будет выходным состоянием всей конструкции.#part5
и#part6
) и справа (filter >&4
) выполняются. Выходные данныеfilter
перенаправляются в файловый дескриптор 4. В#part1
файловом дескрипторе 4 был перенаправлен на стандартный вывод. Это означает, что выводомfilter
является стандартный вывод всей конструкции.#part6
распечатывается в файловый дескриптор 3. В#part3
файловом дескрипторе 3 был перенаправлен на#part2
. Это означает, что статус выхода из#part6
будет окончательным статусом выхода для всей конструкции.someprog
выполнен. Статус выхода принимается#part5
. Stdout берется по трубе#part4
и пересылаетсяfilter
. Выход изfilter
воли в свою очередь достигает стандартного вывода, как объяснено в#part4
источник
(read; exit $REPLY)
(exec 3>&- 4>&-; someprog)
упрощает доsomeprog 3>&- 4>&-
.{ { { { someprog 3>&- 4>&-; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; }; } 4>&1
Хотя не совсем то, что вы просили, вы могли бы использовать
так что ваши каналы возвращают последний ненулевой возврат.
может быть немного меньше кодирования
Изменить: Пример
источник
set -o pipefail
внутри скрипта должен быть более устойчивым, например, в случае, если кто-то выполняет скрипт черезbash foo.sh
.-o pipefail
нет в POSIX.#!/bin/bash -o pipefail
. Ошибка:/bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name
#!
линию за пределами первого, и таким образом это становится/bin/bash
-o pipefail
/tmp/ff
, вместо необходимого/bin/bash
-o
pipefail
/tmp/ff
-getopt
(или аналогичного) разбора использованиеoptarg
, который является следующим пунктом вARGV
, в качестве аргумента чтобы-o
, так что не получается. Если бы вам нужно было сделать обертку (скажем,bash-pf
что только что сделалexec /bin/bash -o pipefail "$@"
и поместить это в#!
строку, это сработало бы. См. Также: en.wikipedia.org/wiki/Shebang_%28Unix%29Что я делаю, когда это возможно, чтобы передать код выхода из
foo
вbar
. Например, если я знаю, чтоfoo
никогда не выдает строку, состоящую только из цифр, я могу просто добавить код выхода:Или, если я знаю, что вывод
foo
никогда не содержит строку с просто.
:Это всегда можно сделать, если есть какой-то способ заставить
bar
работать все, кроме последней строки, и передать последнюю строку в качестве кода выхода.Если
bar
это сложный конвейер, вывод которого вам не нужен, вы можете обойти его часть, напечатав код завершения в другом файловом дескрипторе.После этого
$exit_codes
обычноfoo:X bar:Y
, но это может быть,bar:Y foo:X
если выйдет,bar
прежде чем прочитать все его входные данные или если вам не повезло. Я думаю , что пишет для труб диаметром до 512 байт атомарны на всех юниксы, такfoo:$?
иbar:$?
части не будут смешиваться, пока строки тегов находятся в 507 байт.Если вам нужно захватить выходные данные
bar
, это становится трудным. Вы можете комбинировать описанные выше приемы, организовав выводbar
никогда не содержать строку, которая выглядит как указание кода выхода, но она становится неудобной.И, конечно же, есть простой вариант использования временного файла для хранения статуса. Простой, но не , что простой в производстве:
/tmp
это единственное место, где скрипт наверняка сможет писать файлы. Используйтеmktemp
, который не является POSIX, но в настоящее время доступен на всех серьезных устройствах.источник
Начиная с конвейера:
Вот общее решение с использованием только оболочки POSIX и без временных файлов:
$error_statuses
содержит коды состояния любых сбойных процессов в произвольном порядке с индексами, указывающими, какая команда отправила каждое состояние.Обратите внимание на цитаты
$error_statuses
в моих тестах; без нихgrep
невозможно дифференцироваться, потому что переводы строк приводятся к пробелам.источник
Поэтому я хотел дать ответ, подобный ответу Лесманы, но я думаю, что мой, пожалуй, немного проще и немного более выгодно решение с чистой оболочкой Борна:
Я думаю, это лучше всего объяснить изнутри - command1 выполнит и напечатает свой обычный вывод на stdout (дескриптор файла 1), затем, как только это будет сделано, printf выполнит и напечатает код выхода command1 на своем stdout, но этот stdout перенаправляется на дескриптор файла 3.
Пока команда command1 выполняется, ее стандартный вывод передается в command2 (вывод printf никогда не попадает в command2, потому что мы отправляем его в файловый дескриптор 3 вместо 1, который читает канал). Затем мы перенаправляем вывод command2 в файловый дескриптор 4, чтобы он также не входил в файловый дескриптор 1 - потому что мы хотим, чтобы файловый дескриптор 1 был немного позже, потому что мы перенесем вывод printf из файлового дескриптора 3 обратно в файловый дескриптор 1 - потому что это то, что команда замещения (обратные метки), будет захватывать, и это то, что будет помещено в переменную.
Последнее волшебство заключается в том, что сначала
exec 4>&1
мы сделали отдельную команду - она открывает дескриптор файла 4 как копию стандартного вывода внешней оболочки. Подстановка команд будет захватывать все, что написано в стандарте, с точки зрения команд внутри него - но, поскольку выходные данные команды 2 собираются в файловом дескрипторе 4, что касается подстановки команд, подстановка команд не захватывает это - однако, как только он «выходит» из подстановки команд, он фактически все еще идет к общему файловому дескриптору скрипта 1.(Это
exec 4>&1
должна быть отдельная команда, потому что многим распространенным оболочкам не нравится, когда вы пытаетесь записать в файловый дескриптор внутри подстановки команды, которая открывается во «внешней» команде, использующей подстановку. Так что это Простейший портативный способ сделать это.)Вы можете посмотреть на это менее технически и более игриво, как если бы результаты команд перепрыгивали друг друга: команда 1 отправляет канал команде 2, затем вывод printf перепрыгивает через команду 2, так что команда 2 не перехватывает ее, а затем выходные данные команды 2 перепрыгивают из подстановки команд так же, как и printf приземляется как раз вовремя, чтобы быть захваченными подстановкой, так что она попадает в переменную, а выходные данные команды 2 продолжают работать, записываясь в стандартный вывод, так же, как в обычной трубе.
Кроме того, насколько я понимаю, он по-
$?
прежнему будет содержать код возврата второй команды в конвейере, поскольку назначения переменных, подстановки команд и составные команды эффективно прозрачны для кода возврата команды внутри них, поэтому статус возврата command2 должен быть распространен - это, и не нужно определять дополнительную функцию, поэтому я думаю, что это могло бы быть несколько лучшим решением, чем предложенное lesmana.В соответствии с предостережениями, которые упоминает лесмена, вполне возможно, что в какой-то момент команда1 будет использовать файловые дескрипторы 3 или 4, поэтому для большей надежности вы должны сделать следующее:
Обратите внимание, что в моем примере я использую составные команды, но подоболочки (использование
( )
вместо{ }
будет также работать, хотя, возможно, будет менее эффективным.)Команды наследуют файловые дескрипторы от процесса, который их запускает, поэтому вся вторая строка наследует файловый дескриптор четыре, а составная команда, за которой следует,
3>&1
наследует файловый дескриптор три. Таким образом, команда4>&-
гарантирует, что внутренняя составная команда не унаследует файловый дескриптор четыре, и3>&-
не унаследует файловый дескриптор три, поэтому команда 1 получает «более чистую», более стандартную среду. Вы также можете перемещать внутреннее4>&-
рядом с3>&-
, но я понимаю, почему бы просто не ограничить его область настолько, насколько это возможно.Я не уверен, как часто вещи используют файловый дескриптор 3 и 4 напрямую - я думаю, что в большинстве случаев программы используют системные вызовы, которые возвращают неиспользуемые в данный момент файловые дескрипторы, но иногда код записывает код непосредственно в файловый дескриптор 3, я предположить (я мог бы представить программу, проверяющую дескриптор файла, чтобы увидеть, открыт ли он, и использующую его, если он есть, или соответствующим образом ведущий себя иначе, если это не так). Поэтому последнее, вероятно, лучше всего учитывать и использовать в случаях общего назначения.
источник
-bash: 3: Bad file descriptor
.Если у вас установлен пакет moreutils , вы можете использовать утилиту mispipe, которая делает именно то, что вы просили.
источник
Приведенное выше решение lesmana также может быть выполнено без дополнительных затрат на запуск вложенных подпроцессов с использованием
{ .. }
взамен (помня, что эта форма сгруппированных команд всегда должна заканчиваться точкой с запятой). Что-то вроде этого:Я проверил эту конструкцию с помощью dash версии 0.5.5 и bash версий 3.2.25 и 4.2.42, поэтому, даже если некоторые оболочки не поддерживают
{ .. }
группировку, она по-прежнему совместима с POSIX.источник
set -o pipefail
in ksh или любым количеством набросанныхwait
команд в обоих. Я думаю, что это может быть, по крайней мере частично, проблемой синтаксического анализа для ksh, так как если я придерживаюсь использования подоболочек, то это работает нормально, но даже еслиif
выбрать вариант подоболочки для ksh, но оставить составные команды для других, это не удастся ,Это переносимо, то есть работает с любой POSIX-совместимой оболочкой, не требует записи в текущий каталог и позволяет одновременно запускать несколько сценариев, использующих один и тот же прием.
Изменить: вот более сильная версия после комментариев Жиля:
Edit2: и вот немного более легкий вариант после комментария dubiousjim:
источник
(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
. @Johan: Я согласен, что с Bash легче, но в некоторых случаях знание того, как избежать Bash, того стоит.Следующее подразумевается как дополнение к ответу @Patrik, если вы не можете использовать одно из распространенных решений.
Этот ответ предполагает следующее:
$PIPESTATUS
ниset -o pipefail
Before:
foo | bar | baz
однако это только возвращает код завершения последней команды (baz
)Wanted:
$?
не должно быть0
(true), если какая-либо из команд в конвейере завершилась неудачноПосле:
Разъяснение:
mktemp
. Обычно это сразу создает файл в/tmp
wait
Необходимо дляksh
, потому чтоksh
еще не ждать , пока все трубы команды , чтобы закончить. Однако обратите внимание, что при наличии некоторых фоновых задач возможны нежелательные побочные эффекты, поэтому я закомментировал их по умолчанию. Если ожидание не помешает, вы можете оставить комментарий.read
возвращаетfalse
, значитtrue
указывает на ошибкуЭто может использоваться как замена плагина для одной команды и требует только следующее:
/proc/fd/N
ошибки:
В этом скрипте есть ошибка на случай
/tmp
нехватки места. Если вам также нужна защита от этого искусственного случая, вы можете сделать это следующим образом, однако это имеет недостаток, заключающийся в том, что число0
in000
зависит от количества команд в конвейере, поэтому это немного сложнее:Примечания по переносимости:
ksh
и подобные оболочки, которые ожидают только последнюю команду канала, нуждаются вwait
незакомментированномПоследний пример использует
printf "%1s" "$?"
вместо того,echo -n "$?"
потому что это более переносимо. Не каждая платформа интерпретирует-n
правильно.printf "$?"
будет делать это также, однакоprintf "%1s"
ловит некоторые угловые случаи в случае, если вы запускаете скрипт на какой-то действительно сломанной платформе. (Читайте: если вам случится программировать наparanoia_mode=extreme
.)FD 8 и FD 9 могут быть выше на платформах, которые поддерживают несколько цифр. AFAIR POSIX-совместимая оболочка должна поддерживать только однозначные числа.
Был протестирован с Debian 8.2
sh
,bash
,ksh
,ash
,sash
и дажеcsh
источник
С некоторой осторожностью это должно сработать:
источник
Следующий блок if будет выполняться только в случае успешного выполнения команды:
В частности, вы можете запустить что-то вроде этого:
Который запустит
haconf -makerw
и сохранит свои stdout и stderr в "$ haconf_out". Если возвращаемое значение fromhaconf
равно true, тогда блок 'if' будет выполнен иgrep
будет читать «$ haconf_out», пытаясь сопоставить его с «Cluster уже доступным для записи».Обратите внимание, что трубы автоматически очищаются; с перенаправлением вы должны быть осторожны, чтобы удалить «$ haconf_out», когда закончите.
Не так элегантно, как
pipefail
, но законная альтернатива, если эта функциональность недоступна.источник
источник
(По крайней мере, с bash) в сочетании с
set -e
одним может использовать subshell для явной эмуляции pipefail и выхода при ошибке каналаТак что, если
foo
по какой-то причине происходит сбой - остальная часть программы не будет выполнена, и скрипт завершится с соответствующим кодом ошибки. (Это предполагает, чтоfoo
печатает свою ошибку, которой достаточно, чтобы понять причину сбоя)источник
РЕДАКТИРОВАТЬ : Этот ответ неправильный, но интересный, поэтому я оставлю его для дальнейшего использования.
Добавление
!
к команде инвертирует код возврата.http://tldp.org/LDP/abs/html/exit-status.html
источник
ls
, а не инвертировать код выходаbogus_command