Como capturar o código de saída/lidar com erros corretamente ao usar a substituição de processo?

Como capturar o código de saída/lidar com erros corretamente ao usar a substituição de processo?

Eu tenho um script que analisa nomes de arquivos em uma matriz usando o seguinte método retirado deperguntas e respostas sobre SO:

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

Isso funciona muito bem e lida perfeitamente com todos os tipos de variações de nomes de arquivos. Às vezes, porém, passarei um arquivo inexistente para o script, por exemplo:

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

Em circunstâncias normais, eu faria com que o script capturasse o código de saída com algo parecido RET=$?e o usasse para decidir como proceder. Isso não parece funcionar com a substituição do processo acima.

Qual é o procedimento correto em casos como este? Como posso capturar o código de retorno? Existem outras maneiras mais adequadas de determinar se algo deu errado no processo substituído?

Responder1

Você pode facilmente obter o retorno de qualquer processo subshell, repetindo seu retorno sobre seu stdout. O mesmo se aplica à substituição de processos:

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

Se eu executar isso, a última linha -(ou \0seção delimitada conforme o caso)será findo status de retorno de. readretornará 1 quando obtiver um EOF - portanto, o único horário $returndefinido $FILEé para a última informação lida.

Eu costumo printfevitar adicionar um \newline extra - isso é importante porque mesmo um readexecutado regularmente - aquele em que você não delimita em \0NULs - retornará diferente de 0 nos casos em que os dados que acabou de ler não terminam em um \newline. Portanto, se a sua última linha não terminar com um \newline, o último valor da sua variável de leitura será o seu retorno.

Executando o comando acima e depois:

echo "$return"

SAÍDA

0

E se eu alterar a parte de substituição do processo...

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

SAÍDA

1

Uma demonstração mais simples:

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

SAÍDA

pipe

E, de fato, contanto que o retorno que você deseja seja a última coisa que você escreve no stdout de dentro da substituição do processo - ou qualquer processo subshell do qual você lê dessa maneira - então $FILEsempre será o status de retorno que você deseja quando for através. E assim a || ! return=...parte não é estritamente necessária - ela é usada apenas para demonstrar o conceito.

Responder2

Use umcoprocessar. Usando o coprocbuiltin você pode iniciar um subprocesso, ler sua saída e verificar seu status de saída:

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

Se o diretório não existir, waitsairá com um código de status diferente de zero.

Atualmente é necessário copiar o PID para outra variável porque $LS_PIDele será desativado antes de waitser chamado. VerBash desativa a variável *_PID antes que eu possa esperar pelo coprocpara detalhes.

Responder3

Os processos na substituição de processos são assíncronos: o shell os inicia e não oferece nenhuma maneira de detectar quando eles morrem. Portanto, você não conseguirá obter o status de saída.

Você pode gravar o status de saída em um arquivo, mas isso geralmente é complicado porque você não pode saber quando o arquivo foi gravado. Aqui, o arquivo é gravado logo após o término do loop, então é razoável esperar por isso.

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

Outra abordagem é usar um pipe nomeado e um processo em segundo plano (que você pode waitusar).

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

Se nenhuma das abordagens for adequada, acho que você precisará optar por uma linguagem mais capaz, como Perl, Python ou Ruby.

Responder4

Uma abordagem é:

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"

A ideia é repetir o status de saída junto com o token aleatório após a conclusão do comando e, em seguida, usar expressões regulares bash para procurar e extrair o status de saída. O token é usado para criar uma string exclusiva a ser procurada na saída.

Provavelmente não é a melhor maneira de fazer isso no sentido geral de programação, mas pode ser a maneira menos dolorosa de lidar com isso no bash.

informação relacionada