Como posso obter o resultado de um comando e seu código de retorno ao mesmo tempo em um script Bash?

0

Eu estou jogando com scripts Bash. Gostaria de atribuir o resultado de um comando a uma variável e seu código de retorno a outro.

Ex .:

line_count=$(cat "$filename" | wc -l) #If the file does not exist, an error code is returned
cmd_return_code=$? #I want to assign the previous error code to a variable

Eu tentei usar $? como mostrado acima, mas ele captura o código de retorno da atribuição anterior, que é 0.

Como posso fazer isso?

Obrigado

    
por peflorencio 21.04.2018 / 23:08

2 respostas

3

O problema aqui é que no pipeline cat "$filename" | wc -l , quando o arquivo não existir, cat sairá com um erro, mas wc -l contará com êxito as 0 linhas de texto que recebe de cat . O status de saída do último comando no pipeline é tratado como o status final do pipeline como um todo, portanto, todo o pipeline é considerado bem-sucedido. Assim:

$ cat nosuchfile | wc -l
cat: nosuchfile: No such file or directory
       0
$ echo "$?"
0

Normalmente, no bash, você pode obter os status do comando individual em um pipeline com a matriz PIPESTATUS , assim:

$ cat nosuchfile | wc -l
cat: nosuchfile: No such file or directory
       0
$ echo "${PIPESTATUS[@]}"
1 0

... mas isso não funciona com um pipeline em uma expansão de comando como $( ) , porque esse pipeline será executado em uma subshell e PIPESTATUS não poderá se propagar a partir da subshell; somente o status final é passado de volta para o shell pai:

$ line_count=$(cat nosuchfile | wc -l)
cat: nosuchfile: No such file or directory
$ echo "${PIPESTATUS[@]}"
0

Então, o que você pode fazer sobre isso? Bem, como disse o l0b0, uma possibilidade é definir a opção pipefail . Você não precisa fazer isso para o script inteiro, você pode configurá-lo apenas para essa sub-rotina em particular, fazendo isso dentro da substituição do comando:

$ line_count=$(set -o pipefail; cat nosuchfile | wc -l)
cat: nosuchfile: No such file or directory
$ echo "$?"
1

Para esse comando específico, você também pode eliminar o pipeline (o que é chamado de Uso inútil de cat, ou UUOC ), e ter wc lido diretamente do arquivo, passando o nome do arquivo como parâmetro:

$ line_count=$(wc -l nosuchfile)
wc: nosuchfile: open: No such file or directory
$ echo $?
1

... ou usando um redirecionamento de entrada:

$ line_count=$(wc -l <nosuchfile)
-bash: nosuchfile: No such file or directory
$ echo $?
1

Existem algumas diferenças entre essas duas opções: se você passar o nome do arquivo para wc (e ele existir), ele exibirá o nome do arquivo e o número de linhas:

$ line_count=$(wc -l realfile.txt)
$ echo "$line_count"
       6 realfile.txt

... enquanto com a opção de redirecionamento não. Além disso, com a segunda opção, o shell é responsável por abrir o arquivo (e entregar o identificador de arquivo aberto ao comando wc ), portanto, se ele falhar, é o shell que fornece um erro (observe que a mensagem de erro é "- bash ", não wc ) e wc nunca são executados.

    
por 22.04.2018 / 00:19
2

O código de saída de uma atribuição de um substituição de comando é o código de saída da substituição de comando. Veja, por exemplo, o código de saída de foo="$(false)" .

Para tornar o código de saída de um pipeline, o código de saída do primeiro comando com falha no pipeline:

set -o pipefail
    
por 21.04.2018 / 23:15