$> set -o pipefail
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Повезло!
$> fortune | head -1 > /dev/null && echo "Повезло!" || echo "Вы проиграли"
Вы проиграли
il est fortune
programme conditionnel sans exit(rand())
.
Peux-tu expliquer? qu'est-ce qui ne va pas ici?
Digression lyrique-historique
J'ai découvert ce Heisenbug pour la première fois il y a un quart de siècle. Ensuite pour la passerelle dans FaxNET il a fallu créer plusieurs utilitaires via
Mon expérience précédente dans la gestion des bogues dans sendmail et uucp/uupc a ajouté à ma diligence dans la « gestion approfondie des erreurs ». Cela ne sert à rien de plonger dans les détails de cette histoire, mais j'ai lutté avec ce Heisenbug pendant deux semaines pendant 10 à 14 heures. Par conséquent, on s'en est souvenu, et hier, cette vieille connaissance est passée nous rendre visite à nouveau.
TL;DR Réponse
Utilitaire head
может fermer la chaîne de fortune
immédiatement dès qu'il lit la première ligne. Si fortune
génère plus d'une ligne, puis l'appel correspondant write()
renverra une erreur ou signalera que moins d’octets sont générés que demandé. À son tour, écrit avec une gestion minutieuse des erreurs fortune
a le droit de refléter cette situation dans son statut de sortie. Puis à cause de l'installation set -o pipefail
travaillera || echo "Вы проиграли"
.
cependant, head
je n'arriverai peut-être pas à temps fermer avant fortune
terminera la sortie des données. Alors ça marchera && echo "Повезло!"
.
Dans l'un de mes aujourd'hui GNUMakefile
echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"'
Traduit en humain
C'est courant ici pour --version
il demande qui il est, et si l'option n'est pas prise en charge, alors un stub est inséré "Veuillez utiliser un compilateur compatible GCC ou CLANG".
comme
#define MDBX_BUILD_COMPILER "lcc:1.23.20:Sep--4-2019:e2k-v3-linux Please use GCC or CLANG compatible compiler"
Franchement, je n’ai pas immédiatement reconnu mon ancienne « connaissance ». De plus, le projet a déjà été testé à plusieurs reprises sur Elbrus et sous de nombreuses distributions différentes, dont Alt. Avec différents compilateurs, versions de GNU Make et bash. Par conséquent, je ne voulais pas voir mon erreur ici.
En essayant de reproduire le problème et/ou de comprendre ce qui se passait, des choses plus étranges ont commencé à se produire.
Sort de ligne de commande :
echo "#define MDBX_BUILD_COMPILER '$(set -o pipefail; LC_ALL=C cc --version | head -1 || echo "Please use GCC or CLANG compatible compiler")'"
De temps en temps, cela produisait du texte supplémentaire, puis non... Souvent, l'une des options restait assez longtemps, mais si vous pouviez plus longtemps, vous obteniez toujours les deux !
Bien sûr, strace
D'ailleurs, comme tout homme qui se respecte strace
préfère ne pas se reproduire.
Alors que se passe-t-il?
- Utilitaire
head
a le droit (ou plutôt est même obligé) de fermer le canal en cours de lecture dès qu'il lit le nombre de lignes demandé. - L'auteur du programme générateur de données (dans ce cas
cc
) может imprimer plusieurs lignes et gratuit faites-le via plusieurs appelswrite()
. - si le lecteur aura le temps de fermer la chaîne de son côté avant la fin de l'enregistrement du côté de l'écrivain, celui-ci recevra alors une erreur.
- Programme d'écriture a le droit les deux ignorent l’erreur d’écriture du canal et la reflètent dans votre code d’achèvement.
- En raison de l'installation
set -o pipefail
le code d'achèvement du pipeline sera différent de zéro (erroné) si le résultat est non nul d'au moins un élément, et alors cela fonctionnera|| echo "Please use GCC or CLANG compatible compiler"
.
Il peut y avoir des variations en fonction de la façon dont le programme d'écriture fonctionne avec les signaux. Par exemple, le programme peut se terminer anormalement (avec génération automatique d'un statut de fin différent de zéro/erreur), ou write()
renverra le résultat de l'écriture de moins d'octets que demandé et défini errno = EPIPE
.
Qui est à blâmer?
Dans le cas décrit Un peu de tout. Gestion des erreurs dans cc
(lcc:1.23.20:Sep—4-2019:e2k-v3-linux) n'est pas redondant. Dans de nombreux cas, il est préférable de pécher par excès de prudence, même si cela révèle des défauts soudains dans un modèle conçu pour un comportement traditionnel.
Que faire?
Mauvais:
fortune | head -1 && echo "Повезло, но вы рискуете!" || echo "WTF?"
Correctement:
-
(fortune && echo "Успешно" || echo "Ошибка") | head -1
Ici, la fermeture anticipée d'un canal sera gérée par l'interpréteur de commandes lors de la maintenance du pipeline imbriqué ("à l'intérieur" des parenthèses). En conséquence, si
fortune
signalera une erreur d'écriture sur un canal fermé dans l'état, puis la sortie|| echo "Ошибка"
cela n’aboutira à rien, puisque la chaîne est déjà fermée. -
fortune | cat - | head -1 && echo "Успешно" || echo "Ошибка"
Voici l'utilitaire
cat
agit comme un amortisseur car il ignore l'erreurEPIPE
lors du retrait. C'est suffisant pour l'instant conclusionfortune
petit (plusieurs lignes) et tient dans le tampon du canal (de 512 octets à ≈64K, dans la plupart des systèmes d'exploitation ≥4K). Sinon, le problème pourrait réapparaître.
Comment traiter correctement EPIPE
et autres erreurs d'enregistrement ?
Il n’existe pas de solution unique, mais il existe des recommandations simples :
EPIPE
requis doit être traité (et reflété dans l'état de sortie) lors de la sortie de données qui nécessitent une intégrité. Par exemple, lors du fonctionnement d'archiveurs ou d'utilitaires de sauvegarde.EPIPE
mieux vaut ignorer lors de l'affichage d'informations et de messages auxiliaires. Par exemple, lors de l'affichage d'informations sur les options--help
ou--version
.- Si le code en cours de développement peut être utilisé dans un pipeline avant
| head
puisEPIPE
Il vaut mieux ignorer, sinon il vaut mieux traiter et refléter dans le statut de sortie.
J'en profite pour exprimer ma gratitude aux équipes
Continuez ainsi Camarades, debout
merci
KDPV de
Source: habr.com