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

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

У меня есть скрипт, который анализирует имена файлов в массив, используя следующий метод, взятый изВопросы и ответы по 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=$?и использовал его для принятия решения о том, как действовать дальше. Похоже, это не работает с подстановкой процесса выше.

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

решение1

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

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строку ewline — это важно, потому что даже readрегулярно выполняемая функция — та, в которой вы не ограничиваете ее \0NUL-символами — вернет значение, отличное от 0, в случаях, когда только что прочитанные данные не заканчиваются на \nстроку ewline. Так что если ваша последняя строка не заканчивается на строку \newline, последнее значение в вашей прочитанной переменной будет вашим возвратом.

Выполняем команду выше, а затем:

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

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

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

решение3

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

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

… < <(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.

решение4

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

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.

Связанный контент