$ awk -v f=<(cmdmayfail) -e 'BEGIN { r = getline < f ; print r }'
-bash: cmdmayfail: command not found
0
No exemplo do pipe sem nome acima, awk
não saberemos o erro do pipe sem nome.
$ awk -v f=<(cmdmayfail || echo "control sequence" ) -e 'BEGIN { r = getline < f ; print r }'
Para awk
saber desse erro, eu poderia usar o código acima enviando uma sequência de controle e algumas informações de erro.
Dado que file
já conhece muitos tipos de arquivos, existe uma sequência de controle razoável que pode ser usada para este aplicativo, de modo que awk
não trate mal a sequência de controle de erros como se fosse um arquivo legítimo? Obrigado.
Responder1
Se o seu commandmayfail
comando 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 awk
a 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 commandmayfail
for file
, como a pergunta sugere, pode ser suficiente usar uma string sem :
.
Responder2
Se a saída de cmdmayfail
for 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ê awk
deve getline<f
obter o status de saída. Consecutivo getline<f
lerá 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ãoprintf
adicionará 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 cmdmayfail
do BEGIN
bloco pode indicar que você espera cmdmayfail
encerrar mais cedo. Talvez esta solução seja suficiente.
Em geral, cmdmayfail
pode 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 cmdmayfail
com 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 awk
código deve getline<f
e verifique se é OK
. Nesse caso, a próxima linha ( getline<f
de novo) é com cmdmayfail
certeza. Loop para analisar todas as linhas até que não haja OK
quando você espera. Então é o status de saída.
Isso funcionará bem, a menos que cmdmayfail
possa 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
- ignore a linha incompleta ou
- processe-o e adicione o caractere de nova linha ausente ou
- processe-o como está.
Com efeito você vai
- perder alguma parte da saída, ou
- não sabia que a linha estava incompleta, ou
- 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 cmdmayfail
estiver completa; desta forma, seu awk
código será capaz de saber.
Considere um caso em que cmdmayfail
foi encerrado à força na linha média. Você pode não querer analisar a linha incompleta então. O problema é: para saber awk
se o formulário da linha cmdmayfail
estava completo ou não é preciso testar a próxima linha (status). Implementar uma lógica útil para isso awk
pode ser no mínimo inconveniente.
É bom detectar uma linha incompleta o mais rápido possível,read
no Bash pode fazer isso. A desvantagem é read
a 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.)
- Leia a primeira linha de um par (
getline<f
). - Armazene a primeira linha em uma variável (
first=$0
). - Leia a segunda linha de um par (
getline<f
). - Analise a primeira linha (
$first
).- Se for,
C
então o segundo (atual$0
) é uma linha completa decmdmayfail
, você pode analisá-lo. - Se for,
I
então o segundo é uma linha incompleta decmdmayfail
, você pode ou não querer analisá-lo. EspereE
no próximo par. - Se for
E
, o segundo é o status de saída decmdmayfail
. Você não deve esperar mais pares.
- Se for,
- Laço.
Nota que usei eval "$@"
dentro da função. O que você escreve depois encerr
será 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