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 \0
seção delimitada conforme o caso)será find
o status de retorno de. read
retornará 1 quando obtiver um EOF - portanto, o único horário $return
definido $FILE
é para a última informação lida.
Eu costumo printf
evitar adicionar um \n
ewline extra - isso é importante porque mesmo um read
executado regularmente - aquele em que você não delimita em \0
NULs - retornará diferente de 0 nos casos em que os dados que acabou de ler não terminam em um \n
ewline. Portanto, se a sua última linha não terminar com um \n
ewline, 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 $FILE
sempre 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 coproc
builtin 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, wait
sairá com um código de status diferente de zero.
Atualmente é necessário copiar o PID para outra variável porque $LS_PID
ele será desativado antes de wait
ser 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 wait
usar).
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.