Как правильно перехватить код выхода / обработать ошибки при использовании подстановки процесса?


13

У меня есть скрипт, который анализирует имена файлов в массив, используя следующий метод, взятый из Q & A на SO :

unset ARGS
ARGID="1"
while IFS= read -r -d $'\0' FILE; do
    ARGS[ARGID++]="$FILE"
done < <(find "$@" -type f -name '*.txt' -print0)

Это прекрасно работает и прекрасно обрабатывает все типы вариаций имени файла. Однако иногда я передаю несуществующий файл в скрипт, например:

$ findscript.sh existingfolder nonexistingfolder
find: `nonexistingfile': No such file or directory
...

При нормальных обстоятельствах я хотел бы, чтобы скрипт захватывал код завершения с чем-то вроде RET=$?и использовал его, чтобы решить, как действовать дальше. Это, похоже, не работает с процессом замены выше.

Какова правильная процедура в подобных случаях? Как я могу получить код возврата? Есть ли другие более подходящие способы определить, что-то пошло не так в замененном процессе?

Ответы:


5

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

while IFS= read -r -d $'\0' FILE || 
    ! return=$FILE
do    ARGS[ARGID++]="$FILE"
done < <(find . -type f -print0; printf "$?")

Если я выполню это, то самая последняя строка - (или \0разделенный раздел в зависимости от обстоятельств) будет findвозвращать статус. readбудет возвращать 1, когда получит EOF - так что единственное время $returnустановлено $FILEдля самого последнего бита считанной информации.

Я использую, printfчтобы не добавлять дополнительную электронную \nстроку - это важно, потому что даже readрегулярно выполняемая - та, в которой вы не ограничиваете \0NUL - будет возвращать не 0, если данные, которые она только что прочитала, не заканчиваются на \newline. Поэтому, если ваша последняя строка не заканчивается на \newline, последним значением в вашей переменной read будет ваша возвращаемая величина.

Запустите команду выше и затем:

echo "$return"

ВЫХОД

0

И если я изменю часть процесса замены ...

...
done < <(! find . -type f -print0; printf "$?")
echo "$return"

ВЫХОД

1

Более простая демонстрация:

printf \\n%s list of lines printed to pipe |
while read v || ! echo "$v"
do :; done

ВЫХОД

pipe

И на самом деле, до тех пор, пока вы хотите, чтобы возвращаемый результат был последним, что вы пишете в стандартный вывод из подстановки процесса - или любого подпараллельного процесса, из которого вы читаете таким образом - тогда $FILEвсегда будет желаемый статус возврата, когда он через. И поэтому эта || ! return=...часть не является строго необходимой - она ​​используется только для демонстрации концепции.


5

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

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

 < <(find …; echo $? >find.status.tmp; mv find.status.tmp find.status)
while ! [ -e find.status ]; do sleep 1; done
find_status=$(cat find.status; rm find.status)

Другой подход заключается в использовании именованного канала и фонового процесса (который вы можете waitиспользовать).

mkfifo find_pipe
find  >find_pipe &
find_pid=$!
 <find_pipe
wait $find_pid
find_status=$?

Если ни один из подходов не подходит, я думаю, вам нужно выбрать более способный язык, такой как Perl, Python или Ruby.


Спасибо за этот ответ. Методы, которые вы описали, работают хорошо, но я должен признать, что они немного сложнее, чем я ожидал. В моем случае я остановился на цикле перед тем, который показан в вопросе, который перебирает все аргументы и печатает ошибку, если один из них не является файлом или папкой. Хотя это не обрабатывает другие типы ошибок, которые могут возникнуть в замещаемом процессе, этого достаточно для этого конкретного случая. Если мне когда-нибудь понадобится более сложный метод обработки ошибок в подобных ситуациях, я обязательно вернусь к вашему ответу.
Glutanimate

2

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

coproc LS { ls existingdir; }
LS_PID_=$LS_PID
while IFS= read i; do echo "$i"; done <&"$LS"
wait "$LS_PID_"; echo $?

Если каталог не существует, waitвыйдет с ненулевым кодом состояния.

В настоящее время необходимо скопировать PID в другую переменную, поскольку $LS_PIDон будет сброшен до вызова wait. Посмотрите переменную Bash unsets * _PID, прежде чем я смогу ждать на coproc для деталей.


1
Мне любопытно, когда можно использовать <& "$ LS" вместо read -u $ LS? - спасибо
Брайан Крисман

1
@BrianChrisman В этом случае, вероятно, никогда. read -uдолжно работать так же хорошо. Этот пример должен был быть универсальным и показывать, как вывод сопроцесса может быть передан в другую команду.
Feuermurmel

1

Один из подходов:

status=0
token="WzNZY3CjqF3qkasn"    # some random string
while read line; do
    if [[ "$line" =~ $token:([[:digit:]]+) ]]; then
        status="${BASH_REMATCH[1]}"
    else
        echo "$line"
    fi
done < <(command; echo "$token:$?")
echo "Return code: $status"

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.