Як правильно зафіксувати вихідний код / ​​обробити помилки при використанні підстановки процесу?


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

Ви можете легко отримати повернення від будь-якого підгрупового процесу, повторивши його повернення над його 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регулярне виконання - таке, коли ви не обмежуєте NULs - \0повертається за винятком 0, у випадку, коли дані, які він тільки що прочитав, не закінчуються в \newline. Отже, якщо ваш останній рядок не закінчується \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=...частина не є строго необхідною - вона використовується лише для демонстрації концепції.


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.


Дякую за цю відповідь. Описані вами методи прекрасно працюють, але мушу визнати, що вони трохи складніші, ніж я передбачав. У моєму випадку я зупинився на циклі перед тим, який показаний у запитанні, який повторюється через всі аргументи та друкує помилку, якщо один з них не є файлом чи папкою. Хоча це не обробляє інші типи помилок, які можуть виникнути в процесі заміщення, це досить добре для цього конкретного випадку. Якщо мені колись знадобиться більш складний метод поводження з помилками в таких ситуаціях, я неодмінно повернусь до вашої відповіді.
Глютаніматі

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, перш ніж я можу чекати копроку для отримання деталей


1
Мені цікаво, коли можна використовувати <& "$ LS" проти читання -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 регулярні вирази для пошуку та вилучення стану виходу. Маркер використовується для створення унікальної рядки, яку слід шукати у висновку.

Це, мабуть, не найкращий спосіб зробити це в загальному сенсі програмування, але це може бути найменш болісним способом впоратися з ним в баш.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.