Como grep saída de um programa, mas também ecoar a saída normalmente?

6

Estou trabalhando com um programa que gera mensagens de erro quando algo dá errado, mas não define seu status de saída de acordo: o status de saída é sempre 0, indicando sucesso. Eu gostaria de executar este programa a partir de um script de shell e obter um status de saída diferente de zero se ele emitir qualquer mensagem de erro, para que o script possa dizer que o programa falhou.

As mensagens de erro seguem um padrão previsível que eu posso igualar com grep e grep define seu status de saída com base em se encontrou uma correspondência, então posso enviar a saída do programa para grep e negar o resultado com ! para obter o status de saída que desejo.

O problema é que, se eu canalizar para grep , não consigo mais ver a saída do programa, porque grep consome. Eu gostaria de de alguma forma digitalizar a saída para mensagens de erro, mas também exibir a saída normalmente, uma vez que existem outras mensagens importantes além dos erros. Infelizmente, grep não tem uma opção para passar todas as suas linhas de entrada, correspondentes e não correspondentes.

Uma abordagem que eu acho que principalmente funciona é:

! my_program | tee /dev/stderr | grep -q "error message"

Isso alimenta a saída do programa em grep , mas também copia para o erro padrão, que não é redirecionado, então eu posso vê-lo. Tudo bem quando a saída padrão e o erro padrão do meu script vão para um terminal (portanto, não importa qual recebe as mensagens), mas pode causar problemas se o padrão e o erro padrão forem redirecionados para locais diferentes; as mensagens acabarão no lugar errado.

Usando bash ou shell POSIX e ferramentas GNU, existe uma maneira (concisa) de varrer os fluxos de erro padrão e / ou padrão de um programa com algo como grep e definir o status de saída de acordo, mas também deixar ambos fluxos vão para seus destinos normais?

(Observe que my_program grava suas mensagens de erro no padrão, não no erro padrão, portanto, uma solução que só pode varrer o fluxo de saída padrão é OK, embora não seja a ideal. E, caso isso faça diferença, fazendo isso no CentOS 7.)

    
por Wyzard 03.09.2016 / 01:12

4 respostas

4

...is there a (concise) way to scan a program's standard out and/or standard error streams with something like grep and set exit status accordingly, but also let both streams go to their normal destinations?

...my_program writes its error messages to standard out, not standard error, so a solution that can only scan the standard output stream is OK, though not ideal.

Minha solução responde à parte em negrito acima.

Acho que a maneira mais fácil de fazer isso é através de awk :

myprogram |
awk 'BEGIN {status = 0} /error message/ {status = 1} 1; END {exit(status)}'

O comando awk produz tudo o que ele insere exatamente como está, mas no final ele sai com um status dependente se a "mensagem de erro" fazia parte da entrada.

Versão concisa (com um nome de variável mais curto):

myprogram | awk 'BEGIN{s=0} /error message/{s=1} 1; END{exit(s)}'
    
por 03.09.2016 / 04:22
3

Aqui estão alguns liners que fazem a mensagem de erro $MSG no stdout / stderr, e não param o programa assim que a mensagem de erro aparece:

# grep on stdout, do not stop early
my_program | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}'

# grep on stdout, do stop early
my_program | awk -v s="$MSG" '$0~s{exit(1)} 1'

# grep on stderr, do not stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{r=1} 1; END{exit(r)}' >&2; } 3>&1

# grep on stderr, do stop early
{ my_program 2>&1 >&3 | awk -v s="$MSG" '$0~s{exit(1)} 1' >&2; } 3>&1

Notas:

  • Para todos: o fluxo no qual você aplica grep perderá seu potencial tty status, porque my_program o verá entrando em awk . Isso pode afetar a forma como o my_program grava nesse fluxo, por exemplo, pode não imprimir barras de progresso rotativas porque pode presumir que não pode controlar a posição do cursor.

  • Para todos: stdout e stderr não serão mesclados e podem ser redirecionados independentemente um do outro, como de costume.

  • Para todos: o código de saída original de my_program é completamente ignorado. A única outra alternativa fácil é: sair com erro se my_program sair com erro ou a mensagem de erro aparecer . Para obter esse comportamento, você precisa habilitar pipefail no shell Bash que executa o pipeline. Por exemplo:

    (set -o pipefail; my_program | awk -v s="$MSG" '$0~s{exit(1)} 1')
    
  • Para grepping em stderr: as linhas de comando simples acima assumem que o descritor de arquivo 3 não é usado. Isso é geralmente uma suposição ok para fazer. Para evitar essa suposição, você pode obter o Bash para alocar um descritor de arquivo que não pode ser usado com:

    bash -c 'exec {fd}>&1; { my_program 2>&1 >&${fd} | awk -v s="$MSG" '\''$0~s{exit(1)} 1'\'' >&2; } ${fd}>&1'
    
por 03.09.2016 / 03:34
0

Tente dois terminais lado a lado. Na primeira janela do shell:

tail -F /tmp/xyzzy

No segundo, apenas o que você estava fazendo, apenas para o arquivo tmp:

my_program | tee /tmp/xyzzy | grep -q "error message"

Inicie-os nessa ordem.

É klunky, como você vai querer limpar o arquivo temporário de vez em quando, ou escolher um novo nome, mas funciona.

Adicionado depois ... Experimente algo como:

my_program | tee /tmp/xyzzy ; grep -q "error message" /tmp/xyzzy

O status de saída da seqüência é o status de saída do grep. Que está invertido do que você quer, suspiro. Então negue isso.

my_program | tee /tmp/xyzzy ; ! grep -q "error message" /tmp/xyzzy
    
por 03.09.2016 / 02:42
0

Faça uma função de demonstração baz() que envia "foo" para stdout e "bar" para stderr :

baz() { echo foo ; echo bar >& 2 ; }

Caso simples, execute grep foo em stdout e grep bar em stderr :

{ baz 2>&1 1>&3 | grep bar 1>&2 ; } 3>&1 | grep foo

Mesma coisa, mas silenciosamente, usando tee ; a saída parece que nenhum grep s foi usado:

{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1

Agora adicione uma condicional depois de grep -q bar , que imprime "BEEP!" para stderr se bar for encontrado:

{ baz 2>&1 1>&3 | tee /dev/stderr | grep -q bar && echo "BEEP!" >&2 ; } 3>&1 | \
{ tee /dev/stderr | grep -q foo ; } 2>&1

Saída das últimas duas linhas:

foo
bar
BEEP!
    
por 03.09.2016 / 03:35