Bash: как распространять ошибки при замене процесса?

19

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

Обычно я делаю это с:

set -e
set -o pipefail

(как правило, я добавляю set -uтакже)

Дело в том, что ничего из вышеперечисленного не работает с заменой процесса. Этот код выводит «ok» и завершается с кодом возврата = 0, в то время как я хотел бы, чтобы он потерпел неудачу:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Есть ли что-то эквивалентное "pipefail", кроме как для замены процесса? Любой другой способ передать команде вывод команд, как будто они были файлами, но выдавать ошибку всякий раз, когда любая из этих программ дает сбой?

Решение плохого человека было бы определить, пишут ли эти команды в stderr (но некоторые команды пишут в stderr в успешных сценариях).

Другим решением, более совместимым с posix, было бы использование именованных каналов, но мне нужно использовать эти команды, которые используют подстановку процессов, так как oneliners создаются на лету из скомпилированного кода, а создание именованных каналов усложнит ситуацию (дополнительные команды, ошибка перехвата для удаляя их и т. д.)

juanleon
источник
Вам не нужно перехватывать ошибки, чтобы удалить именованные каналы. На самом деле, это не совсем хороший способ. Посмотрим mkfifo pipe; { rm pipe; cat <file; } >pipe. Эта команда будет зависать до тех пор, пока не откроется читатель, pipeпотому что это оболочка, которая выполняет операцию, open()и так, как только в pipeссылке fs есть читатель для pipeis rm'd, а затем catкопирует infile в дескриптор оболочки для этого канала. И вообще, если вы хотите распространить ошибку из подпрограммы процесса, то сделайте: <( ! : || kill -2 "$$")
mikeserv
Спасибо за подсказку по удалению именованных каналов. К сожалению, $$замена не работает для меня, так как эта подстановка команд не выполняется, так как команда, использующая подстановку процессов, выполняется внутри конвейера команд, порожденного из кода «не оболочки» (python). Вероятно, я должен создать подпроцесс в Python и программно передать их.
Джуанлеон
Так что пользуйтесь kill -2 0.
mikeserv
К сожалению "kill -2 0" убьет (сигнализирует) питона. Написание обработчиков сигналов для выполнения бизнес-логики в многопоточном приложении - это не то, чего я жду с нетерпением :-)
juanleon
Если вы не хотите обрабатывать сигналы, то почему вы пытаетесь их получить? В любом случае, я ожидаю, что именованные каналы, в конце концов, будут самым простым решением, хотя бы потому, что для правильной работы требуется создание собственной структуры и настройка собственного специализированного диспетчера. Преодолеть это препятствие, и все это вместе.
mikeserv

Ответы:

6

Вы можете обойти эту проблему, например:

cat <(false || kill $$) <(echo ok)
other_command

Подоболочка сценария SIGTERMd перед выполнением второй команды ( other_command). Команда echo okвыполняется «иногда»: проблема в том, что подстановки процессов асинхронны. Там нет никакой гарантии , что kill $$команда выполняется до того или после того, как в echo okкоманде. Это вопрос планирования операционных систем.

Рассмотрим скрипт bash, подобный этому:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

Вывод этого скрипта может быть:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Или:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Вы можете попробовать это, и после нескольких попыток вы увидите два разных порядка в выводе. В первом сценарий был прерван до того, как две другие echoкоманды смогли записать в дескриптор файла. Во втором команда falseили killкоманда, вероятно, были запланированы после echoкоманд.

Или более точно: Системный вызов signal()от Предприятия обслуживания killнаселения , который посылает на SIGTERMсигнал процесс раковин был запланирован (или был доставлен) позже или раньше , чем эхо write()системных вызовов.

Но, тем не менее, скрипт останавливается и код выхода не равен 0. Поэтому он должен решить вашу проблему.

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

Ссылки:

хаос
источник
2

Для справки, и даже если ответы и комментарии были хорошими и полезными, я прекратил реализовывать что-то немного другое (у меня были некоторые ограничения на получение сигналов в родительском процессе, которые я не упомянул в вопросе)

По сути, я закончил делать что-то вроде этого:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Затем я проверяю файл ошибок. Если он существует, я знаю, какая подкоманда завершилась неудачно (и содержимое файла error_file может быть полезным). Более многословный и хакерский, который я изначально хотел, но менее громоздкий, чем создание именованных каналов в однострочном комманде bash.

juanleon
источник
Все, что работает для вас, обычно является лучшим способом. Спасибо, что вернулись и ответили - Селфи мои любимые.
mikeserv
2

Этот пример показывает, как использовать killвместе с trap.

#! /bin/bash
failure ()
{
  echo 'sub process failed' >&2
  exit 1
}
trap failure SIGUSR1
cat < <( false || kill -SIGUSR1 $$ )

Но killнельзя передать код возврата из вашего подпроцесса в родительский процесс.

ceving
источник
Мне нравится это изменение на общепринятом ответ
sehe
1

Аналогично тому, как вы реализуете $PIPESTATUS/ $pipestatusс помощью оболочки POSIX, которая ее не поддерживает , вы можете получить статус завершения команд, передав их через канал:

unset -v false_status echo_status
{ code=$(
    exec 3>&1 >&4 4>&-
    cat 3>&- <(false 3>&-; echo >&3 "false_status=$?") \
             <(echo ok 3>&-; echo >&3 "echo_status=$?")
);} 4>&1
cat_status=$?
eval "$code"
printf '%s_code=%d\n' cat   "$cat_status" \
                      false "$false_status" \
                      echo  "$echo_status"

Который дает:

ok
cat_code=0
false_code=1
echo_code=0

Или вы можете использовать pipefailи реализовывать процесс подстановки вручную, как если бы вы использовали оболочки, которые его не поддерживают:

set -o pipefail
{
  false <&5 5<&- | {
    echo OK <&5 5<&- | {
      cat /dev/fd/3 /dev/fd/4
    } 4<&0 <&5 5<&-
  } 3<&0
} 5<&0
Стефан Шазелас
источник