Forum Programmation.shell Un pipeline, deux pipelines, trois pipelines...

Posté par .
Tags : aucun
0
16
juin
2005
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 . Évalué à 4.

    As-tu vérifié que

    * 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 . Évalué à 2.

      programme1 | traitement : ça affiche bien le résultat traité au fur et à mesure dans mon terminal.
      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 . Évalué à 3.

        Effectivement, j'avais un doute alors j'ai fait un test simple :

        { 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 . Évalué à 4.

      Ah ben en fait la réponse était dans le premier message.
      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 (page perso) . Évalué à 5.

    je soupçonne un problème de flush. ce n'est pas bash qui "gère" les pipes c'est le kernel : bash invoque les processus et lie tout simplement le stdin de droite au stdout de gauche pour chaque pipe.

    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 . Évalué à 2.

      Oki, donc trouver comment flusher en awk...
      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 (page perso) . Évalué à 2.

        Ben awk manque cruellement de features et est bien plus dur à debugger que du perl ou du python.

        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 . Évalué à 2.

          En ce moment je prépare quelques petits outils pratiques usant et abusant de Xdialog, awk, grep et autres joyeusetés du genre (on les trouve sur le site de l'association Antesis.org : http://antesis.org(...) ).
          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 :

          /usr/local/bin/Xwget :
          #!/bin/bash
          if [ ! "$@" ]; then
          url=`Xdialog --stdout --title Xwget --inputbox "URL du fichier à télécharger" 0 0` || exit 1
          else
          url=$@
          fi
          wget "$url" 2>&1 | awk '{for( i=1; i<= NF; i=i+1)if($i~/[[:digit:]]+%/){ gsub( "%", "",$i); print $i};fflush()}' | Xdialog --title "Xwget : $url" --gauge "$url" 0 0

          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 à ceux qui les ont postés. Nous n'en sommes pas responsables.