Jak prawidłowo przechwytywać kod wyjścia / obsługiwać błędy podczas korzystania z zastępowania procesów?


13

Mam skrypt, który analizuje nazwy plików w tablicy przy użyciu następującej metody wziętej z pytań i odpowiedzi na temat SO :

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

Działa to świetnie i doskonale radzi sobie ze wszystkimi odmianami nazw plików. Czasami jednak przekażę nieistniejący plik do skryptu, np .:

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

W normalnych okolicznościach skrypt powinien przechwycić kod wyjścia za pomocą czegoś podobnego RET=$?i użyć go, aby zdecydować, jak kontynuować. Nie wydaje się, aby działało to z powyższym podstawieniem procesu.

Jaka jest poprawna procedura w takich przypadkach? Jak mogę przechwycić kod powrotu? Czy istnieją inne bardziej odpowiednie sposoby ustalenia, czy coś poszło nie tak w zastępowanym procesie?

Odpowiedzi:


5

Możesz dość łatwo uzyskać zwrot z dowolnego procesu z podpowłoką, powtarzając jego powrót przez standardowe wyjście. To samo dotyczy podstawienia procesu:

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

Jeśli to uruchomię, to ostatnim wierszem (lub \0ograniczoną sekcją, zależnie od przypadku) będzie findstan powrotu. readzwróci 1, gdy otrzyma EOF - więc jedynym $returnustawionym czasem jest $FILEostatni odczytany fragment informacji.

Używam, printfaby nie dodawać dodatkowej \newline - jest to ważne, ponieważ nawet readwykonywane regularnie - takie, w których nie ograniczasz \0NUL - zwróci inne niż 0 w przypadkach, gdy właśnie odczytane dane nie kończą się na \newline. Więc jeśli twój ostatni wiersz nie kończy się \newline, ostatnia wartość w zmiennej wczytywanej będzie zwrotem.

Uruchomienie polecenia powyżej, a następnie:

echo "$return"

WYNIK

0

A jeśli zmienię część zastępowania procesu ...

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

WYNIK

1

Prostsza demonstracja:

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

WYNIK

pipe

I tak naprawdę, o ile pożądany zwrot jest ostatnią rzeczą, którą piszesz na standardowe wyjście w ramach podstawienia procesu - lub dowolnego procesu z podpowłoką, z którego czytasz w ten sposób - $FILEzawsze będzie to pożądany stan zwrotu, kiedy jest przez. A zatem ta || ! return=...część nie jest absolutnie niezbędna - służy jedynie do przedstawienia koncepcji.


5

Procesy zastępowania procesów są asynchroniczne: powłoka uruchamia je, a następnie nie daje możliwości wykrycia, kiedy umrą. Więc nie będziesz w stanie uzyskać statusu wyjścia.

Możesz zapisać status wyjścia do pliku, ale ogólnie jest to nieporadne, ponieważ nie wiesz, kiedy plik jest zapisywany. Tutaj plik jest zapisywany wkrótce po zakończeniu pętli, więc rozsądnie jest na niego poczekać.

 < <(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)

Innym podejściem jest użycie nazwanego potoku i procesu w tle (dla którego można wait).

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

Jeśli żadne z tych podejść nie jest odpowiednie, myślę, że będziesz musiał wybrać bardziej wydajny język, taki jak Perl, Python lub Ruby.


Dziękuję za tę odpowiedź. Opisane przez ciebie metody działają dobrze, ale muszę przyznać, że są nieco bardziej skomplikowane, niż się spodziewałem. W moim przypadku zdecydowałem się na pętlę przed tą pokazaną w pytaniu, która iteruje wszystkie argumenty i drukuje błąd, jeśli jeden z nich nie jest plikiem lub folderem. Chociaż nie obsługuje innych rodzajów błędów, które mogą wystąpić w zastępowanym procesie, jest wystarczająco dobry dla tego konkretnego przypadku. Jeśli kiedykolwiek będę potrzebować bardziej wyrafinowanej metody obsługi błędów w takich sytuacjach, z pewnością wrócę do twojej odpowiedzi.
Glutanimate

2

Użyj koprocesu . Za pomocą coprocwbudowanego programu możesz uruchomić podproces, odczytać jego dane wyjściowe i sprawdzić status wyjścia:

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

Jeśli katalog nie istnieje, waitzakończy działanie z niezerowym kodem stanu.

Obecnie konieczne jest skopiowanie PID do innej zmiennej, ponieważ $LS_PIDzostanie ona rozbrojona przed waitwywołaniem. Zobacz Bash unsets * _PID zmienną, zanim będę mógł czekać na coproc, aby uzyskać szczegółowe informacje.


1
Jestem ciekawy, kiedy należy użyć <& "$ LS" vs read -u $ LS? - dzięki
Brian Chrisman,

1
@BrianChrisman W tym przypadku prawdopodobnie nigdy. read -upowinien równie dobrze działać. Przykład miał być ogólny i pokazywać, w jaki sposób dane wyjściowe koprocesu mogą być przesyłane do innego polecenia.
Feuermurmel,

1

Jednym z podejść jest:

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"

Chodzi o to, aby po zakończeniu polecenia powtórzyć status wyjścia wraz z losowym tokenem, a następnie użyć wyrażeń regularnych bash, aby wyszukać i wyodrębnić status wyjścia. Token służy do utworzenia unikatowego ciągu, którego należy szukać w danych wyjściowych.

Prawdopodobnie nie jest to najlepszy sposób na zrobienie tego w ogólnym znaczeniu programistycznym, ale może to być najmniej bolesny sposób radzenia sobie z nim w trybie bash.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.