Como obter o PIPESTATUS e a saída no script bash

9

Estou tentando obter a data da última modificação de um arquivo com este comando

TM_LOCAL=`ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'`

TM_LOCAL tem um valor como "2012-05-16 23:18" após a execução desta linha

Também gostaria de verificar o PIPESTATUS para ver se houve um erro. Por exemplo, se o arquivo não existe, lsretorna 2. Mas $?tem o valor 0, pois tem o valor de retorno de awk.

Se eu executar este comando sozinho, posso verificar o valor de retorno de ls olhando para ${PIPESTATUS[0]}

ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'

Mas $PIPESTATUSnão funciona como eu esperava, se eu atribuir a saída a uma variável, como no primeiro exemplo. Nesse caso, a $PIPESTATUSmatriz possui apenas 1 elemento, o mesmo que$?

Portanto, a questão é: como posso obter os dois $PIPESTATUSe atribuir a saída a uma variável ao mesmo tempo?

Mustafa Serdar Şanlı
fonte

Respostas:

8

Você pode fazer isso:

TM_LOCAL=$(ls -l --time-style=long-iso ~/.vimrc | \
             awk '{ print $6" "$7 }' ; exit ${PIPESTATUS[0]} )

Então $?será o código de retorno de ls. Isso não funciona se você precisar do código de retorno de mais de uma das peças do tubo (mas poderá dividir o pipeline se a saída não for muito grande, como aqui).

Aqui está uma maneira bastante cara de obter a PIPESTATUSmatriz completa e a saída. Não é muito elegante, mas não encontrou mais nada:

result=$(echo -e "a\nb\nc" | \
          ( cat ; exit 1 ) | \
          ( cat ; exit 42 ) ; echo ${PIPESTATUS[@]})
output=$(head -n -1 <<< "$result")
status=($(tail -n 1 <<< "$result"))
echo "Output:"
echo "$output"
echo "Results:"
echo "${status[@]}"

Que dá:

Output:
a
b
c
Results:
0 1 42
Esteira
fonte
Este trabalho no meu caso, mas ainda estou curioso para saber se existe uma maneira de obter a matriz de pipestatus completa e a saída.
Mustafa Serdar Şanlı
3

Use set -o pipefailin bashpara obter o código de saída diferente de zero à direita em uma sequência de comandos canalizada como $?. De man bash:

Se definido, o valor de retorno de um pipeline é o valor do último comando (mais à direita) para sair com um status diferente de zero ou zero se todos os comandos no pipeline forem encerrados com êxito. Esta opção está desativada por padrão.

Então você pode simplesmente acessar $?. Use set +o pipefailpara desativar novamente.

Daniel Beck
fonte
2

Suponho que o problema aqui é que o PIPESTATUS desaparece completamente assim que você executa um comando. Você pode obter a matriz completa do PIPESTATUS na versão 2 ou superior do bash desta maneira:

declare -a status
status=(${PIPESTATUS[@]})

Em seguida, o acesso ${status[0]}, ${status[1]}etc.

eewanco
fonte
2

O principal problema com "o que você espera" é que um comando entre aspas é executado em uma subshell; $PIPESTATUSexiste lá e o status retornado segue as mesmas regras como se você executasse um único executável (ou shell script). O status do comando backquote é o awkstatus mais à direita ( ).

Para implementar o que Daniel Beck disse, defina a pipefailopção no subshell da seguinte forma:

TM_LOCAL=`set -o pipefail; ls -l --time-style=long-iso ~/.vimrc | awk '{ print $6" "$7 }'` agora o status armazenado $?posteriormente será o status de ls(se não for zero).

No entanto, acho que um if [ -f ~/.vimrc ];teste ... explícito seria mais legível.

Você não pode obter saída em uma variável e PIPESTATUSretornar sem um arquivo temporário para o primeiro ou organizar o último em uma sequência.

toddkaufmann
fonte
0

Eu queria enviar email do cron cron apenas se o status de saída não fosse zero

O truque é que, para obter o stdin para o final do pipeline, é necessário colocá-lo em um subshell - mas isso parece ocultar o valor PIPESTATUS ...

O teste cron cospe alguma saída e sai com 1 ou 0 ..

./testcron | (test ${PIPESTATUS[0]} -ne 0 || mail -s "testcron output" paul)

UPDATE: o PIPESTATUS não fica visível até que o comando pipeline seja processado

Paul Davey
fonte
0

Uma opção é verificar a existência do seu arquivo antes de obter seu horário de modificação com uma chamada para stat. Como statretorna um pouco mais do que você deseja no carimbo de data e hora, você pode apará-lo usando a expansão de parâmetros.

Com o GNU stat(por exemplo, no Linux), você pode executar:

[[ -f ~/.vimrc ]] && TM_LOCAL=$(stat -c '%y' ~/.vimrc 2>/dev/null)
TM_LOCAL=${TM_LOCAL%:*}  # Safe to do, even if stat fails

No Mac OS X e em outros sistemas BSD, a statsintaxe difere e pode especificar um formato de hora:

[[ -f ~/.vimrc ]] && TM_LOCAL=$(stat -f '%Sm' -t '%Y-%m-%d %H:%M' ~/.vimrc 2>/dev/null)
chepner
fonte
No que é agora a resposta do GNU, você diz que a alteração $TM_LOCALé segura. Só é seguro se você espera que ele não tenha um valor anterior. Digamos que o valor era anteriormente 2020-02-27 17:14e não há ~/.vimrcarquivo. Você então teria 2020-02-27 17. Portanto, eu amarraria essas duas linhas com uma adicional &&ou (de preferência, porque isso não é tão legível) usar uma ifestrofe.
Adam Katz