Bonjour ô forum.
La question technique du jour :
Je veux enchaîner deux pipes en shell (j'utilise bash), et que le tout s'effectue en parallèle et donc avoir au fur et à mesure la sortie des deux pipes.
La commande ressemble à ça :
programme1 | traitement | programme2
Typiquement dans programme1 on a un programme qui fait des trucs et indique son statut sur stdout. Dans traitement on a une commande awk assez complexe qui choppe le résultat de programme1 et le met en forme pour être compris par programme2. Et enfin programme2 va faire des trucs avec le résultat transformé par traitement de programme1.
Le problème est le suivant : il faut que tout se fasse au fur et à mesure. programme1 peut durer très longtemps, et je veux que programme2 traite les sorties de programme1 passées à la moulinette traitement immédiatement.
Or ce qu'il se passe c'est que le tout reste bêtement bloqué jusqu'à ce que programme1 ait fini, et alors programme2 analyse tout les résultats d'un coup.
Ca ne vient ni de programme1, ni de traitement, ni de programme2, j'ai fait les tests suivants :
programme1 | traitement : ça affiche bien le résultat traité au fur et à mesure dans mon terminal.
for i in liste_de_résultats_type_traitement; do echo $i; sleep 1; done | programme2 : toutes les secondes programme2 m'affiche bien ce qui ressort de son analyse de la ligne fournie.
Bref je soupçonne donc que ce comportement vient du chaînage de deux pipes, je suppose même qu'en fait le bash là ne dispose que d'un seul pipeline, donc il balance programme1, le pipe dans traitement, garde tout ça jusqu'à ce que le pipe soit libéré ie que programme1 ait fini, rebalance le pipe entre traitement et programme2, et zou.
Donc il faudrait définir deux pipelines différents, mais la doc de bash n'indique pas si c'est possible, donc pas non plus "si oui comment"...
Et il est aussi possible que je me fourvoie lamentablement...
Alors si une âme charitable et éclairée, passant par ici, a la réponse à mes questionnements, et même une solution merveilleuse à mon problème, je suis preneur !
Merci d'avance,
Yth.
# Commençons par le début
Posté par Obsidian . Évalué à 4.
* Tes données ne soient pas dans le cache ? (utiliser fflush())
* Ton programme « traitement » n'ait pas besoin d'avoir l'intégralité de tes résultats pour t'en fournir le bilan (typiquement le cas de sort) ?
[^] # Re: Commençons par le début
Posté par Yth (Mastodon) . Évalué à 2.
J'ai fais ce test là, c'est sûr traitement n'a pas besoin de l'intégralité des données pour les traiter.
programme1 dure très longtemps (une heure environ), donc je suis vraiment sûr de moi, j'ai des affichages sortis par traitement immédiatement.
Que mes données soient dans le cache...
traitement devrait faire un flush à chaque ligne qu'il affiche ? Histoire d'être sûr qu'il ne cache pas bêtement son truc avant que ça passe dans le deuxième pipe ?
Comment je fais ça en bash...
Un exemple concret est le suivant :
url=un fichier quelque part, plutôt gros pour bien voir la chose
wget $url 2>&1 | awk '{for(i=1;i<=NF;i=i+1)if($i~/[[:digit:]]+%/){gsub("%","",$i);print $i};}' | Xdialog --gauge "$url" 0 0
unset url
On voit mieux avec Xdialog qu'avec dialog simplement, parce que ça se traine beaucoup plus, et je teste avec un fichier assez gros, il le télécharge en entier et après on voit la jauge avancer jusqu'à 100%, enfin avec dialog ça va trop vite on ne voit rien, mais avec Xdialog, la barre monte joliment de 0% à 100%. Le fichier faisant 27Mo, il y a nombre d'étapes.
Et tu peux tester juste programme1 | traitement :
wget $url 2>&1 | awk '{for(i=1;i<=NF;i=i+1)if($i~/[[:digit:]]+%/){gsub("%","",$i);print $i};}'
Tu vois bien l'affichage se faire au fur et à mesure que le fichier se télécharge, les pourcents s'affichent bien.
Puis faire un :
for i in 10 20 30 40 50 60 70 80 90 100; do echo $i; sleep 1; done | Xdialog --gauge "$url" 0 0
nous fait bien monter le pourcentage de la jauge de 10% toutes les secondes.
Wala wala...
Yth.
[^] # Re: Commençons par le début
Posté par Sylvain Sauvage . Évalué à 3.
{ echo prout; sleep 3; echo prout; } | grep -n "^"
(pas besoin de X ou d'url à télécharger ;o)
2:prout met bien 3 secondes pour arriver après 1:prout
et
{ echo prout; sleep 3; echo prout; } | grep -n "^" | grep -n "^"
là, 1:1:prout et 2:2:prout arrivent en même temps, mais après 3 secondes.
J'ai aussi testé en utilisant des pipelines nommés :
mkfifo p1 p2
grep -n "^" < p2 &
grep -n "^" < p1 > p2 &
{ echo prout; sleep 3; echo prout; } > p1
et ça fait pareil : le résultat n'est affiché qu'à la fin.
Donc, j'en conclus qu'il y a effectivement quelque chose qui coince.
J'ai pas la réponse mais maintenant j'ai la question. Affaire à suivre...
[^] # Re: Commençons par le début
Posté par Yth (Mastodon) . Évalué à 4.
Je n'avais pas tilté que fflush() était la fonction awk.
En l'utilisant, en effet ça marche maintenant.
Donc conclusion du cas général :
programme1 | traitement | programme2
Il faut s'assurer que "traitement" fasse bien un flush de sa sortie, voilà, c'est tout.
Bon, ben merci à tous, encore une fois ce forum répond aux questions assez rapidement et efficacement, c'est cooool :)
Yth.
# flush
Posté par gc (site web personnel) . Évalué à 5.
avec les trois programmes perl suivants j'arrive à afficher chaque seconde le résultat des deux traitements du résultat du premier programme (le premier substitue le premier caractère par un "m" et le deuxième passe en majuscules) :
[gc@meuh /tmp] perl -e '$| = 1; while () { print "foo1\n"; sleep 1; }' | perl -pe 'BEGIN { $| = 1 } s/./m/' | perl -pe '$_ = uc'
MOO1
MOO1
MOO1
MOO1
ce qui doit coincer pour toi c'est le flush. note les ``$| = 1'' qui permettent aux deux premiers programmes de ne pas coincer (variable d'autoflush en perl). typiquement pas mal de programmes bufferisent y compris les retours à la ligne lorsqu'ils se rendent compte qu'ils ne sont pas branchés sur un vrai terminal (enlève le ``$| = 1'' du programme du milieu pour voir).
je ne peux pas t'aider en awk mais j'espère que tu es _obligé_ d'utiliser awk sinon c'est du suicide de choisir d'utiliser un truc pareil ;p. cherche à flusher ton awk (et le traitement aussi bien sûr).
[^] # Re: flush
Posté par Yth (Mastodon) . Évalué à 2.
Merci pour ces infos !
Et euh, tu as quoi contre awk ? Je ne vais pas invoquer perl ou python pour des traitements triviaux que awk sait très bien faire...
Yth.
[^] # Re: flush
Posté par gc (site web personnel) . Évalué à 2.
Ensuite si on considère par exemple le startup time, sur mon système j'ai 0.02s pour python, 0.004s pour perl et 0.002s pour awk donc le bénéfice est franchement faible.
Après effectivement si c'est juste du traitement ligne par ligne... mais dès que tu veux rajouter une regexp ou quelque chose de plus poussé tu risques de perdre du temps (sauf si tu gurutises en awk et tu neuneutises en perl évidemment).
[^] # Re: flush
Posté par Yth (Mastodon) . Évalué à 2.
Ce sont principalement des choses très simples, comme là mon problème était juste de récupérer une valeur sur chaque ligne, jusqu'à présent awk est amplement suffisant.
Mais c'est sûr que si je me met à faire des choses plus complexes je devrais certainement passer à autre chose. Plutôt python, comme je ne connais pas perl :)
J'ai toujours tendance à essayer d'utiliser l'outil le plus adapté, et donc généralement le plus simple et à même de répondre au besoin. Pour ça je considère awk plus "simple" que perl ou python, ce qui ne veut pas dire que toute action faisable en awk se fait plus simplement avec lui qu'avec python, je veux bien croire qu'on peut faire des lignes absolument imbitables en awk :p
Mais j'évite d'utiliser un marteau-pilon à vapeur pour écraser une mouche, la tapette fait aussi bien et plus simplement le travail.
Je viens donc de sortir officiellement et en exclusivité Xwget (le nom n'existe pas encore ?) version 1.0 (soyons fous !) dont voici le code source intégral :
Et dont l'(in?)intérêt extrêmement limité est d'avoir une petite fenètre graphique permettant de gérer un téléchargement avec wget.
Yth.
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.