Forum Programmation.shell zenity : processus père/fils, fifo,…

Posté par  (site web personnel) .
Étiquettes : aucune
0
18
juil.
2011

J'essaye d'écrire un script bash qui lance plusieurs commandes et qui montre la progression avec zenity. Mon script est long donc je ne vais écrire ici qu'une version ultra light illustrant le problème :

(
    echo "25" ; echo "# C'est parti"
    var=$RANDOM
    echo "50" ; echo "# La variable est $var"
    echo "100" ; echo "# C'est fini"
) | zenity --progress --title="Test" --text="progression..."  --percentage=0

echo $var

Le problème c'est que évidemment le echo $var ne renvoie rien. J'ai compris que c'est parce que $var est déclaré dans un processus fils et qu'il ne peut donc pas passé de variable au processus père. Je crois comprendre qu'il est possible d'utiliser fifo mais je ne comprends vraiment pas comment ca s'utilise. Chaque essai que j'ai fait s'est soldé par un échec (script qui reste en attente). Quelqu'un aurait-il une solution ?

  • # par exemple

    Posté par  (site web personnel) . Évalué à 2.

    mkfifo /tmp/myvarvar
    
    echo $var > /tmp/myvarvar
    # dans une autre shell
    read var < /tmp/myvarvar
    echo $var
    rm /tmp/myvarvar
    

    le fifo est bloquant tant qu'on a pas lu la valeur

  • # Mais pourquoi est-il aussi méchant ?

    Posté par  . Évalué à 5.

    J'ai compris que c'est parce que $var est déclaré dans un processus fils et qu'il ne peut donc pas passé de variable au processus père.

    En effet.

    [...] il est possible d'utiliser fifo mais je ne comprends vraiment pas comment ca s'utilise. Chaque essai que j'ai fait s'est soldé par un échec (script qui reste en attente)

    D'abord, ici, fifo n'est qu'un nom de fichier; un fichier particulier : c'est un pipe nommé (pipe==fifo). En gros, c'est un tuyau. Un processus écrit d'un côté du tuyau, et un autre lit de l'autre côté. C'est à sens-unique.

    Le problème des pipes, c'est qu'il ont une taille fixe, 4KiB IIRC. Le processus qui écrit est bloqué dès que le pipe est rempli, ce qui peut arriver si personne ne lit à l'autre bout, ou si l'autre bout ne se vide pas assez vite. Dès que le lecteur lit un bout du pipe, l'écrivain peut alors recommencer à le remplir. Ainsi de suite.

    L'exemple que tu pointes (mal, ceci dit, il faut lire tout les commentaires pour voir à quoi tu fais référence) est bancal. Reprenons-le :

    #!/bin/sh
    
    mkfifo fifo
    cat $fichier | grep $valeur_recherche > fifo
    while read ligne; do
     (( var = var + 1 ))
     echo $var
    done < fifo
    echo $var
    rm fifo
    

    Si le contenu filtré :

    cat $fichier | grep $valeur_recherche > fifo
    

    dépasse les 4KiB, alors ca va bloquer en attendant que le pipe se libère, ce qui n'arrivera jamais avec ce script, puisque la lecture se fait après :

    while read ligne; do
     (( var = var + 1 ))
     echo $var
    done < fifo   # <--- ici on lit depuis le pipe
    

    Donc, il faut mettre l'écrivain en arrière plan, pour qu'il puisse continuer à écrire.

    En plus, il y a une erreur de syntaxe sur cette ligne :

     (( var = var + 1 ))  # On devrait écrcire : var=$(( var + 1 ))
    

    #!/bin/sh
    
    rm -f fifo     # Suppression de la fifo au cas ou...
    mkfifo fifo    # Création de la fifo
    
    # Lancement de l'écrivain', en arrière plan, pour qu'il ne bloque
    # pas sur remplissage de la fifo : note le '&' en fin de ligne
    cat "${fichier}" | grep "${valeur_recherche}" > fifo &
    
    # Lancement du 'lecteur'
    while read ligne; do
     var=$(( var + 1 ))
     printf "%s"\n" "${var}"
    done < fifo
    
    # Resultat :
    printf "J'ai lu '%s' lignes\n" "${var}"
    
    # Plus besoin de la fifo, on la supprime
    rm -f fifo
    

    MAIS ! Cela ne fonctionne plus si la sortie de la boucle est à son tour 'pipée' dans une commande :

    while read ligne; do
     var=$(( var + 1 ))
     printf "%s"\n" "${var}"
    done < fifo |zenity
    

    Là, je sèche... :-/

    • [^] # Re: Mais pourquoi est-il aussi méchant ?

      Posté par  . Évalué à 1.

      MAIS ! Cela ne fonctionne plus si la sortie de la boucle est à son tour 'pipée' dans une commande :
      Là, je sèche... :-/

      Alors, je crois que j'ai la réponse. Prenons ce bout de code :

      while foo-bar; do
          var=blabla
      done |grep toto
      

      Si je comprends bien POSIX dit explicitement (à la fin du chapitre) qu'un sous-shell doit être utilisé dans ce cas :

      Additionally, each command of a multi-command pipeline is in a subshell environment; [...]

      En fait, la commande while foo-bar; do blabla; done est une commande composée (multi-command), et en plus le résultat est pipé, donc ça rentre dans cette définition.

      Donc il n'est pas possible de récupérer la valeur de '${var}' en dehors de la boucle while.

      Donc, si tu veux récupérer sa valeur, tu devras faire un truc du genre :

      tmp_file="$( mktemp /tmp/XXXXXX )"
      while foo-bar; do
          var=blabla
          printf "%s" "${var}" >"${tmp_file}"
      done |grep toto
      var="$(cat "${temp_file}")"
      

      C'est crade, mais ça marche... :-(

      Hop,
      Moi.

      • [^] # Re: Mais pourquoi est-il aussi méchant ?

        Posté par  . Évalué à 2.

        À propos de bricolage pourquoi ne pas mettre zenity dans un sous processus au lieu de tout ton code si ça gêne pas ? genre :

        #!/bin/sh
        
        fifo=$(mktemp -u)
        
        mkfifo $fifo
        
        tail -f $fifo | zenity --progress --title="Test" --text="progression..."  --percentage=0 &
        
        echo 10 > $fifo
        sleep 1
        echo 20 > $fifo
        sleep 1
        echo 30 > $fifo
        sleep 1
        echo 40 > $fifo
        sleep 1
        echo 50 > $fifo
        sleep 1
        echo 60 > $fifo
        sleep 1
        echo 70 > $fifo
        sleep 1
        echo 80 > $fifo
        sleep 1
        echo 90 > $fifo
        sleep 1
        echo 100 > $fifo
        
        rm $fifo
        

        Vous aurez compris le principe...

        Bon ça reste crade, surtout le tail -f qui est le seul moyen que j'ai trouvé de "pooler" la fifo, si quelqu’un à mieux ?

        • [^] # Re: Mais pourquoi est-il aussi méchant ?

          Posté par  . Évalué à 1.

          [...] pourquoi ne pas mettre zenity dans un sous processus [...] ?

          Ouais ! Bravo ! Nous avons un gagnat ! :-)

          fifo=$(mktemp -u)

          Ça, moi, j'aime bien ! Vraiment ! ;-)

          Bon ça reste crade, surtout le tail -f qui est le seul moyen que j'ai trouvé de "pooler" la fifo, si quelqu’un à mieux ?

          Oui, deux points :

          1. directement utiliser la fifo comme entrée à zenity
          2. tuer le process quand il n'y en plus besoin ? ;-)

          #!/bin/bash
          
          fifo=$(mktemp -u)
          mkfifo $fifo
          
          zenity blabla 2>/dev/null <"${fifo}" &
          progress_pid="${!}"
          
          exec 6>&1   # Save stdout
          exec >"${fifo}" # Redirect stdout to the fifo
          
          i="toto"
          for(( i=0; i<=100; i+=10 )); do
            sleep 1
            printf "%s\n" "${i}"  # stdout goes to the fifo ! :-)
          done
          
          exec >&6 # Restore stdout
          kill "${progress_pid}"
          
          printf "Et maintenant i='%s'\n" "${i}"
          
          rm -f "${fifo}"
          

          Hop,
          Moi.

          • [^] # Re: Mais pourquoi est-il aussi méchant ?

            Posté par  . Évalué à 1.

            Ha oui pas mal, en utilisant la sortie du script comme entrée pour zenity la pipe n'est pas détruite tant que le script tourne.

            Bravo !

            • [^] # Re: Mais pourquoi est-il aussi méchant ?

              Posté par  . Évalué à 1.

              Bon, pas d'autre améliorations ?

              Aller, je ne résiste pas à faire une nouvelle proposition ! :-)

              Voilà, c'est assez complet, avec tout plein de commentaires, tous les cas tordus auxquels j'ai pensé sont pris en compte, le ménage est fait.

              Ne reste plus qu'à remplacer la boucle for par le vrai boulot à faire, abstraire un peu pour le rendre générique et réutilisable (au cas où il y ai besoin de plusieurs progress dans le même script), etc..

              Je l'ai fait en Anglais, car je vais me le mettre de côté, çui-ci... :-)

              Enjoy !

              #!/bin/bash
              # (C) 2011 "Yann E. MORIN" <yann.morin.1998@anciens.enib.fr>
              # License: GPLv2
              
              # In case the user cancels, we want to be notified
              cancelled=n
              
              # Create the fifo
              # Note! If the witer comes and goes (eg. multiple open()
              # and close() of the fifo), the reader seems to get a
              # SIGPIPE. So we just open the fifo once using FD#6, see below.
              # Original idea by "Tonton Benoit" <xmpp:maxime@neurone-network.org>
              fifo_dir="$( mktemp -d )"
              fifo="${fifo_dir}/fifo"
              mkfifo "${fifo}"
              
              # Print progress, and catch cancel
              # There is a slight race here, as we can get cancelled
              # between the check and the printf, but as we are emptying
              # the fifo (see below), we can still print into it.
              report_progress() {
                  if [ "${cancelled}" = "n" ]; then
                      printf "%s\n" "${1}" >&6 2>/dev/null
                  else
                      return 1
                  fi
              }
              
              # Catch cancel:
              # - set global variable
              # - empty the fifo so we don't block in report_progress, above
              # Need to do that before running zenity, or there is
              # a slight race window where the user could cancel, and us
              # not having our trap set yet.
              # Note: when we finally remove the fifo, 'cat' will die. We
              # don't care about this, but we want it to be silent
              trap_cancel() {
                  cancelled=y
                  cat "${fifo}" >/dev/null 2>&1 &
              }
              trap trap_cancel SIGHUP
              
              # Start the progress report
              # Consign output to oblivion, as there is a race window
              # in case we were to remove the fifo just while zenity
              # was not yet finished (ie. on its dying path, but not
              # yet dead)
              zenity --progress --title="Test"    \
                     --text="progression..."      \
                     --percentage=0               \
                     --auto-close                 \
                     --auto-kill                  \
                     >/dev/null 2>&1              \
                     <"${fifo}"                   &
              progress_pid="${!}"
              
              # In case we are ourselves killed (think Ctrl-C!), we need
              # to kill zenity, and leave it no chance to bite us back.
              # Yes, there is a slight race window here, too, in case the
              # user kills us between the moment we launch zenity, and
              # the moment we set up the trap. But we can't set the trap
              # before we know the PID of zenity... This is unavoidable... :-(
              trap_int() {
                  kill -9 ${progress_pid}
                  exec 6>&-
                  rm -rf "${fifo_dir}"
                  printf 'Argh, user killed us... :-(\n'
                  exit 1
              }
              trap trap_int SIGINT
              
              # Opening FD#6 for writing earlier than that seems
              # to hang?!?... OK, anyway we only need it now...
              exec 6>"${fifo}"
              
              # Start our job, now...
              for(( i=0; i<=100; i++ )); do
                  sleep 0.1
                  report_progress "${i}" || break;
              done
              
              # We end up here for two reasons:
              # 1- the job finished, so zenity (in auto-close) is now
              #    about to die by itself, so will no longer bug us
              # 2- the job was cancelled, so zenity is about to die
              #    by itself as well, so won't bother us either
              # So, we no longer have a valid reason to trap
              trap - SIGHUP SIGINT
              
              # The fifo is of no use, now. Close FD#6 first.
              exec 6>&-
              rm -rf "${fifo_dir}"
              
              if [ "${cancelled}" = "y" ]; then
                  printf "Argh, user cancelled us... :-(\n"
              fi
              printf "Now, i='%s'\n" "${i}"
              

              Hop,
              Moi.

              • [^] # Re: Mais pourquoi est-il aussi méchant ?

                Posté par  . Évalué à -2.

                Il va être content celui qui a posé la question initiale, "tenez, je vous aide sur un simple message de forum, mais si vous acceptez mon aide, alors vous devez utiliser la licence que je choisis !". Enfin il peut tout de même se rabattre sur les messages précédents.

                • [^] # Re: Mais pourquoi est-il aussi méchant ?

                  Posté par  . Évalué à 0.

                  "tenez, je vous aide sur un simple message de forum, mais si vous acceptez mon aide, alors vous devez utiliser la licence que je choisis !

                  Heu... Comment dire. C'est moi qui ai écrit le script. Je peux bien lui coller la licence que je veux, non ?

                  Certes, c'est le résultat d'un ensemble de réflexions partagées, mais si tu suis le cheminement des commentaires, tu verras tout de même comment j'en suis arrivé à ce script sans réutilisation de code... J'ai correctement crédité Tonton Benoit pour son idée de fifo créée avec mktemp et d'utilisation de la fifo en entrée de zenity .

                  Par ailleurs, aucune licence n'est visible sur la page du post. Donc si je n'en met pas une explicitement, il n'a pas le droit d'utiliser ce script, puisque par défaut le droit de propriété intellectuelle français (et ailleurs aussi, IIRC) n'autorise pas l'utilisation sans accord explicite de l'auteur. En précisant une licence telle que la GPLv2, je lui donne explicitement le droit, en se conformant à la licence, de réutiliser ce script.

                  Ceci dit, si je me trompe ( et c'est toujours possible, personne n'est infaillible, même pas moi ;-) ), du coup il ne peut pas utiliser ce script, puisque je ne lui en ai pas donnée. Du coup. :-(

                  Hop,
                  Moi.

                  • [^] # Re: Mais pourquoi est-il aussi méchant ?

                    Posté par  . Évalué à 0.

                    Heu... Comment dire. C'est moi qui ai écrit le script. Je peux bien lui coller la licence que je veux, non ?

                    Bien sûr, rien ne vous empêche de le faire, rien ne vous empêche non plus de nommer vos variables de façon volontairement incompréhensible (je ne dis pas que vous l'avez fait, c'est un exemple), ou autre chose désagréable.
                    Cependant, on attend généralement d'une réponse de forum qu'elle aide, qu'elle apprenne quelque chose, ou qu'elle ne soit pas volontairement contraignante sur la façon de s'en servir (là, par contre, la licence est une contrainte volontaire).
                    Alors, bien sûr, ce programme commence à devenir élaboré par rapport au premier message, et n'est pas les bouts de code le précédant, dont on peut encore se servir sans contrainte de licence, et ainsi faire soi-même son programme sans GPL.

                    Par ailleurs, aucune licence n'est visible sur la page du post. Donc si je n'en met pas une explicitement, il n'a pas le droit d'utiliser ce script, puisque par défaut le droit de propriété intellectuelle français (et ailleurs aussi, IIRC) n'autorise pas l'utilisation sans accord explicite de l'auteur. En précisant une licence telle que la GPLv2, je lui donne explicitement le droit, en se conformant à la licence, de réutiliser ce script.

                    Cette partie est complètement inutile.
                    Aucun message de forum répondant à une demande d'aide sur un code, n'a jamais eu de licence autorisant explicitement son utilisation. C'est tacite et accepté par tout le monde. A-t-on vu un jour une plainte "Mon message de forum a été utilisé alors que je n'avais pas mis de licence donc mon mon droit d'auteur est violé" ?

                    • [^] # Re: Mais pourquoi est-il aussi méchant ?

                      Posté par  . Évalué à 1.

                      A-t-on vu un jour une plainte "Mon message de forum a été utilisé alors que je n'avais pas mis de licence donc mon mon droit d'auteur est violé"

                      En théorie, c'est tout à fait possible. OK, je dis bien en théorie . Et en spécifiant la licence, je lui impose certes des devoirs, mais aussi, et surtout, je lui octroie explicitement des droits. Ce n'est peut-être pas la licence qui te plait le plus ; c'est celle de mon choix. Tant pis si elle ne te plaît pas. Tant pis si elle ne lui plaît pas.

                      Et puis, franchement, que peut donc imposer la GPLv2 à un script shell ? Aucun binaire n'est généré.

                      • si il garde le script pour lui et l'utilise au quotidien, le résultat (eg. fichiers générés) n'est de toute façon pas couvert par la GPLv2 ( et heureusement, sinon on ne pourrait pas compiler le code de logiciels privateurs avec gcc)
                      • si il redistribue le script, ce que la GPLv2 autorise, la seule contrainte que je lui impose est que le destinataire obtienne les mêmes droits que lui.

                      Hop,
                      Moi.

                      • [^] # Re: Mais pourquoi est-il aussi méchant ?

                        Posté par  . Évalué à 1.

                        En théorie, c'est tout à fait possible. OK, je dis bien en théorie.

                        ...

                        Et puis, franchement, que peut donc imposer la GPLv2 à un script shell ?

                        Là, vous marquez un point.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.