$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
é fortune
programa condicional sem exit(rand())
.
Você pode explicar o que há de errado aqui?
Digressão lírico-histórica
Conheci esse Heisenbug pela primeira vez há um quarto de século. Então para o gateway no FaxNET foi necessário criar vários utilitários via
Minha experiência anterior lidando com bugs no sendmail e uucp/uupc aumentou minha diligência no “tratamento completo de erros”. Não faz sentido mergulhar nos detalhes dessa história, mas lutei com esse Heisenbug por duas semanas, durante 10 a 14 horas. Por isso, foi lembrado, e ontem esse velho conhecido passou por aqui novamente para nos visitar.
Resposta DR
Utilitário head
lata feche o canal de fortune
imediatamente assim que ele lê a primeira linha. Se fortune
gera mais de uma linha, então a chamada correspondente write()
retornará um erro ou informará que menos bytes são gerados do que o solicitado. Por sua vez, escrito com tratamento cuidadoso de erros fortune
tem o direito de reflectir esta situação no seu estatuto de saída. Então devido à instalação set -o pipefail
vai funcionar || echo "Вы проиграли"
.
No entanto, head
pode não chegar a tempo fechar antes fortune
terminará a saída de dados. Então vai funcionar && echo "Повезло!"
.
Em um dos meus hoje GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Traduzido em humano
É comum aqui --version
pergunta quem ele é e, se a opção não for suportada, um esboço é inserido "Por favor, use um compilador compatível com GCC ou CLANG".
como
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Francamente, não reconheci imediatamente meu antigo “conhecido”. Além disso, o projeto já foi testado diversas vezes no Elbrus e em diversas distribuições diferentes, incluindo Alt. Com vários compiladores, versões do GNU Make e bash. Portanto, eu não queria ver meu erro aqui.
Ao tentar reproduzir o problema e/ou entender o que estava acontecendo, mais coisas estranhas começaram a acontecer.
Feitiço de linha de comando:
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
De vez em quando produzia texto extra, então não... Muitas vezes uma das opções durava bastante tempo, mas se você cutucasse por mais tempo, sempre teria as duas!
Naturalmente, strace
A propósito, como qualquer pessoa que se preze strace
prefere não reproduzir.
Então o que está acontecendo?
- Utilitário
head
tem o direito (ou melhor, é forçado) de fechar o canal que está sendo lido assim que ler o número de linhas solicitado. - O escritor do programa gerador de dados (neste caso
cc
) lata imprimir várias linhas e livre faça isso por meio de várias chamadaswrite()
. - Se o leitor terá tempo de fechar o canal do seu lado antes do final da gravação do lado do escritor, então o escritor receberá um erro.
- Programa escritor tem o direito ambos ignoram o erro de gravação do canal e o refletem em seu código de conclusão.
- Devido à instalação
set -o pipefail
o código de conclusão do pipeline será diferente de zero (errôneo) se o resultado for diferente de zero de pelo menos um elemento, e então funcionará|| echo "Please use GCC or CLANG compatible compiler"
.
Pode haver variações dependendo de como o programa gravador trabalha com os sinais. Por exemplo, o programa pode terminar de forma anormal (com geração automática de um status de encerramento diferente de zero/erro), ou write()
retornará o resultado da gravação de menos bytes do que o solicitado e definirá errno = EPIPE
.
Quem é o culpado?
No caso descrito um pouco de tudo. Tratamento de erros em cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) não é redundante. Em muitos casos, é melhor agir com cautela, embora isso exponha falhas repentinas em um padrão projetado para o comportamento tradicional.
O que fazer?
Errado:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Correto:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Aqui, o fechamento antecipado de um pipe será tratado pelo interpretador de comandos ao atender o pipeline aninhado ("dentro" dos parênteses). Assim, se
fortune
reportará um erro ao escrever para um canal fechado no status, então a saída|| echo "Ошибка"
não vai chegar a lugar nenhum, pois o canal já está fechado. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Aqui está o utilitário
cat
atua como um amortecedor porque ignora o erroEPIPE
após a retirada. Isso é o suficiente por enquanto conclusãofortune
pequeno (várias linhas) e cabe no buffer do canal (de 512 bytes a ≈64K, na maioria dos sistemas operacionais ≥4K). Caso contrário, o problema poderá retornar.
Como processar corretamente EPIPE
e outros erros de gravação?
Não existe uma solução única e certa, mas existem recomendações simples:
EPIPE
necessário deve ser processado (e refletido no status de saída) ao gerar dados que requerem integridade. Por exemplo, durante a operação de arquivadores ou utilitários de backup.EPIPE
é melhor ignorar ao exibir informações e mensagens auxiliares. Por exemplo, ao exibir informações sobre opções--help
ou--version
.- Se o código que está sendo desenvolvido puder ser usado em um pipeline antes
| head
em seguidaEPIPE
É melhor ignorar, caso contrário é melhor processar e refletir no status de saída.
Gostaria de aproveitar esta oportunidade para expressar minha gratidão às equipes
Continuem assim, camaradas,
Obrigado
KDPV de
Fonte: habr.com