Modification script shell

bonjour, j'ai le script shell suivant que j'ai écrit:
for i in 1 2 3 
do 

if [ $1 = "x" ] 
then 
 FILE=$1.sync 
 echo > $FILE 
fi 

while [ ! -f $FILE ] 
do 
sleep 1 
done 

echo $1 : $i 
rm -f $FILE 
done


Je souhaite le modifier pour que quand je tape ça dans mon shell:
./script x & ./script y &
il m'affiche ALTERNATIVEMENT x : 1 , y : 1 , x : 2 , y : 2...

Quelqu'un saurait me filer un coup de main car personne ne sait depuis des jours et des jours..
merci

21 réponses

Résumé de la discussion

La problématique porte sur la synchronisation dans un script shell utilisé en parallèle, avec l’objectif d’afficher alternativement x et y pour chaque itération lorsque deux instances s’exécutent en arrière-plan. Parmi les réponses, des propositions évoquent des tests basés sur des valeurs lues dans des fichiers ou des compteurs, l’usage d’un verrou (lockfile) et même une approche en C pour contrôler l’ordre d’exécution. D’autres participants rappellent que l’ordre d’exécution n’est pas garanti par nature des processus concurrents et que certaines solutions proposées peuvent être lourdes ou ne pas répondre exactement au besoin. En complément, des suggestions techniques, comme un autre langage et des démonstrations concrètes, apparaissent, soulignant que des alternances propres exigent des mécanismes de synchronisation ou des commandes spécialisées.

Bobot (l’IA à votre service)
  1. bon c'est pas grave tant pis on aura essayé, j'aurais la réponse dans quelques temps, attendons...
    merci pour toute cette énergie déployée ça a été instructif.
    1. Contributeur
      Par contre si tu y penses à l'occasion, merci de venir nous faire part de la solution, qu'on ne meure pas trop c*n ;-))
    2. oui pas de problème, de toute façon personne n'a trouvé donc dans l'affaire on a tous perdu (surtout moi).
  2. j'ai testé le script et échec du premier coup lol, c'est pas évident je sais...
    1. Contributeur
      Ben faut croire que t'as la scoumoune ;-((
    2. Contributeur
      zipe, fait un man lockfile
  3. je ne comprends pas ton while : (jamais vu) et je n'ai pas encore vu l'instruction lock, les seules choses dont j'ai le droit c'est le lockfile et un fichier de synchronisation, on ma dit que c'était ça la clef..
    1. Contributeur
      while : = while true
      Tant que la condition est vraie.

      et je n'ai pas encore vu l'instruction lock, les seules choses dont j'ai le droit c'est le lockfile
      Ben en fait le "touch lock.tmp" ne fait que creer un fichier nommé "lock.tmp" ;-)
  4. Contributeur
    Tiens essaie ça :

    #! /bin/bash
    
    #set -xv
    
    for i in {1..50}
    do
            while :
            do
                    if [ -e lock.tmp ]
                    then continue
                    else
                            echo "$1 : $i"
                            touch lock.tmp
                            sleep 1
                            rm -f lock.tmp
                            break
                    fi
            done
    done
    1. alors mon ami zipe voici le preuve que tu es chanceux et je te conseille tout de suite d'aller t'acheter un billet de loto, j'ai testé ton script donc celui ci:
      #! /bin/bash 
      
      for i in 1 2 3 
      do 
              echo $1 : $i 
              sleep 1 
      done


      et voici ce que j'ai dans le shell, observe bien l'erreur arrive à la fin :-)
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1884 
      [2] 1885 
      -VirtualBox:~/TEST$ b : 1 
      a : 1 
      b : 2 
      a : 2 
      b : 3 
      a : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1892 
      [2] 1893 
      -VirtualBox:~/TEST$ a : 1 
      b : 1 
      a : 2 
      b : 2 
      a : 3 
      b : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1900 
      [2] 1901 
      -VirtualBox:~/TEST$ b : 1 
      a : 1 
      b : 2 
      a : 2 
      b : 3 
      a : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1908 
      [2] 1909 
      -VirtualBox:~/TEST$ b : 1 
      a : 1 
      b : 2 
      a : 2 
      b : 3 
      a : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1916 
      [2] 1917 
      -VirtualBox:~/TEST$ a : 1 
      b : 1 
      a : 2 
      b : 2 
      a : 3 
      b : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1924 
      [2] 1925 
      -VirtualBox:~/TEST$ b : 1 
      a : 1 
      b : 2 
      a : 2 
      b : 3 
      a : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1932 
      [2] 1933 
      -VirtualBox:~/TEST$ b : 1 
      a : 1 
      b : 2 
      a : 2 
      b : 3 
      a : 3 
      
      [1]-  Done                    ./toto.sh a 
      [2]+  Done                    ./toto.sh b 
      -VirtualBox:~/TEST$ ./toto.sh a & ./toto.sh b & 
      [1] 1940 
      [2] 1941 
      -VirtualBox:~/TEST$ a : 1 
      b : 1 
      a : 2 
      b : 2 
      b : 3 
      a : 3 
      
      1. Contributeur
        Pourquoi lances-tu le second script en arrière-plan ?
        Le 1er je veux bien, mais le second ce n'est pas la peine... si ?
      2. et bien si c'est obligatoire lol
      3. Contributeur
        je suis plutôt d'accord avec gerard, rien ne garantit l'ordre d'exécution.
        Si c'est un exercice, j'aimerai bien connaitre la solution, car pour moi c'est impossible ou très compliqué d'avoir ce que tu veux exactement.
      4. l'exercice en lui même est très simple, j'ai ce script de départ:
        for i in 1 2 3 
        do 
        echo $1 : $i 
        done 
        


        A l'exécution de cette commande: ./script.sh x & ./script.sh y & il faut que chaque processus fasse son affichage en alternant obligatoirement, donc l'ordre doit etre ici x y x y x y ou y x y x y x.

        Je sais qu'il faut utiliser la synchronisation et la protection des sections critiques (genre lockfile), mais malgré des dizaines d'essais, nada. C'est du parallélisme et oui c'est difficile.
    2. zipe tu es vraiment chanceux mais je t'assure que ton script ne fonctionne pas je l'ai testé des dizaines de fois et ya tjs une erreur tôt ou tard, j'ai donné la preuve théorique il me semble...
      crois moi j'aurais aimé que tu aies raison :'(
      1. Contributeur
        Dois-je m'inquiéter d'être aussi chanceux ? ;-))

        $ cat foo.sh
        #! /bin/bash
        
        #set -xv
        
        for i in {1..50}
        do
                echo -e "$1 : $i, \c"
                sleep 1
        done
        
        $ ./foo.sh x | tee -a log& ./foo.sh y | tee -a log
        x : 1, [1] 29041
        y : 1, x : 2, y : 2, x : 3, y : 3, x : 4, y : 4, x : 5, y : 5, x : 6, y : 6, x : 7, y : 7, x : 8, y : 8, x : 9, y : 9, x : 10, y : 10, x : 11, y : 11, x : 12, y : 12, x : 13, y : 13, x : 14, y : 14, x : 15, y : 15, x : 16, y : 16, x : 17, y : 17, x : 18, y : 18, x : 19, y : 19, x : 20, y : 20, x : 21, y : 21, x : 22, y : 22, x : 23, y : 23, x : 24, y : 24, x : 25, y : 25, x : 26, y : 26, x : 27, y : 27, x : 28, y : 28, x : 29, y : 29, x : 30, y : 30, x : 31, y : 31, x : 32, y : 32, x : 33, y : 33, x : 34, y : 34, x : 35, y : 35, x : 36, y : 36, x : 37, y : 37, x : 38, y : 38, x : 39, y : 39, x : 40, y : 40, x : 41, y : 41, x : 42, y : 42, x : 43, y : 43, x : 44, y : 44, x : 45, y : 45, x : 46, y : 46, x : 47, y : 47, x : 48, y : 48, x : 49, y : 49, x : 50, y : 50, [1]+  Done                    ./foo.sh x | tee -a log
        
        $ cat log
        x : 1, y : 1, x : 2, y : 2, x : 3, y : 3, x : 4, y : 4, x : 5, y : 5, x : 6, y : 6, x : 7, y : 7, x : 8, y : 8, x : 9, y : 9, x : 10, y : 10, x : 11, y : 11, x : 12, y : 12, x : 13, y : 13, x : 14, y : 14, x : 15, y : 15, x : 16, y : 16, x : 17, y : 17, x : 18, y : 18, x : 19, y : 19, x : 20, y : 20, x : 21, y : 21, x : 22, y : 22, x : 23, y : 23, x : 24, y : 24, x : 25, y : 25, x : 26, y : 26, x : 27, y : 27, x : 28, y : 28, x : 29, y : 29, x : 30, y : 30, x : 31, y : 31, x : 32, y : 32, x : 33, y : 33, x : 34, y : 34, x : 35, y : 35, x : 36, y : 36, x : 37, y : 37, x : 38, y : 38, x : 39, y : 39, x : 40, y : 40, x : 41, y : 41, x : 42, y : 42, x : 43, y : 43, x : 44, y : 44, x : 45, y : 45, x : 46, y : 46, x : 47, y : 47, x : 48, y : 48, x : 49, y : 49, x : 50, y : 50, $
        $

        ;-))
    3. non zipe ta solution marche pas je l'avais déjà essayé tout au début, si tu fais 10 essais tu verras que à un moment tu auras 2 x ou 2 y qui se suivent.

      voici la preuve:

      image x : 1 s'affiche, tu commutes donc y : 1 s'affiche ok? mais rien n'empêche le processus y de continuer par la suite...il y a d'autres commutations aléatoires produites par l'ordonnanceur.

      Donc malheureusement je suis pas le seul à bloquer sur ce petit exercice...
      1. Contributeur
        $ cat foo.sh
        #! /bin/bash
        
        #set -xv
        
        for i in {1..10}
        do
                echo $1 : $i
                sleep 1
        done
        
        $ ./foo.sh x | tee -a log& ./foo.sh y | tee -a log
        x : 1
        [1] 27445
        y : 1
        x : 2
        y : 2
        x : 3
        y : 3
        x : 4
        y : 4
        x : 5
        y : 5
        x : 6
        y : 6
        x : 7
        y : 7
        x : 8
        y : 8
        x : 9
        y : 9
        x : 10
        y : 10
        [1]+  Done                    ./foo.sh x | tee -a log
        
        $ cat log
        x : 1
        y : 1
        x : 2
        y : 2
        x : 3
        y : 3
        x : 4
        y : 4
        x : 5
        y : 5
        x : 6
        y : 6
        x : 7
        y : 7
        x : 8
        y : 8
        x : 9
        y : 9
        x : 10
        y : 10
        $
        

        Désolé ;-))
    4. Contributeur
      Salut,

      $ cat foo.sh
      #! /bin/bash
      
      #set -xv
      
      for i in 1 2 3
      do
              echo $1 : $i
              sleep 1
      done
      
      $ ./foo.sh x& ./foo.sh y
      x : 1
      [1] 25503
      y : 1
      x : 2
      y : 2
      x : 3
      y : 3
      [1]+  Done                    ./foo.sh x
      $


      A part le PID du job en arrière-plan qui vient s'intercaler, le résultat me semble bon, non ?
      1. Contributeur
        Pour un affichage plus conforme :

        $ ./foo.sh x | tee -a log& ./foo.sh y | tee -a log
        [1] 25765
        x : 1
        y : 1
        x : 2
        y : 2
        x : 3
        y : 3
        [1]+  Done                    ./foo.sh x | tee -a log
        
        $ cat log
        x : 1
        y : 1
        x : 2
        y : 2
        x : 3
        y : 3
        $

        ;-))
    5. sinon à tout hasard dans mon script de départ:
      for i in 1 2 3 
      do 
      
      if [ $1 = "x" ] 
      then 
       FILE=$1.sync 
       echo > $FILE 
      fi 
      
      while [ ! -f $FILE ] 
      do 
      sleep 1 
      done 
      
      echo $1 : $i 
      rm -f $FILE 
      done


      as tu une petite idée pour juste forcer le premier affichage à être x : 1 ?
      1. non ça ne marche pas, et cela me semble bien trop volumineux pour ce qui est demandé, je crois que je vais abandonné cet exercice tant pis, merci pour tes réflexions c'est sympa.
        1. Contributeur
          #! /bin/bash
          
          FILE=$1.sync 
          touch  $FILE
          # on attend un peu que tout les scripts créent leur fichier
          sleep 10
          
          #on compte le nombre de script
          nbs=$(ls *.sync|wc -l)
          
          for i in 1 2 3 
          do 
            echo $i> $FILE 
            echo $1 : $i 
            test=0
          #tant que tout les scripts n'ont pas atteints la valeur en cour
            while [ $test -lt $nbs ]
            do
                test=0
                for x in 'ls *.sync'
                do
                    if  [ $i <= 'cat $x' ]
          	  then
                       (( test++ )
                    fi
                done
                sleep 1
            done
          #mieu vaut ecraser que supprimer
          #rm -f $FILE
          done
          1. Contributeur
            là c'est pas de l'expertise en sh qu'il faut, c'est un bon algoritmhe.
            Je me suis d'ailleur rendu compte que mon algo été mauvais.
            Je regarde si un seul fichier vaut au moins $i, alors qu'il faudrait tout les regardé.
            C'est tout con, mais j'ai un peu de mal à l'écrire cet algo.
            1. non ya rien à faire, je me demande finalement si l'idée est bonne car rien n'empêche ton algorithme d'afficher:

              a: 1
              a: 2
              ...

              bref ça ne fonctionne tjs pas et on est plusieurs à y avoir réfléchi c'est incroyable qu'un exercice si court nous demande tant de reflexion ça doit pas etre sorcier pourtant....

              Personne connait un expert en sh? lol
              1. alors regarde j'ai modifié le script mais ça marche tjs pas et d'ailleurs je trouve bizarre qu'on doive vérifier que le fichier .sync existe puisque de toute manière il existe avant d'entrer dans la boucle si on met le rm -f $FILE à la fin, voici le script:

                #! /bin/bash
                
                for i in 1 2 3 
                do 
                
                  FILE=$1.sync 
                  echo $i> $FILE 
                  echo $1 : $i 
                  test=0
                  while [ $test ]
                  do
                     if [ -f $FILE ]
                     then
                      for x in 'ls *.sync'
                      do
                          if  [ $i <= 'cat $x' ]
                	  then
                             test=1
                          fi
                      done
                      else
                      sleep 1
                      fi
                  done
                rm -f $FILE
                done


                c'est cette ligne if [ $i <= 'cat $x' ] qui pose problème même avec ton script...
                1. Contributeur
                  essai if [ $i -le $(cat $x) ] ou alors carément : if (( $i <= $(cat $x) ))
              2. alors j'ai testé ton script et ton algorithme me semble pas mal, je voulais faire ça au début mais bon, ton script m'affiche:

                y : 1
                x : 1

                et ensuite un défilement du message ls: ne peut accéder *.sync: Aucun fichier ou dossier de ce type. impossible à arrêter meme avec un ctrl+c ...
                Même en déplaçant l'instruction rm -f $FILE après la boucle while ça ne change rien, bizarre.
                1. Contributeur
                  oui, je m'en doutais que ça ne fonctionnerai pas aussi simplement. Il faut rajouter un if avant le for, pour vérifier qu'il existe bien un fichier .sync. S'il n'y en a pas, tu peux alors bouclé.
                  Je pense aussi qu'il faut déplacer le rm à la fin.
                  Après ça devrais aller mieux.
              3. je vais tester, en fait l'exercice c'est ça, soit le script suivant:

                for i in 1 2 3
                do
                echo $1 : $i
                done

                modifier ce script pour que lors de la commande ./script.sh x & ./script.sh y & on ait un affichage alternatif parfait:

                x: 1
                y: 1
                x: 2
                y: 2
                x: 3
                y: 3
                1. Contributeur
                  Salut.
                  en fait, c'est assez simple en modifiant le script pour vérifier les pichier .sync
                  for i in 1 2 3 
                  do 
                  
                    FILE=$1.sync 
                    echo $i> $FILE 
                    
                    echo $1 : $i 
                    rm -f $FILE 
                    test=0
                    while $test
                    do :
                        for a in $(ls *.sync)
                        do :
                            if  [ $i <= $(cat $a) ]
                               test=1
                            fi
                        done
                        sleep 1
                    done
                  
                  done

                  Je n'ai pas testé, mais tu vois l'idée ? tu stockes dans le fichier le nombre courant, tant que ce nombre est supérieur à ceux des autres scripts, tu attends. ça garantie d'avoir 1 puis 2 puis 3.
                  si tu veux aficher x y z dans cet ordre, c'est faisable avec un classement par ordre alphabétique des fichier : tant qu'il y a une lettre avant le script en court ne rien faire.
                  Si l'affichage doit se faire selon l'ordre de lancement des scripts, je pense que c'est impossible à réaliser.
                  1. l'exercice est uniquement à faire en sh lol, j'ai tenté de poser des verrous avec lockfile mais ça n'a pas marché, si jamais un connaisseur du sh passe par là qu'il se manifeste car tout le monde se casse les dents dessus lol.
                    Merci Hxyp pour ces tentatives :-(
                    1. Je n'avais pas imaginé que c'était un exercice, heu sinon bah hm il faudrait voir au niveau des id car lorsqu'on démarre une list avec & il affiche le premier job [1] (on peut récupérer l'id courant avec $!) et l'id du dernier processus dans la list, j'ai testé en faisant :
                      #!/bin/bash
                      set -m
                      for i in 1 2 3
                      do
                          echo $1 : $i
                          suspend $!
                      done
                      

                      mais pas réussi à "resume" avec "bg %-" qui semble ne fonctionner qu'en mode interactif bien que j'ai utilisé "set -m" dans le script.
                      Faites le test, le script va suspendre chaque job (on peut les voir en utilisant la commande "jobs") puis les remettre en route en répétant la commande "bg %-" à la main c'est cette action que je ne sais pas comment la mettre dans le script.

                      quels liens la dessus :
                      https://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_09_03_02
                      http://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Job-Control
                      https://www.linuxtopia.org/online_books/advanced_bash_scripting_guide/x6632.html
                      https://ss64.com/bash/syntax-jobs.html
                    2. Contributeur
                      help suspend te dit que tu peux continuer un processus en envoyant le signal SIGCONT. Tu peux alors envoyer un signal avec "kill".
                      essai "kill -s SIGCONT %-"
                    3. Merci pour l'aide j'ai pu continué mais il y a un problème que je ne comprend pas sur mon script, le suspend créé un logout et donc ça ferme mon shell à chaque fois, et je ne peux pas voir le résultat !

                      #!/bin/bash
                      set -m
                      vid=""
                      sta=0
                      sto=0
                      
                      function get_list
                      {
                          get='ps | grep ${0##*/} | gawk '{ printf $1 " " }''
                      }
                      function v_id
                      {
                          get_list
                          #vid='echo $get | gawk -v var=$$ '{ for(j=1;j<NF+1;j++){ if($j == var) printf var } }''
                          vid='echo $get | gawk -v var=$$ -v stt=$sto '{ for(i=1;i<NF+1&&NF>1;i++){ if($i==var&&i!=NF&&i>1&&stt==1) printf $((i+1)); if(i==NF&&$i==var) printf $1 } }''
                          sta='echo $get | gawk -v var=$$ '{ for(i=1;i<NF+1;i++){ if($i==var&&i==NF) printf 1 } }''
                      }
                      for i in 1 2 3
                      do
                          v_id
                          echo $vid
                          if [ $sto ] && [ $sta ]
                          then
                              kill -s CONT $vid
                          fi
                          echo $1 : $i : $$
                          sto=1
                          suspend $!
                      done
                      #exit 0
                      

                      C'est un peu spaghetti, l'idée est de lister tout les id avec soit "ps" ou "jobs -l" et de récupérer la liste des id avec awk en fonction du nom du script "${0##*/} et d'alterner avec CONT et suspend sur la liste en fonction de leur position vu qu'ils se lancent en incrémentant le numéro de l'id c'est dans l'ordre mais je bug là... j'apprends tout juste le bash scripting (là en fait)
                  2. Re, je pense avoir trouvé un début de réponse ou peut-être la réponse ? en utilisant le C car on peut exécuter des commandes avec et gérer l'ordre. Alors voici un petit code en c :
                    en commentaire j'ai mis la référence du bout de code que j'ai pompé et modifié pour qu'on puisse faire une boucle en fonction des paramètres... j'explique un peu plus bas

                    //https://stackoverflow.com/questions/646241/c-run-a-system-command-and-get-output
                    #include <stdio.h>
                    #include <stdlib.h>
                    
                    int main(int argc,char *argv[])
                      {
                        FILE *fp;
                        char path[1035];
                        int i, a, x=atoi(argv[1]);
                        for(a=0;a<x;a++)
                          {
                            for(i=2;i<argc;i++)
                              {
                                fp=popen(argv[i],"r");
                                if(fp==NULL)
                                  {
                                    fprintf(stderr,"Erreur popen()\n");
                                    exit(1);
                                  }
                                while(fgets(path,sizeof(path),fp) != NULL)
                                  {
                                    printf("%s",path);
                                  }
                                pclose(fp);
                              }
                          }
                        return 0;
                      }
                    


                    Pour compiler tu peux faire :
                    gcc -Wall -o prog prog.c

                    Pour l'utiliser :
                    ./prog 3 "echo x" "echo y"

                    le premier paramètre le 3 pour l'exemple c'est le nombre de répétitions : les commandes qui suivent seront alors exécutées à la suite le nombre de fois indiqué dans le premier param!

                    le résultat :
                    x
                    y
                    x
                    y
                    x
                    y

                    Faut pas oublier de mettre les commandes entre des guillemets
                    1. salut HXYP, non j'avais déjà essayé de rajouter un sleep exactement au même endroit que toi mais ça ne fonctionne pas, en fait a fonctionne à 90% mais pas à 100% car rien n'empêche à un processus d'afficher 2 fois x :1 x : 2, essaye tu verras :-).
                      Donc le fait de rajouter un sleep à mon grand désarroi ne change rien...
                      • 1
                      • 2