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