Получите статус выхода процесса, это передается по каналу другому

Собственный вес команды удалит от текущей позиции курсора до начала следующего словесного символа. d$ команды (примечание, это - знак доллара, не 'S') удалит из текущей позиции курсора в конец текущей строки. D является синонимом за d$.

303
17.05.2013, 20:18
17 ответов

Если Вы используете bash, можно использовать PIPESTATUS переменная типа массив для получения статуса выхода каждого элемента конвейера.

$ false | true
$ echo "${PIPESTATUS[0]} ${PIPESTATUS[1]}"
1 0

Если Вы используете zsh, они выстраивают, назван pipestatus (вопросы случая!) и индексы массива запускаются в одном:

$ false | true
$ echo "${pipestatus[1]} ${pipestatus[2]}"
1 0

Для объединения их в функции способом, который не теряет значения:

$ false | true
$ retval_bash="${PIPESTATUS[0]}" retval_zsh="${pipestatus[1]}" retval_final=$?
$ echo $retval_bash $retval_zsh $retval_final
1 0

Выполненное вышеупомянутое в bash или zsh и Вы получите те же результаты; только один из retval_bash и retval_zsh будет установлен. Другой будет пробел. Это позволило бы функции заканчиваться return $retval_bash $retval_zsh (отметьте отсутствие кавычек!).

267
27.01.2020, 19:26
  • 1
    И pipestatus в zsh. К сожалению, другие оболочки не имеют этой функции. –  Gilles 'SO- stop being evil' 03.06.2011, 00:05
  • 2
    Отметьте: Массивы в zsh начинаются парадоксально в индексе 1, таким образом, это echo "$pipestatus[1]" "$pipestatus[2]". –  Christoph Wurm 14.11.2011, 16:09
  • 3
    Вы могли проверить целый конвейер как это: if [ `echo "${PIPESTATUS[@]}" | tr -s ' ' + | bc` -ne 0 ]; then echo FAIL; fi –  l0b0 25.09.2012, 18:39
  • 4
    @JanHudec: Возможно, необходимо считать первые пять слов моего ответа. Также любезно укажите, где вопрос запросил ответ только для POSIX. –  camh 16.12.2014, 10:27
  • 5
    @JanHudec: И при этом это не был отмеченный POSIX. Почему Вы предполагаете, что ответом должен быть POSIX? Это не было указано так, я предоставил квалифицированный ответ. Нет ничего неправильного о моем ответе, плюс существует несколько ответов для обращения к другим случаям. –  camh 17.12.2014, 11:15

Править: Этот ответ является неправильным, но интересным, таким образом, я оставлю его для дальнейшего использования.


Добавление a ! к команде инвертирует код возврата.

http://tldp.org/LDP/abs/html/exit-status.html

# =========================================================== #
# 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.
# =========================================================== #
-1
27.01.2020, 19:26
  • 1
    , я думаю, что это не связано. В Вашем примере я хочу знать код выхода ls, не инвертируют код выхода bogus_command –  Michael Mrozek♦ 03.06.2011, 01:13
  • 2
    я предлагаю отодвинуть тот ответ. –  maxschlepzig 03.06.2011, 12:15
  • 3
    Ну, кажется, что я - идиот. Я на самом деле использовал это в сценарии прежде, чем думать, что он сделал то, что хотел OP. Хорошая вещь я не использовал его ни для чего важный –  Falmarri 08.06.2011, 20:14

С небольшим количеством предосторожности это должно работать:

foo-status=$(mktemp -t)
(foo; echo $? >$foo-status) | bar
foo_status=$(cat $foo-status)
3
27.01.2020, 19:26
  • 1
    Как насчет к очистке как jlliagre? Разве Вы не оставляете файл позади названного состояния нечто? –  Johan 08.06.2011, 21:39
  • 2
    @Johan: Если Вы предпочитаете мое предложение, проголосуйте за него ;) Кроме того, не к отъезду файла, это имеет преимущество разрешения нескольких процессов выполнить это одновременно, и текущий каталог не должен быть перезаписываем. –  jlliagre 08.06.2011, 23:40

Это портативно, т.е. работает с любым POSIX совместимая оболочка, не требует, чтобы текущий каталог был перезаписываем, и позволяет несколько сценариев с помощью того же приема для выполнения одновременно.

(foo;echo $?>/tmp/_$$)|(bar;exit $(cat /tmp/_$$;rm /tmp/_$$))

Править: вот более сильная версия после комментариев Gilles:

(s=/tmp/.$$_$RANDOM;((foo;echo $?>$s)|(bar)); exit $(cat $s;rm $s))

Edit2: и вот немного более легкий вариант после комментария dubiousjim:

(s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s))
4
27.01.2020, 19:26
  • 1
    Это не работает по нескольким причинам. 1. Временный файл может быть считан, прежде чем он будет записан. 2. Создание временного файла в общем каталоге с предсказуемым именем небезопасно (тривиальный DoS, гонка символьной ссылки). 3. Если тот же сценарий будет несколько раз использовать этот прием, то он будет всегда использовать то же имя файла. Для решения 1 считайте файл после того, как конвейер завершится. Для решения 2 и 3 используйте временный файл со случайным образом сгенерированным именем или в частном каталоге. –  Gilles 'SO- stop being evil' 09.06.2011, 02:00
  • 2
    +1 Хорошо $ {PIPESTATUS [0]} легче, но основная идея здесь работают, если Вы знаете о проблемах, что Gilles упоминает. –  Johan 09.06.2011, 09:36
  • 3
    Можно сохранить несколько подоболочек: (s=/tmp/.$$_$RANDOM;{foo;echo $?>$s;}|bar; exit $(cat $s;rm $s)). @Johan: Я соглашаюсь, что легче с Bash, но в некоторых контекстах, зная, как избежать, чтобы Bash стоил того. –  dubiousjim 30.08.2012, 01:25

Запуск с конвейера:

foo | bar | baz

Вот общее решение с помощью только оболочку POSIX и никакие временные файлы:

exec 4>&1
error_statuses="`((foo || echo "0:$?" >&3) |
        (bar || echo "1:$?" >&3) | 
        (baz || echo "2:$?" >&3)) 3>&1 >&4`"
exec 4>&-

$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 не может дифференцироваться, потому что новые строки принуждены к пробелам.

17
27.01.2020, 19:26

Что я делаю, если это возможно, должен подать код выхода от foo в bar. Например, если я знаю это foo никогда не продолжает линию только с цифрами, затем я могу просто лавировать на коде выхода:

{ foo; echo "$?"; } | awk '!/[^0-9]/ {exit($0)} {…}'

Или если я знаю что вывод от foo никогда не содержит строку только с .:

{ foo; echo .; echo "$?"; } | awk '/^\.$/ {getline; exit($0)} {…}'

Это может всегда делаться, если существует некоторый способ добраться 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, но доступный на всех серьезных нельдах в наше время.
foo_ret_file=$(mktemp -t)
{ foo; echo "$?" >"$foo_ret_file"; } | bar
bar_ret=$?
foo_ret=$(cat "$foo_ret_file"; rm -f "$foo_ret_file")
22
27.01.2020, 19:26
  • 1
    Когда использование временного файла приближается, я предпочитаю добавлять прерывание для ВЫХОДА, который удаляет все временные файлы так, чтобы никакой мусор не оставляли, даже если сценарий умрет –  miracle173 16.10.2013, 23:16

В то время как не точно, что Вы спросили, Вы могли использовать

#!/bin/bash -o pipefail

так, чтобы Ваши каналы возвратили последнее не нулевой возврат.

мог бы немного меньше кодировать

Править: Пример

[root@localhost ~]# false | true
[root@localhost ~]# echo $?
0
[root@localhost ~]# set -o pipefail
[root@localhost ~]# false | true
[root@localhost ~]# echo $?
1
37
27.01.2020, 19:26
  • 1
    set -o pipefail в сценарии должно быть более устойчивым, например, в случае, если кто-то выполняет сценарий через bash foo.sh. –  maxschlepzig 03.06.2011, 12:17
  • 2
    Как это работает? у Вас есть пример? –  Johan 08.06.2011, 21:40
  • 3
    Отметьте это -o pipefail не находится в POSIX. –  scy 25.01.2013, 17:15
  • 4
    Это не работает в моем Bash 3.2.25 (1) - выпуск. Наверху/tmp/ff я имею #!/bin/bash -o pipefail. Ошибка: /bin/bash: line 0: /bin/bash: /tmp/ff: invalid option name –  Felipe Alvarez 24.03.2014, 08:01
  • 5
    @FelipeAlvarez: Некоторые среды (включая Linux) не анализируют пробелы на #! строки вне первой, и таким образом, это становится /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 –  lindes 05.12.2015, 02:09

Следующее, 'если' блок будет работать только если 'команда', за которой следуют:

if command; then
   # ...
fi

Специфически говорящий, можно выполнить что-то вроде этого:

haconf_out=/path/to/some/temporary/file

if haconf -makerw > "$haconf_out" 2>&1; then
   grep -iq "Cluster already writable" "$haconf_out"
   # ...
fi

Который будет работать haconf -makerw и сохраните его stdout и stderr к "$haconf_out". Если возвращенное значение от haconf верно, затем, 'если' блок будет выполняться и grep считает "$haconf_out", пытаясь соответствовать ему против "Кластера, уже перезаписываемого".

Заметьте, что каналы автоматически моются; с перенаправлением необходимо будет быть осторожными для удаления "$haconf_out" при выполнении.

Не столь изящный как pipefail, но законная альтернатива, если эта функциональность не в пределах досягаемости.

2
27.01.2020, 19:26

Существует 3 распространенных способа сделать это:

Pipefail

Первый путь состоит в том, чтобы установить pipefail опция (ksh, zsh или bash). Это является самым простым и что это делает в основном установлен статус выхода $? к коду выхода последней программы, которая выйдет ненулевой (или нуль, если все вышли успешно).

$ false | true; echo $?
0
$ set -o pipefail
$ false | true; echo $?
1

$PIPESTATUS

Bash также назвали переменную типа массив $PIPESTATUS ($pipestatus в zsh) который содержит статус выхода всех программ в последнем конвейере.

$ true | true; echo "${PIPESTATUS[@]}"
0 0
$ false | true; echo "${PIPESTATUS[@]}"
1 0
$ false | true; echo "${PIPESTATUS[0]}"
1
$ true | false; echo "${PIPESTATUS[@]}"
0 1

Можно использовать 3-й пример команды для получения определенного значения в конвейере, в котором Вы нуждаетесь.

Отдельное выполнение

Это является самым громоздким из решений. Выполните каждую команду отдельно и получите состояние

$ OUTPUT="$(echo foo)"
$ STATUS_ECHO="$?"
$ printf '%s' "$OUTPUT" | grep -iq "bar"
$ STATUS_GREP="$?"
$ echo "$STATUS_ECHO $STATUS_GREP"
0 1
249
27.01.2020, 19:26
  • 1
    Штопка! Я просто собирался отправлять о PIPESTATUS. –  slm♦ 21.04.2013, 16:57
  • 2
    Для ссылки существует несколько других методов, обсужденных в этом ТАК вопрос: stackoverflow.com/questions/1221833 / … –  slm♦ 21.04.2013, 17:00
  • 3
    @Patrick, pipestatus решением являются работы над ударом, просто больше вопроса в случае, если я использую ksh сценарий, Вы думаете, что мы можем найти что-то подобным pipestatus?, (между тем я вижу pipestatus, не поддерживаемый ksh), –  yael 21.04.2013, 17:32
  • 4
    @yael я не использую ksh, но от краткого взгляда на он - страница справочника, это не поддерживает $PIPESTATUS или что-либо подобное. Это действительно поддерживает pipefail опция все же. –  Patrick 21.04.2013, 18:30
  • 5
    я решил пойти с pipefail, поскольку он позволяет мне получать состояние неудавшейся команды здесь: LOG=$(failed_command | successful_command) –  vmrob 08.02.2014, 11:20

Это решение работает, не используя удар определенные функции или временные файлы. Премия: в конце статус выхода является на самом деле статусом выхода и не некоторой строкой в файле.

Ситуация:

someprog | filter

Вы хотите статус выхода от someprog и вывод от filter.

Вот мое решение:

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

результатом этой конструкции является stdout от filter как stdout конструкции и статуса выхода от someprog как статус выхода конструкции.


эта конструкция также работает с простой группировкой команды {...} вместо подоболочек (...). подоболочки имеют некоторые последствия среди других стоимость производительности, в которой мы не нуждаемся здесь. прочитайте прекрасное руководство удара для получения дополнительной информации: https://www.gnu.org/software/bash/manual/html_node/Command-Grouping.html

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1

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

Для остальной части этого текста я буду использовать вариант подоболочки.


Пример someprog и filter:

someprog() {
  echo "line1"
  echo "line2"
  echo "line3"
  return 42
}

filter() {
  while read line; do
    echo "filtered $line"
  done
}

((((someprog; echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

echo $?

Пример произвел:

filtered line1
filtered line2
filtered line3
42

Примечание: дочерний процесс наследовал открытые дескрипторы файлов от родителя. Это означает someprog наследует открытый дескриптор файла 3 и 4. Если someprog записи к дескриптору файла 3 затем это станет статусом выхода. Реальный статус выхода будет проигнорирован потому что read только чтения однажды.

Если Вы волнуетесь что Ваш someprog мог бы записать в дескриптор файла 3, или 4 затем лучше закрывать дескрипторы файлов перед вызовом someprog.

(((((exec 3>&- 4>&-; someprog); echo $? >&3) | filter >&4) 3>&1) | (read xs; exit $xs)) 4>&1

exec 3>&- 4>&- прежде someprog закрывает дескриптор файла перед выполнением someprog таким образом для someprog те дескрипторы файлов просто не существуют.

Это может также быть записано как это: someprog 3>&- 4>&-


Пошаговое объяснение конструкции:

( ( ( ( someprog;          #part6
        echo $? >&3        #part5
      ) | filter >&4       #part4
    ) 3>&1                 #part3
  ) | (read xs; exit $xs)  #part2
) 4>&1                     #part1

От вверх дном:

  1. Подоболочка создается с дескриптором файла 4 перенаправленных к stdout. Это означает, что то независимо от того, что печатается к дескриптору файла 4 в подоболочке, закончится как stdout всей конструкции.
  2. Канал создается и команды слева (#part3) и право (#part2) выполняются. exit $xs также последняя команда канала, и это означает, что строка от stdin будет статусом выхода всей конструкции.
  3. Подоболочка создается с дескриптором файла 3 перенаправленных к stdout. Это означает, что то независимо от того, что печатается к дескриптору файла 3 в этой подоболочке, закончится в #part2 и в свою очередь будет статус выхода всей конструкции.
  4. Канал создается и команды слева (#part5 и #part6) и право (filter >&4) выполняются. Вывод filter перенаправляется к дескриптору файла 4. В #part1 дескриптор файла 4 был перенаправлен к stdout. Это означает что вывод filter stdout всей конструкции.
  5. Статус выхода от #part6 печатается к дескриптору файла 3. В #part3 дескриптор файла 3 был перенаправлен к #part2. Это означает что статус выхода от #part6 будет заключительный статус выхода для всей конструкции.
  6. someprog выполняется. Статус выхода принят #part5. stdout взят каналом в #part4 и переданный filter. Вывод от filter в свою очередь достигнет stdout, как объяснено в #part4
56
27.01.2020, 19:26
  • 1
    Хороший. Для функции я мог бы просто сделать (read; exit $REPLY) –  jthill 02.03.2016, 03:03
  • 2
    (exec 3>&- 4>&-; someprog) упрощает до someprog 3>&- 4>&-. –  Roger Pate 13.04.2017, 16:33

Если Вам установили moreutils пакет, можно использовать mispipe утилиту, которая делает точно, что Вы спросили.

11
27.01.2020, 19:26

решение lesmana выше может также быть сделано без издержек запуска вложенных подпроцессов при помощи { .. } вместо этого (помнящий, что эта форма сгруппированных команд всегда должна заканчиваться с точками с запятой). Что-то вроде этого:

{ { { { someprog; echo $? >&3; } | filter >&4; } 3>&1; } | stdintoexitstatus; } 4>&1

Я проверил эту конструкцию с версией 0.5.5 тире и колочу версии 3.2.25 и 4.2.42, поэтому даже если некоторые оболочки не поддерживают { .. } группировка, это - все еще совместимый POSIX.

7
27.01.2020, 19:26
  • 1
    , Это работает действительно хорошо с большинством оболочек, я попробовал его, включая NetBSD sh, pdksh, mksh, тире, удар. Однако я не могу заставить это работать с AT&T Ksh (93 +, 93u +) или zsh (4.3.9, 5.2), даже с set -o pipefail в ksh или любом количестве опрыснутых wait команды в также. Я думаю, что это может, частично по крайней мере, быть проблема парсинга для ksh, как будто я придерживаюсь использования подоболочек затем, это хорошо работает, но даже с if чтобы выбрать вариант подоболочки для ksh, но оставить составные команды для других, это перестало работать. –  Greg A. Woods 03.08.2017, 03:42

Итак, я хотел дать ответ наподобие ответа lesmana, но я думаю, что мой, возможно, немного проще и выгоднее чисто-борновского решения:

# 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 как копию stdout внешней оболочки. Подстановка команд захватит все, что написано в стандарте с точки зрения команд внутри него, но, поскольку вывод command2 идет в файловый дескриптор 4, что касается подстановки команд, подстановка команды не захватывает это, однако, как только он выходит из подстановки команды, он по-прежнему переходит к общему файловому дескриптору сценария 1.

( exec 4> & 1 должен быть отдельной командой, потому что многие общие оболочки не Мне не нравится, когда вы пытаетесь записать в файловый дескриптор внутри подстановки команды, которая открывается во "внешней" команде, использующей подстановку. Так что это самый простой переносимый способ сделать это.)

Вы можете посмотреть на это менее технически и более игриво, как будто выходные данные команд перепрыгивают друг друга: command1 переходит к command2, тогда вывод printf перескакивает через команду 2, так что command2 не поймает это, а затем вывод команды 2 перескакивает из подстановки команды так же, как printf приземляется как раз вовремя, чтобы быть захваченным подстановкой, так что он заканчивается в переменной, а вывод command2 продолжает свой веселый путь, записанный в стандарт вывод, как в обычном пайпе.

Кроме того, насколько я понимаю, $? по-прежнему будет содержать код возврата второй команды в конвейере, потому что присвоения переменных, подстановки команд и составные команды фактически прозрачны для кода возврата. команды внутри них, поэтому статус возврата command2 должен распространяться - это, а не необходимость определять дополнительную функцию, поэтому я думаю, что это может быть несколько лучшее решение, чем то, которое предлагает lesmana.

Согласно предостережениям, которые упоминает лесмана, возможно, что command1 в какой-то момент закончится использованием файловых дескрипторов 3 или 4, поэтому для большей надежности вы должны:

exec 4>&1
exitstatus=`{ { command1 3>&-; printf $? 1>&3; } 4>&- | command2 1>&4; } 3>&1`
exec 4>&-

Обратите внимание, что в моем примере я использую составные команды, но подоболочки (использование () вместо {} также будет работать, хотя, возможно, будет менее эффективным.)

Команды наследуют файловые дескрипторы от процесса, который их запускает, поэтому все вторая строка унаследует дескриптор файла четыре, а составная команда, за которой следует 3> & 1 , унаследует дескриптор файла три.Таким образом, 4> & - гарантирует, что внутренняя составная команда не унаследует дескриптор файла четыре, а 3> & - не будет наследовать дескриптор файла три, поэтому command1 получит ' более чистая, более стандартная среда. Вы также можете переместить внутренний 4> & - рядом с 3> & - , но я думаю, почему бы просто не ограничить его область действия насколько это возможно.

Я не уверен, как часто вещи используют файловый дескриптор третий и четвертый напрямую - я думаю, что в большинстве случаев программы используют системные вызовы, которые возвращают неиспользуемые в данный момент файловые дескрипторы, но иногда код записывает в файловый дескриптор 3 Я думаю, напрямую (я мог бы представить себе программу, проверяющую дескриптор файла, чтобы увидеть, открыт ли он, и использовать его, если он открыт, или вести себя соответствующим образом по-другому, если это не так). Так что последнее, вероятно, лучше всего иметь в виду и использовать в случаях общего назначения.

12
27.01.2020, 19:26
Alternate example for @lesmana solution, possibly simplified.
Provides logging to file if desired.
=====
$ cat z.sh
TEE="cat"
#TEE="tee z.log"
#TEE="tee -a z.log"

exec 8>&- 9>&-
{
  {
    {
      { #BEGIN - add code below this line and before #END
./zz.sh
echo ${?} 1>&8  # use exactly 1x prior to #END
      #END
      } 2>&1 | ${TEE} 1>&9
    } 8>&1
  } | exit $(read; printf "${REPLY}")
} 9>&1

exit ${?}
$ cat zz.sh
echo "my script code..."
exit 42
$ ./z.sh; echo "status=${?}"
my script code...
status=42
$
1
27.01.2020, 19:26

Следующее является дополнением к ответу @Patrik на случай, если вы не можете использовать одно из распространенных решений.

Этот ответ предполагает следующее:

  • У вас есть оболочка, которая не знает ни $ PIPESTATUS , ни set -o pipefail
  • . Вы хотите использовать конвейер для параллельного выполнения, поэтому нет временных файлов.
  • Вы не хотите, чтобы вокруг был дополнительный беспорядок, если вы прервете сценарий, возможно, из-за внезапного отключения электроэнергии.
  • Это решение должно быть относительно простым в использовании и чистым для чтения.
  • Вы не хотите вводить дополнительные подоболочки.
  • Вы не можете возиться с существующими файловыми дескрипторами, поэтому stdin / out / err нельзя трогать (однако вы можете временно ввести новый).

Дополнительные предположения. Вы можете избавиться от всего, но это слишком усложняет рецепт, поэтому здесь он не рассматривается:

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

До: foo | бар | baz , однако это возвращает только код выхода последней команды ( baz )

Требуется: $? не должно быть 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

Объяснение:

  • Временный файл создается с помощью mktemp . Обычно это немедленно создает файл в / tmp
  • . Этот временный файл затем перенаправляется на FD 9 для записи и FD 8 для чтения
  • Затем временный файл немедленно удаляется. Однако он остается открытым до тех пор, пока оба FD не прекратят свое существование.
  • Теперь труба запущена. Каждый шаг добавляет к FD 9 только в случае ошибки.
  • Ожидание необходимо для ksh , потому что ksh иначе не ожидает завершения всех команд конвейера. Однако обратите внимание, что есть нежелательные побочные эффекты, если присутствуют некоторые фоновые задачи, поэтому я закомментировал это по умолчанию. Если ожидание не повредит, вы можете его прокомментировать.
  • После этого читается содержимое файла.Если он пуст (потому что все сработало) read возвращает false , поэтому true указывает на ошибку

Это может использоваться как замена плагина для одного и требует только следующего:

  • Неиспользуемые FD 9 и 8
  • Одна переменная среды для хранения имени временного файла
  • И этот рецепт может быть адаптирован практически для любой оболочки, которая позволяет перенаправление ввода-вывода
  • Кроме того, он не зависит от платформы и не требует таких вещей, как / proc / fd / N

. ОШИБКИ:

Этот сценарий содержит ошибку в случае, если в / tmp не хватает места. Если вам также нужна защита от этого искусственного случая, вы можете сделать это следующим образом, однако это имеет тот недостаток, что число 0 в 000 зависит от количества команд в канал, поэтому он немного сложнее:

TMPRESULTS="`mktemp`"
{
rm -f "$TMPRESULTS"

{ foo; printf "%1s" "$?" >&9; } |
{ bar; printf "%1s" "$?" >&9; } |
{ baz; printf "%1s" "$?" >&9; }
#wait
read TMPRESULTS <&8
[ 000 = "$TMPRESULTS" ]
} 9>>"$TMPRESULTS" 8<"$TMPRESULTS"

Примечания по переносимости:

  • 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

4
27.01.2020, 19:26

(С bash по крайней мере )в сочетании с set -eможно использовать подоболочку для явной эмуляции pipefail и выхода при ошибке канала

set -e
foo | bar
( exit ${PIPESTATUS[0]} )
rest of program

Таким образом, если fooпо какой-то причине произойдет сбой, -остальная часть программы не будет выполнена, и скрипт завершится с соответствующим кодом ошибки. (Это предполагает, что fooпечатает собственную ошибку, которой достаточно, чтобы понять причину сбоя)

0
20.08.2021, 13:35

Следующий ответ похож на некоторые другие (, особенно[0] и[1] )здесь, с разницей:

  • Он также фиксирует (в переменной )стандартный вывод окончательной программы в конвейере.

Также:

  • пытается быть совместимым с POSIX
  • получает статус выхода команд в конвейере
  • пропускает стандартную ошибку (файловый дескриптор 2 )через

Расширения и ограничения:

  • Я предполагаю, что он должен быть расширяемым, чтобы иметь более одной команды в конвейере.
  • Но может вернуть только один из статусов выхода (или комбинированный ).

Как вы увидите из моего выбора команд (для чтения двоичных данных и base64), я использовал это для чтения и сохранения двоичных данных (, включая 0x0 )в переменной оболочки (естественно только в закодированном виде ). Если это нужно будет обработать позже, его нужно будет снова декодировать (, например. printf '%s' "${stdout_command2}" | base64 -d| что _что _может _обрабатывать _двоичные _данные ).

Это конструкция:

stdout_command2="$(
                    exec 4>&1
                    exitstatus_command1="$(
                                            {
                                                { read_binary 3>&- ; printf $? >&3 ; } 4>&-   | \
                                                base64 -w 0 >&4 3>&- 4>&- ;
                                            } 3>&1
                                        )" 4>&1
                    exitstatus_command2=$?  # this must come directly after the above command substitution
                    exec 4>&-
          
                    if [ "${exitstatus_command1}" -ne 0 ]; then
                        exit 11
                    fi
                    if [ "${exitstatus_command2}" -ne 0 ]; then
                        exit 22
                    fi
                )"

overall_exitstatus=$?   # this would be e.g. `11` or `22` if either of the two commands failed

Кажется, это работает в dash, bashи busyboxв shсоответственно ash.

exec 4>&1(и последующее закрытие с помощьюexec 4>&-)необходимо, чтобы соответствовать POSIX -ly правильно:
Для простой команды без командного слова (, которого нет в строке exitstatus_command1=…, которая является просто назначением ), POSIX оставляет порядок присвоения переменных и перенаправления открытым (, см. 2.9. 1 Простые команды:«В приведенном выше списке порядок шагов 3 и 4 может быть обратным, если на шаге 2 не получается имя команды…» ).
Например, dashи busyboxshсоответственно ash, кажется, выполняют перенаправление перед назначением (, которое, таким образом, работает без exec),в то время как bashвыполняет это после (, что, таким образом, терпит неудачу безexec).

Я также закрываю файловые дескрипторы 3 и 4 там, где они не нужны:

  • 3, с 3>&-дляread_binary
  • 4, с 4>&-в целом{ read_binary 3>&- ; printf $? >&3 ; }
  • 3и 4, с 3>&- 4>&-для base64 -w 0, но только после того, как1был перенаправлен на 4с >&4(, поэтому порядок имеет значение)

Вы можете проверить, какие файловые дескрипторы видны командам, заключив их в дополнительные сценарии оболочки, подобные этому:

#!/bin/sh
find /proc/$$/fd -type l | sort -V |  sed 's/^/before /' >&2
base64 -w 0
e=$?
find /proc/$$/fd -type l | sort -V |  sed 's/^/after /' >&2
exit $e

У меня по некоторым причинам (, которые я не совсем понял )это не сработало, оборачивая их в функции оболочки, выполняемые в той же оболочке, что и конструкция выше. В этом случае я никогда не использую все дополнительные файловые дескрипторы.

И последнее, но не менее важное, т.е.read_binary(илиbase64)вполне могут быть функциями (, выполняемыми в той же оболочке ), например:

read_binary()
{
    if [ -z "${foo}" ]
        some_more_complext_read"${path}"
        if [ $? -ne 0 ]; then
            return 1
            #exit 1 # exit wouldn't work here
        fi
    else
        cat -- "${path}"
        if [ $? -ne 0 ]; then
            return 1
            #exit 2 # exit wouldn't work here
        fi
    fi
}

Но имейте в виду , что нельзя использовать exitв такой функции (с приведенной выше конструкцией ), так как это не просто оставило бы функцию, но всю подоболочку из соответствующей командной подстановки($(…)).

И exitв либо read_binary, либо base64(, если это функция ), может привести к тому, что printf $? >&3никогда не произойдет, и, таким образом, статус выхода будет потерян.

0
13.09.2021, 17:57

Теги

Похожие вопросы