Come acquisire il codice di uscita / gestire correttamente gli errori quando si utilizza la sostituzione del processo?


13

Ho uno script che analizza i nomi dei file in un array usando il seguente metodo tratto da una domanda e risposta su SO :

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

Funziona alla grande e gestisce perfettamente tutti i tipi di varianti di nome file. A volte, tuttavia, passerò un file inesistente allo script, ad esempio:

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

In circostanze normali vorrei che lo script acquisisse il codice di uscita con qualcosa di simile RET=$?e lo usassi per decidere come procedere. Questo non sembra funzionare con la sostituzione del processo sopra.

Qual è la procedura corretta in casi come questo? Come posso acquisire il codice di ritorno? Ci sono altri modi più adatti per determinare se qualcosa è andato storto nel processo sostituito?

Risposte:


5

Puoi facilmente ottenere il ritorno da qualsiasi processo sottoscritto facendo eco al suo ritorno sul suo stdout. Lo stesso vale per la sostituzione del processo:

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

Se lo eseguo, l'ultima riga (o la \0sezione delimitata, a seconda del caso) sarà lo stato finddi restituzione. readrestituirà 1 quando ottiene un EOF, quindi l'unica volta $returnimpostata $FILEè per l'ultimo bit di informazione letto.

Uso printfper evitare di aggiungere una \newline aggiuntiva - questo è importante perché anche una readeseguita regolarmente - una in cui non si delimita su \0NUL - restituirà un valore diverso da 0 nei casi in cui i dati che ha appena letto non finiscono in una \newline. Quindi se l'ultima riga non termina con una \newline, l'ultimo valore nella variabile read in sarà il tuo ritorno.

Eseguendo il comando sopra e quindi:

echo "$return"

PRODUZIONE

0

E se modifico la parte di sostituzione del processo ...

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

PRODUZIONE

1

Una dimostrazione più semplice:

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

PRODUZIONE

pipe

E infatti, fintanto che il ritorno che desideri è l'ultima cosa che scrivi su stdout all'interno della sostituzione del processo - o qualsiasi processo sottoscritto da cui leggi in questo modo - allora $FILEsarà sempre lo stato di ritorno che desideri quando è finito. E quindi la || ! return=...parte non è strettamente necessaria: viene utilizzata solo per dimostrare il concetto.


5

I processi di sostituzione dei processi sono asincroni: la shell li avvia e quindi non dà modo di rilevare quando muoiono. Quindi non sarai in grado di ottenere lo stato di uscita.

È possibile scrivere lo stato di uscita in un file, ma in genere è maldestro perché non si può sapere quando il file è stato scritto. Qui, il file viene scritto subito dopo la fine del ciclo, quindi è ragionevole aspettarlo.

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

Un altro approccio consiste nell'utilizzare una pipe denominata e un processo in background (che è possibile waitper).

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

Se nessuno dei due approcci è adatto, penso che dovrai scegliere un linguaggio più capace, come Perl, Python o Ruby.


Grazie per questa risposta I metodi che hai descritto funzionano bene, ma devo ammettere che sono leggermente più complicati di quanto mi aspettassi. Nel mio caso ho optato per un ciclo prima di quello mostrato nella domanda che scorre attraverso tutti gli argomenti e stampa un errore se uno di loro non è un file o una cartella. Sebbene questo non gestisca altri tipi di errori che potrebbero verificarsi nel processo sostituito, è abbastanza buono per questo caso specifico. Se mai avessi bisogno di un metodo di gestione degli errori più sofisticato in situazioni come questa, tornerò sicuramente alla tua risposta.
Glutanimate,

2

Usa un coprocesso . Utilizzando il coprocbuilt-in è possibile avviare un sottoprocesso, leggere il suo output e verificarne lo stato di uscita:

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

Se la directory non esiste, wait verrà chiusa con un codice di stato diverso da zero.

Al momento è necessario copiare il PID in un'altra variabile perché $LS_PIDnon sarà impostato prima che waitvenga chiamato. Vedi Bash unsets * _PID variabile prima di poter attendere coproc per i dettagli.


1
Sono curioso di sapere quando si userebbero <& "$ LS" vs read -u $ LS? - grazie
Brian Chrisman,

1
@BrianChrisman In questo caso, probabilmente mai. read -udovrebbe funzionare altrettanto bene. L'esempio doveva essere generico e mostrare come l'output del coprocesso potesse essere convogliato in un altro comando.
Feuermurmel,

1

Un approccio è:

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"

L'idea è di ripetere lo stato di uscita insieme al token casuale dopo il completamento del comando, quindi utilizzare espressioni regolari bash per cercare ed estrarre lo stato di uscita. Il token viene utilizzato per creare una stringa univoca da cercare nell'output.

Probabilmente non è il modo migliore per farlo in senso generale di programmazione, ma potrebbe essere il modo meno doloroso per gestirlo in bash.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.