bash: como propagar erros na substituição de processos?

14

Eu quero que meus scripts de shell falhem sempre que um comando executado com eles falhar.

Geralmente faço isso com:

set -e
set -o pipefail

(normalmente adiciono set -u também)

O problema é que nenhum dos itens acima funciona com a substituição do processo. Este código imprime "ok" e sai com o código de retorno = 0, enquanto eu gostaria que ele falhasse:

#!/bin/bash -e
set -o pipefail
cat <(false) <(echo ok)

Existe algo equivalente a "pipefail" mas para substituição de processo? Qualquer outra maneira de passar para um comando a saída de comandos como eram arquivos, mas gerando um erro sempre que algum desses programas falhar?

Uma solução do homem pobre seria detectar se esses comandos gravam no stderr (mas alguns comandos gravam no stderr em cenários bem-sucedidos).

Outra solução compatível com posix seria usar pipes nomeados, mas eu preciso lançar esses comandos-que-usar-processo-substituição como oneliners construídos rapidamente de código compilado, e criar pipes nomeados complicaria as coisas (comandos extras, erro de trapping para excluí-los, etc.)

    
por juanleon 22.07.2015 / 14:30

2 respostas

4

Você só poderia contornar esse problema com isso, por exemplo:

cat <(false || kill $$) <(echo ok)
other_command

A subshell do script é SIGTERM d antes que o segundo comando possa ser executado ( other_command ). O comando echo ok é executado "às vezes": O problema é que as substituições de processo são assíncronas. Não há garantia de que o comando kill $$ seja executado antes ou após o comando echo ok . É uma questão de agendamento de sistemas operacionais.

Considere um script bash como este:

#!/bin/bash
set -e
set -o pipefail
cat <(echo pre) <(false || kill $$) <(echo post)
echo "you will never see this"

A saída desse script pode ser:

$ ./script
Terminated
$ echo $?
143           # it's 128 + 15 (signal number of SIGTERM)

Ou:

$ ./script
Terminated
$ pre
post

$ echo $?
143

Você pode tentar e depois de algumas tentativas, você verá as duas ordens diferentes na saída. No primeiro, o script foi encerrado antes que os outros dois comandos echo pudessem gravar no descritor de arquivo. No segundo, o comando false ou kill provavelmente foi agendado após os comandos echo .

Ou, para ser mais preciso: a chamada do sistema signal() da kill utillity que envia o sinal SIGTERM para o processo de shells foi agendada (ou entregue) mais cedo ou mais cedo do que o eco write() syscalls .

No entanto, o script é interrompido e o código de saída não é 0. Portanto, ele deve resolver o problema.

Outra solução é, claro, usar pipes nomeados para isso. Mas, depende do seu script como seria complexo implementar pipes nomeados ou a solução alternativa acima.

Referências:

por 22.07.2015 / 16:27
2

Para o registro, e mesmo se as respostas e comentários foram bons e úteis, acabei implementando algo um pouco diferente (eu tinha algumas restrições sobre receber sinais no processo pai que eu não mencionei na pergunta)

Basicamente, acabei fazendo algo assim:

command <(subcomand 2>error_file && rm error_file) <(....) ...

Então eu verifico o arquivo de erro. Se existir, sei qual subcommand falhou (e o conteúdo do error_file pode ser útil). Mais verboso e hackish que eu queria originalmente, mas menos complicado do que criar pipes nomeados em um comando bash de uma linha.

    
por 23.07.2015 / 12:29