Partage
  • Partager sur Facebook
  • Partager sur Twitter

numéro de ligne variable et remplacement avec sed

Sujet résolu
    1 octobre 2017 à 23:22:30

    Bonjour à tous,

    Alors voilà, j'ai un soucis avec sed dans un script bash. Comment puis-je mettre un numéro de ligne en variable sur sed pour un remplacement.

    Voici ci-dessous ce que j'ai fait pour le moment :

    nlign=$(grep -nm1 -i todo: $1 | cut -d ':' -f1)
    
    [...]
    
    sed "${nlign}s/todo: /done: /g" $1

    Mais ça ne marche pas. La commande m'affiche (et remplace) dans l'ensemble du document et pas uniquement la ligne concernée.

    J'ai vérifié, la variable $nlign contient bien un simple numéro. J'ai essayé plusieurs syntaxes mais rien n'y fait. J'ai soit une erreur, soit ça ne fait pas ce que je demande.

    Une idée ?

    J'en profite pour anticiper sur une potentielle question à venir, mais comment faire pour que sed me modifie directement le fichier, et ne m'affiche rien à l'écran ?

    Merci d'avance !

    • Partager sur Facebook
    • Partager sur Twitter
      2 octobre 2017 à 0:09:57

      salut,

      c'est pas optimal comme manière de faire : trop de commandes !

      sed --in-place '/todo: /{s/todo: /done: /;q}' "$1"



      • Partager sur Facebook
      • Partager sur Twitter

      Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

        2 octobre 2017 à 13:47:31

        Bonjour,

        Merci pour ce retour ; cependant, je ne vois pas où intervient la variable contenant le numéro de ligne dans ton exemple... A savoir, je n'ai mis qu'une partie de mon script pour exposer le problème, mais en réalité, dans mon projet, une même ligne est modifiée plusieurs fois de suite, d'où l’intérêt de conserver le numéro de cette dernière.

        Je ne l'ai pas mis, mais la ligne passe de "todo" à "runing" puis à "done". Ainsi, la ligne correspondant à la première occurrence de "todo", une fois modifiée, n'est pas forcément la même que la première occurrence de "running".

        Il m'est donc nécessaire, à moins que je modifie un peu le processus que j'avais en tête, de pouvoir donner le numéro de ligne en variable. C'est la seule chose qui me permet d'identifier facilement quoi modifier dans mon fichier.

        Je n'ai pas les éléments sous les yeux dans l’immédiat, mais si besoin, je pourrais peut-être poster le script complet ce soir pour que ce soit plus clair.

        Merci !

        • Partager sur Facebook
        • Partager sur Twitter
          2 octobre 2017 à 14:01:46

          donne également un échantillon représentatif du fichier à traiter, et les modifications attendues, ainsi que les éventuels messages d'erreurs complets.
          • Partager sur Facebook
          • Partager sur Twitter

          Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

            2 octobre 2017 à 18:52:53

            Voila une manip qui marche :

            Fichier de test :

            aaaaaaaaaaaa
            bbbbbbbbbbbbb
            cccc
            dddddddd
            eeeeee
            abcdeabcdeabcdeabcde
            


            On dit que le script doit remplacer 'a' par "Z" uniquement dans la 1ère ligne :

            Script :

            contenu_fichier=`cat test.txt`
            numero=1
            # ... code ...
            
            echo "$contenu_fichier" | sed "$numero""s/a/Z/g" 
            

            Résultat :

            ZZZZZZZZZZZZ
            bbbbbbbbbbbbb
            cccc
            dddddddd
            eeeeee
            abcdeabcdeabcdeabcde

            'a' n'est remplacé par 'Z' que dans la 1ère ligne (et pas dans la dernière où il est aussi présent).

            CQFD  

            • Partager sur Facebook
            • Partager sur Twitter
              2 octobre 2017 à 19:06:34

              Ok alors voilà. Le fichier à traiter est du format suivant : 

              #ToDo List#
              
              #status: task
              
              todo: TacheA
              todo: TacheB
              todo: TacheC
              todo: TacheD
              todo: TacheE
              todo: TacheF
              etc...

              Le fichier est présent dans un dossier partager sur le réseau, accessible par plusieurs machines (entre 1 et 4). Toutes les tâches doivent être réalisées une seule fois, peu importe la machine qui la réalise, et peu importe l'ordre. Ainsi, lorsqu'une machine commence une tâche, elle change le flag "todo" en "computing" et une fois la tâche finie, elle passe le flag en "done". Alors, elle cherche l'opération suivante comportant le flag "todo" et recommence tant qu'il existe des flags "todo". Chaque machine sur le réseau agit de la sorte.

              Ainsi, en cours de fonctionnement, il peut y avoir plusieurs lignes portant le flag "todo", "computing" et "done".

              Actuellement, voici le script que chaque machine devra exécuter :

              #!/bin/bash
              
              #script à run en SUDO...
              
              #Ce script permet de lancer récursivement les tâches présentes dans la TaskList (le $1)
              
              
              todo=$(grep -m1 -i "todo: " $1 | sed 's/todo: //g')     
              while [ "$todo" != "" ]
              do
                  nlign=$(grep -nm1 -i todo: $1 | cut -d ':' -f1)
              
                  sed "${nlign}s/todo: /computing: /g" $1
              
                  exec $todo
              
                  sed "${nlign}s/computing: /done: /g" $1
              
                  todo=$(grep -m1 -i "todo: " $1 | sed 's/todo: //g')
              
              done
              
              echo "No more tasks to do..."
              
              exit 0

              Mais j'ai des bugs avec le sed.... J'ai bien entendu fait l'essai sur une seule ligne en particulier (sans boucle et avec numéro de ligne fixe), ça marche sans soucis.

              L'erreur que j'ai est simplement que je passe en boucle infinie car pas de modification des flags (je ne fais qu'afficher l'ensemble du fichier texte).

              Voilà, ce n'est très certainement pas la meilleure méthode (pas la plus performante en tout cas) pour faire du calcul partagé, mais si je peux éviter de me faire le truc en C, qui serait trop spécifique, c'est cool.

              -
              Edité par BioKore 2 octobre 2017 à 19:09:30

              • Partager sur Facebook
              • Partager sur Twitter
                2 octobre 2017 à 21:36:05

                comme ça ?

                while true
                do
                   nbL=$(awk '/^todo:/{print NR; exit}' taskList)
                   ((nbL)) || break
                   sed -i $nbL's/todo/running/' taskList
                   echo "traitement ligne #$nbL en cours"
                   if commande_de_traitement
                   then
                      sed -i $nbL's/running/done/' taskList
                   else
                      sed -i $nbL's/running/failed/' taskList
                   fi
                done

                mais je ne garantis pas qu'il n'y aura pas de "chevauchements" dûs entre autre à la disponibilité des mises à jour du fichier par le réseau, du temps de traitement du fichier...

                -
                Edité par dantonq 2 octobre 2017 à 21:45:11

                • Partager sur Facebook
                • Partager sur Twitter

                Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

                  3 octobre 2017 à 9:39:08

                  Hmmm... Bon, je viens de faire plusieurs essais.

                  Le script suivant marche TANT QUE la commande exec est en fait un simple echo :

                  Ceci marche :

                  #!/bin/bash
                  
                  #script à run en SUDO...
                  
                  #Ce script permet de lancer récursivement les tâches présentes dans la TaskList.lst
                  
                  todo=''
                  nlign=''
                  todo=$(grep -m1 "todo: " $1 | sed 's/todo: //g')     
                  while [ true ]  # "$todo" != ""
                  do
                      nlign=$(grep -nm1 todo: $1 | cut -d ':' -f1)
                  
                      todo=$(grep -m1 "todo: " $1 | sed 's/todo: //g')
                  
                      sed --in-place "$nlign""s/todo: /computing: /g" "$1"
                  
                      echo $todo
                      #exec $todo
                  
                      sed --in-place "$nlign""s/computing: /done: /g" "$1"
                  
                      todo=$(grep -m1 "todo: " $1 | sed 's/todo: //g')
                  
                  done
                  
                  echo "No more tasks to do..."
                  


                  Si on dé commente la ligne avec le exec, Alors, il exécute bien la première commande, et s’arrête aussi-tôt (retour au prompt).

                  J'ai testé avec ce script, le résultat est exactement le même (ok en echo, juste la première fonction si exec) :

                  #!/bin/bash
                  
                  while true
                  do
                      nbL=$(awk '/^todo:/{print NR; exit}' $1)
                      ((nbL)) || break
                      todo=$(grep -m1 "todo: " $1 | sed 's/todo: //g')
                  
                      sed -i $nbL's/todo/running/' $1
                  
                      echo "traitement ligne #$nbL en cours"
                      if exec $todo
                      then
                          sed -i $nbL's/running/done/' $1
                      else
                          sed -i $nbL's/running/failed/' $1
                      fi
                  done

                  Pourtant, dans les deux cas, nous ne sommes pas censés sortir de la boucle avant la dernière commande, qui est un "exit 0" (ou tant qu'il y a des "todo"). J'imagine que j'ai zappé un truc tout bête.... 

                  Quoi qu'il en soit, merci bien !

                  -
                  Edité par BioKore 3 octobre 2017 à 19:11:04

                  • Partager sur Facebook
                  • Partager sur Twitter
                    4 octobre 2017 à 7:36:45

                    C'est normal que le script s'arrête, car la fonction exec remplace tout le code par ce qui est appelé par la fonction. Donc en clair, une fois que l'instruction exec est lue et interprêtée, bash considère que le script tout entier n'est plus que cette ligne de code.
                    Extrait de la page de man de exec (man 3 exec) :
                    The  exec()  family  of functions replaces the current process image with a new process image.
                    C'est d'ailleurs pour ça que dans certains scripts de démarrage (.xinitrc), on demande d'écrire "exec <environnement_a_lancer>" tout à la fin du code, car de toute façon le reste ne s'exécutera jamais, même au retour de la fonction.
                    Pour exécuter le bout de code, essaie de le mettre entre `` (AltGr + 7)
                    • Partager sur Facebook
                    • Partager sur Twitter
                      4 octobre 2017 à 8:10:22

                      Ah super, j'étais persuadé que c'était un truc du genre.... Mais j'ai pas eu le réflexe du man.

                      Comme tu l'as remarqué, dans mon script, la fonction est encapsulée dans une variable ; on verra si les `` fonctionnent. En tout cas, je sais où chercher du coup.

                      En tout cas, je teste dès ce soir ; merci bien !

                      • Partager sur Facebook
                      • Partager sur Twitter
                        4 octobre 2017 à 15:13:27

                        read nbL todo < <(awk '/^todo:/{$1="";print NR,$0; exit}' $1)
                        ((nbL)) || break
                        echo "traitement ligne #$nbL en cours"
                        if $todo; then #...

                        normalement, pas besoin de Substitution de commande (``, ou $()).

                        -
                        Edité par dantonq 4 octobre 2017 à 15:14:46

                        • Partager sur Facebook
                        • Partager sur Twitter

                        Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

                          5 octobre 2017 à 21:12:29

                          Bonjour,

                          Effectivement, je ne sais pourquoi, je me suis entêté à ajouter un exec inutile... Malgré ça, le code continue son chemin, même si la fonction n'est pas terminée. Dans mon cas, le dd ne dure même pas 2*10^-4 secondes (et ne copie donc rien). Je pense que le "if" teste le lancement de la commande, mais pas son message de sortie.

                          Il faudrait que le script laisse la commande tourner tant qu'elle n'est pas finie....

                          On y est presque ! Mais j’avoue que contrairement à e que je pensais, mes compétences en Bash sont finalement assez minces pour ce genre de projet...

                          Je vous remercie donc beaucoup pour l'aide apportée !

                          • Partager sur Facebook
                          • Partager sur Twitter
                            5 octobre 2017 à 21:20:41

                            peux-tu nous fournir une commande qui figure sur une ligne du fichier "todoList" ?

                            normalement, if vérifie le code de retour de la commande qui y est exécutée (c'est à ça qu'il sert !), et attend la fin de cette commande pour exécuter la suite des instructions then ou else.

                            • Partager sur Facebook
                            • Partager sur Twitter

                            Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

                              8 octobre 2017 à 19:48:46

                              En ce qui concerne le if, c'est aussi ce que je pensais, mais dans mon cas, cela ne fonctionne pas.

                              Voici le script :

                              #!/bin/bash
                              
                              while true
                              do
                                  nbL=$(awk '/^todo:/{print NR; exit}' $1)
                                  ((nbL)) || break
                                  todo=$(grep -m1 "todo: " $1 | sed 's/todo: //g')
                              
                                  sed -i $nbL's/todo/running/' $1
                              
                                  echo "traitement ligne #$nbL en cours"
                                  if $todo
                                  then
                                      sed -i $nbL's/running/done/' $1
                                  else
                                      sed -i $nbL's/running/failed/' $1
                                  fi
                              done

                              Pour l'exemple, voici la liste initiale :

                              todo: touch tasktest.tst
                              todo: ls -lArth >> tasktest.tst
                              todo: echo "plop" >> tasktest.tst
                              todo: dd if=/dev/null of=atsk.tst bs=1024 count=2048
                              todo: dd if=/dev/null of=atsk.tst bs=1024 count=1024
                              todo: rm -f atsk.tst
                              todo: exit 0

                              *le exit 0 est plus une sécurité pour le moment... Histoire d'être sure de sortir du script...

                              Et le résultat :

                              ./newTM.sh TaskList.lst 
                              traitement ligne #5 en cours
                              traitement ligne #6 en cours
                              ls: impossible d'accéder à '>>': Aucun fichier ou dossier de ce type
                              -rw-r--r-- 1 biokore biokore 0 oct.   8 19:38 tasktest.tst
                              traitement ligne #7 en cours
                              "plop" >> tasktest.tst
                              traitement ligne #8 en cours
                              0+0 enregistrements lus
                              0+0 enregistrements écrits
                              0 bytes copied, 0,00015342 s, 0,0 kB/s
                              traitement ligne #9 en cours
                              0+0 enregistrements lus
                              0+0 enregistrements écrits
                              0 bytes copied, 0,000145378 s, 0,0 kB/s
                              traitement ligne #10 en cours
                              traitement ligne #11 en cours
                              

                              et enfin, la liste après le passage du script :

                              done: touch tasktest.tst
                              failed: ls -lArth >> tasktest.tst
                              done: echo "plop" >> tasktest.tst
                              done: dd if=/dev/null of=atsk.tst bs=1024 count=2048
                              done: dd if=/dev/null of=atsk.tst bs=1024 count=1024
                              done: rm -f atsk.tst
                              running: exit 0

                              Voilà. A savoir, bien entendu, ces commandes ne seront pas les définitives, il s'agit simplement d'un test... Au final, il s'agira plutôt d'une centaine de commandes plus longues à traiter, sans nécessiter l'usage du disque ; mais tant qu'a faire, j'aimerais pouvoir le faire fonctionner avec tout type de commande. 

                              Merci encore !

                              ps : petite précision, le fameux fichier "tasktest.tst" est bien créé, mais rien n'est écrit dedans.

                              -
                              Edité par BioKore 8 octobre 2017 à 19:54:55

                              • Partager sur Facebook
                              • Partager sur Twitter
                                9 octobre 2017 à 0:20:33

                                je pense que le fichier existait avant l'exécution de la commande, car ls le liste.

                                il semble que le script fonctionne correctement : le ls échoue, et le exit fait s'achever le script.
                                par contre, il sort avant que la dernière commande de modification du fichier soit exécutée ! mais c'est normal.

                                il faut donc exécuter les arguments comme s'il étaient une commande du shell avec eval (voir help eval).

                                if eval $todo; then ... fi



                                • Partager sur Facebook
                                • Partager sur Twitter

                                Validez la réponse utile « Un problème clairement exposé est à moitié résolu. » Pas de MP technique

                                  14 octobre 2017 à 11:23:24

                                  Impeccable ! Avec "eval", tout passe exactement comme je le souhaitais.

                                  Sujet résolu !

                                  Merci à vous pour votre aide !

                                  • Partager sur Facebook
                                  • Partager sur Twitter

                                  numéro de ligne variable et remplacement avec sed

                                  × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                  × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                  • Editeur
                                  • Markdown