Como armazenar um erro padrão em uma variável em um script Bash

Digamos que eu tenha um script como o seguinte:

useless.sh

echo "This Is Error" 1>&2 echo "This Is Output" 

E eu tenho outro script de shell:

tambémUseless.sh

 ./useless.sh | sed 's/Output/Useless/' 

Eu quero capturar “This Is Error”, ou qualquer outro stderr de useless.sh, em uma variável. Vamos chamá-lo de ERRO.

Observe que estou usando stdout para alguma coisa. Eu quero continuar usando stdout, então redirect stderr para stdout não é útil, neste caso.

Então, basicamente, eu quero fazer

 ./useless.sh 2> $ERROR | ... 

mas isso obviamente não funciona.

Eu também sei que eu poderia fazer

 ./useless.sh 2> /tmp/Error ERROR=`cat /tmp/Error` 

mas isso é feio e desnecessário.

Infelizmente, se não aparecerem respostas aqui, é o que terei que fazer.

Eu espero que haja outro jeito.

Alguém tem alguma ideia melhor?

    Seria melhor capturar o arquivo de erro assim:

     ERROR=$( 

    O shell reconhece isso e não precisa executar ' cat ' para obter os dados.

    A questão maior é difícil. Eu não acho que haja uma maneira fácil de fazer isso. Você teria que construir todo o pipeline no sub-shell, eventualmente enviando sua saída padrão final para um arquivo, para que você possa redirect os erros para a saída padrão.

     ERROR=$( { ./useless.sh | sed s/Output/Useless/ > outfile; } 2>&1 ) 

    Note que o ponto-e-vírgula é necessário (em conchas clássicas - Bourne, Korn - com certeza; provavelmente também em Bash). O ' {} ' faz o redirecionamento de E / S sobre os comandos incluídos. Conforme escrito, ele também captura erros do sed .

    (Código formalmente não testado - use a risco próprio.)

    tambémUseless.sh

    Isso permitirá que você canalize a saída do seu script useless.sh por meio de um comando como sed e salve o stderr em uma variável chamada error . O resultado do pipe é enviado para stdout para exibição ou para ser canalizado para outro comando.

    Ele configura um par de descritores de arquivos extras para gerenciar os redirecionamentos necessários para fazer isso.

     #!/bin/bash exec 3>&1 4>&2 #set up extra file descriptors error=$( { ./useless.sh | sed 's/Output/Useless/' 2>&4 1>&3; } 2>&1 ) echo "The message is \"${error}.\"" exec 3>&- 4>&- # release the extra file descriptors 

    Redirecionado stderr para stdout, stdout para / dev / null e, em seguida, use os backticks ou $() para capturar o stderr redirecionado:

     ERROR=$(./useless.sh 2>&1 >/dev/null) 
     # command receives its input from stdin. # command sends its output to stdout. exec 3>&1 stderr="$(command &1 1>&3)" exitcode="${?}" echo "STDERR: $stderr" exit ${exitcode} 

    Existem muitas duplicatas para essa questão, muitas das quais têm um cenário de uso um pouco mais simples, no qual você não deseja capturar stderr e stdout e o código de saída todos ao mesmo tempo.

     if result=$(useless.sh 2>&1); then stdout=$result else rc=$? stderr=$result fi 

    funciona para o cenário comum em que você espera a saída adequada no caso de sucesso ou uma mensagem de diagnóstico no stderr no caso de falha.

    Observe que as instruções de controle do shell já examinam $? sob o capô; então tudo o que parece

     cmd if [ $? -eq 0 ], then ... 

    é apenas uma maneira desajeitada e unidiomática de dizer

     if cmd; then ... 

    Veja como eu fiz:

     # # $1 - name of the (global) variable where the contents of stderr will be stored # $2 - command to be executed # captureStderr() { local tmpFile=$(mktemp) $2 2> $tmpFile eval "$1=$(< $tmpFile)" rm $tmpFile } 

    Exemplo de uso:

     captureStderr err "./useless.sh" echo -$err- 

    Ele usa um arquivo temporário. Mas pelo menos o material feio é envolvido em uma function.

    Este é um problema interessante para o qual eu esperava que houvesse uma solução elegante. Infelizmente, acabo com uma solução semelhante à do Sr. Leffler, mas acrescentarei que você pode chamar de inútil dentro de uma function Bash para melhorar a legibilidade:

     #! / bin / bash
    
     function inútil {
         /tmp/useless.sh |  sed / Output / Useless / '
     }
    
     ERRO = $ (inútil)
     echo $ ERROR
    

    Todos os outros tipos de redirecionamento de saída devem ser suportados por um arquivo temporário.

     $ b=$( ( a=$( (echo stdout;echo stderr >&2) ) ) 2>&1 ) $ echo "a=>$ab=>$b" a=>stdout b=>stderr 

    Este post me ajudou a criar uma solução semelhante para meus próprios propósitos:

     MESSAGE=`{ echo $ERROR_MESSAGE | format_logs.py --level=ERROR; } 2>&1` 

    Então, contanto que nossa MENSAGEM não seja uma string vazia, nós a passamos para outras coisas. Isso nos informará se o arquivo format_logs.py falhou com algum tipo de exceção do Python.

    Capturar e Imprimir stderr

     ERROR=$( ./useless.sh 3>&1 1>&2 2>&3 | tee /dev/fd/2 ) 

    Demolir

    Você pode usar $() para capturar stdout, mas deseja capturar stderr. Então você troca stdout e stderr. Usando o fd 3 como armazenamento temporário no algoritmo de troca padrão.

    Se você quiser capturar e imprimir use tee para fazer uma duplicata. Neste caso, a saída do tee será capturada pelo $() ao invés de ir para o console, mas stderr (do tee ) ainda irá para o console, então nós usamos isso como a segunda saída para tee via o arquivo especial /dev/fd/2 desde que tee espera um caminho de arquivo em vez de um número fd.

    Observação: isso é uma quantidade enorme de redirecionamentos em uma única linha e a ordem é importante. $() está pegando o stdout de tee no final do pipeline eo próprio pipeline roteia stdout de ./useless.sh para o stdin de tee DEPOIS que nós ./useless.sh stdin e stdout por ./useless.sh .

    Usando stdout de ./useless.sh

    O OP disse que ainda queria usar (não apenas imprimir) stdout, como ./useless.sh | sed 's/Output/Useless/' ./useless.sh | sed 's/Output/Useless/' .

    Não há problema, apenas faça isso ANTES de trocar stdout e stderr. Eu recomendo movê-lo para uma function ou arquivo (também-useless.sh) e chamar isso no lugar de ./useless.sh na linha acima.

    No entanto, se você quiser CAPTURE stdout e stderr, então eu acho que você tem que recorrer a arquivos temporários porque $() fará apenas um de cada vez e faz um subshell a partir do qual você não pode retornar variables.

    Se você quiser ignorar o uso de um arquivo temporário, poderá usar a substituição de processo. Eu ainda não consegui fazer isso funcionar. Esta foi minha primeira tentativa:

     $ .useless.sh 2> >( ERROR=$(< ) ) -bash: command substitution: line 42: syntax error near unexpected token `)' -bash: command substitution: line 42: `<)' 

    Então eu tentei

     $ ./useless.sh 2> >( ERROR=$( cat < () ) ) This Is Output $ echo $ERROR # $ERROR is empty 

    Contudo

     $ ./useless.sh 2> >( cat < () > asdf.txt ) This Is Output $ cat asdf.txt This Is Error 

    Portanto, a substituição do processo geralmente faz a coisa certa ... infelizmente, sempre que eu envolvo STDIN dentro de >( ) com algo em $() na tentativa de capturar isso para uma variável, eu perco o conteúdo de $() . Eu acho que isso é porque $() lança um subprocess que não tem mais access ao descritor de arquivo em / dev / fd que é de propriedade do processo pai.

    A substituição de processos me deu a capacidade de trabalhar com um stream de dados que não está mais no STDERR, infelizmente não pareço ser capaz de manipulá-lo da maneira que desejo.

    Em zsh:

     { . ./useless.sh > /dev/tty } 2>&1 | read ERROR $ echo $ERROR ( your message ) 

    Para corrigir seus comandos com erros :

     execute [INVOKING-FUNCTION] [COMMAND] 

     execute () { error=$($2 2>&1 >/dev/null) if [ $? -ne 0 ]; then echo "$1: $error" exit 1 fi } 

    Inspirado na fabricação enxuta:

    • Tornar os erros impossíveis por design
    • Faça os passos mais pequenos
    • Terminar itens um por um
    • Torne óbvio para qualquer um