controle seq para erros em pipe sem nome

controle seq para erros em pipe sem nome
$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0

No exemplo do pipe sem nome acima, awknão saberemos o erro do pipe sem nome.

$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'

Para awksaber desse erro, eu poderia usar o código acima enviando uma sequência de controle e algumas informações de erro.

Dado que filejá conhece muitos tipos de arquivos, existe uma sequência de controle razoável que pode ser usada para este aplicativo, de modo que awknão trate mal a sequência de controle de erros como se fosse um arquivo legítimo? Obrigado.

Responder1

Se o seu commandmayfailcomando for padrão do Unix, preceder a sequência de controle com seu próprio texto complexo (por exemplo __ERROR__CMDFAIL__:) deve ser suficiente para permitir awka compreensão da diferença.

No entanto, se você também incluir seu próprio software e/ou software proprietário, será difícil fornecer uma sequência geral. É possível (embora improvável) que um dos seus comandos de propriedade use tal string. Você deve observar a configuração geral das mensagens de erro e criar uma string que provavelmente não será usada.

Se commandmayfailfor file, como a pergunta sugere, pode ser suficiente usar uma string sem :.

Responder2

Se a saída de cmdmayfailfor relativamente pequena e o próprio comando terminar independentemente de outras partes do seu código, você poderá armazenar sua saída em uma variável e passar o status de saída como a primeira linha. O código <()seria como:

out="$(cmdmayfail)"; printf '%s\n' "$?" "$out"

Você awkdeve getline<fobter o status de saída. Consecutivo getline<flerá a saída real de cmdmayfail.

Limitações:

  • Variáveis ​​​​no Bash não podem armazenar caracteres nulos.
  • $()removerá todos os caracteres de nova linha finais; então printfadicionará exatamente um. Um truque complicado para evitar isso:

    out="$(cmdmayfail; s="$?"; printf X; exit "$s")"; printf '%s\n%s' "$?" "${out%X}"
    

O fato de você manipular a saída cmdmayfaildo BEGINbloco pode indicar que você espera cmdmayfailencerrar mais cedo. Talvez esta solução seja suficiente.


Em geral, cmdmayfailpode até ser executado "infinitamente" (ou seja, até você terminá-lo) e você pode querer ler sua saída enquanto processa o stdin ("infinito") de awk. Nesse caso, a solução acima não funcionará.

Você pode preceder cada linha da saída cmdmayfailcom alguma linha de status fixa (por exemplo OK) e, finalmente, adicionar uma linha com o status de saída de cmdmayfail. O código <()seria como:

 cmdmayfail | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Exemplo:

$ (printf '%s\n' foo "bar baz"; exit 7) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"
OK
foo
OK
bar baz
7

Então seu awkcódigo deve getline<fe verifique se é OK. Nesse caso, a próxima linha ( getline<fde novo) é com cmdmayfailcerteza. Loop para analisar todas as linhas até que não haja OKquando você espera. Então é o status de saída.

Isso funcionará bem, a menos que cmdmayfailpossa gerar umlinha incompleta. Exemplo:

$ (printf 'foo\nincomplete line'; exit 22) | sed 's/^/OK\n/'; printf '%s\n' "${PIPESTATUS[0]}"

Dependendo da implementação do sed, a ferramenta pode

  1. ignore a linha incompleta ou
  2. processe-o e adicione o caractere de nova linha ausente ou
  3. processe-o como está.

Com efeito você vai

  1. perder alguma parte da saída, ou
  2. não sabia que a linha estava incompleta, ou
  3. obtenha a linha com o status de saída anexado a ela.

No caso de (3) printf '\n%s\n' "${PIPESTATUS[0]}"pode ajudar. Irá gerar uma linha vazia extra se a última linha cmdmayfailestiver completa; desta forma, seu awkcódigo será capaz de saber.

Considere um caso em que cmdmayfailfoi encerrado à força na linha média. Você pode não querer analisar a linha incompleta então. O problema é: para saber awkse o formulário da linha cmdmayfailestava completo ou não é preciso testar a próxima linha (status). Implementar uma lógica útil para isso awkpode ser no mínimo inconveniente.


É bom detectar uma linha incompleta o mais rápido possível,readno Bash pode fazer isso. A desvantagem é reada lentidão (e lembre-se que as variáveis ​​Bash não podem armazenar caracteres nulos). Solução de exemplo:

# define this helper function in the main shell
encerr () { ( eval "$@" ) | (while IFS= read -r line; do printf 'C\n%s\n' "$line"; done; [ -n "$line" ] && printf 'I\n%s\n' "$line") ; printf 'E\n%s\n' "${PIPESTATUS[0]}"; }
# this part you want to put in <()
encerr cmdmayfail

Então você precisa decodificar o protocolo personalizado dentro do arquivo awk. As linhas vão em pares. (Veja os exemplos abaixo para entender o protocolo de forma mais intuitiva.)

  1. Leia a primeira linha de um par ( getline<f).
  2. Armazene a primeira linha em uma variável ( first=$0).
  3. Leia a segunda linha de um par ( getline<f).
  4. Analise a primeira linha ( $first).
    • Se for, Centão o segundo (atual $0) é uma linha completa de cmdmayfail, você pode analisá-lo.
    • Se for, Ientão o segundo é uma linha incompleta de cmdmayfail, você pode ou não querer analisá-lo. Espere Eno próximo par.
    • Se for E, o segundo é o status de saída de cmdmayfail. Você não deve esperar mais pares.
  5. Laço.

Nota que usei eval "$@"dentro da função. O que você escreve depois encerrserá avaliado pela segunda vez, então normalmente você gostaria de executar algo como

encerr 'cmd1 -opt foo'

ou

encerr "cmd1 -opt foo"

ou mesmo

encerr 'cmd1 -opt foo | cmd2'

Basicamente, este é o formulário que você usa para executar comandos remotos com o ssh. Comparar:

ssh a@b 'cmd1 -opt foo | cmd2'

Ou você pode construir a função assim:

encerr () { "$@" | …

e chame assim:

encerr cmd1 -opt foo

Comparado a sudo:

sudo cmd1 -opt foo

Exemplos (usando a função original com eval):

  • sucesso com saída vazia

    $ encerr true
    E
    0
    
  • falha com saída vazia

    $ # I'm redirecting stderr so "command not found" doesn't obfuscate the example
    $ encerr nonexisting-command-foo1231234 2>/dev/null
    E
    127
    
  • sucesso após linhas completas

    $ encerr 'date; sleep 1; date'
    C
    Mon Sep 30 09:07:40 CEST 2019
    C
    Mon Sep 30 09:07:41 CEST 2019
    E
    0
    
  • falha após linhas completas

    $ encerr 'printf "foo\nbar\n"; false'
    C
    foo
    C
    bar
    E
    1
    
  • sucesso após uma linha incompleta

    $ encerr 'printf "foo bar\n89 baz"'
    C
    foo bar
    I
    89 baz
    E
    0
    
  • falha após uma linha incompleta

    $ encerr 'printf "\nThe first line was empty and this one was interru"; exit 33'
    C
    
    I
    The first line was empty and this one was interru
    E
    33
    

informação relacionada