Forum Programmation.autre Sed sur plusieurs lignes

Posté par . Licence CC by-sa
2
26
août
2014

Bonjour,

Utilisateur de sed depuis des lustres… il y a un truc que je n'arrive pas à faire… et qui me chicane vraiment. J'ai toujours contourné le problème avec du Perl, mais, cette fois, c'est pour de l'embarqué (en fait une raspberry pi que je ne contrôle pas) et je n'ai pas de Perl à disposition.

Le problème

J'ai de nombreux fichiers qui ont un nombre variable de lignes de la forme:

%!PostProc(Xhtml): 'bidule' 'capsule'

Mais il peut y avoir, avant, entre et après des lignes vides ou avec des espaces et/ou des tabulations.

Je ne contrôle pas la chose…
J'ai besoin d'introduire une ligne après toutes les lignes de commandes %!PostProc.*, mais avant la première ligne de texte.

Mais je n'arrive pas à le faire…

Dans ma logique, probablement foireuse, cela devrait faire l'affaire :

sed -e 'N;s/\(^%!.*\n\n*\)\([^%]\)/\1ma ligne à insérer\n\2/' src/index.t2t

Ha, pour corser le tout… il peut y avoir des fichiers qui n'ont pas du tout de ligne en %!… Et dans ce cas, ma ligne doit être introduite avant toutes les autres…

Sinon, je m'étais dit de simplement utiliser la commande i :

sed -e '/^[%$]/ima ligne\n;Q'

Le Q étant sensé faire arrêter le script dès que la commande à trouvé une ligne sur laquelle s'exécuter. Mais comment faire comprendre à sed qu'il s'agit de la commande Q (ou q) et non pas d'un simple caractère ????

J’ai essayé :

sed -e '/^[%$]/{ima ligne\n};Q'
sed -e '/^[%$]/i{ma ligne\n};Q'
sed -e '/^[%$]/"ima ligne\n";Q'

et plein d'autres, sans succès.

J'ai lu

J'ai passé des heures à chercher, à relire le man de sed et :
- http://linuxfr.org/forums/linux-general/posts/motif-multi-lignes-avec-sed
- https://linuxfr.org/forums/programmation-shell/posts/regex-de-recherche-de-lignes-coup%C3%A9es
- https://www.gentoo.org/doc/fr/articles/l-sed1.xml
- https://www.gentoo.org/doc/fr/articles/l-sed2.xml
- https://www.gentoo.org/doc/fr/articles/l-sed3.xml
- http://sed.sourceforge.net/sed1line_fr.html
- http://sed.sourceforge.net/sedfaq.html
- http://unix.stackexchange.com/questions/20322/replace-string-with-contents-of-a-file-using-sed
- http://austinmatzko.com/2008/04/26/sed-multi-line-search-and-replace/

Le dernier lien semble être le jackpot, mais je n'ai pas réussi à le faire fonctionner dans mon cas…

Petit problème, je suis une vraie bille en anglais… par contre si vous avez des liens en allemand ou en italien je les comprends sans problème.

Résolution temporaire

Le problème est momentanément réglé par se code dans un Makefile (c'est juste un extrait), ça fonctionne très bien, mais cela me colle la nausée à chaque fois que j'ouvre mon fichier…

SRC     =src/
OUT     =out/

${OUT}%.xhtml: ${SRC}%.t2t $(wildcard ${SRC}inc/template/*)
    @echo "" > $@.tmp
    @sed -n "/^%!.*/p" $< >> $@.tmp
    @echo -e "\n%!include: ''src/inc/template/header.xhtml''\n" >> $@.tmp
    @sed "/^%!.*/d" $< | sed '/./,$$!d' | sed -e :a -e '/^\n*$$/{$$d;N;ba' -e '}' >> $@.tmp
    @echo -e "\n\n%!include: ''src/inc/template/footer.xhtml''" >> $@.tmp
    @cat $@.tmp | txt2tags --no-rc -t xhtml --encoding=utf-8 --no-headers -q -i - -o $@
    @rm $@.tmp
    @sed -i "s/£MDATE£/$(shell date "+%A %d %B %Y à %H:%M:%S" --date=@`stat --printf=%Y $<`)/" $@

Un grand merci pour toute aide!!!

  • # syntaxe ? ou idée à tester.

    Posté par . Évalué à 4.

    Sinon, je m'étais dit de simplement utiliser la commande i :

    sed -e '/^[%$]/ima ligne\n;Q'

    Le Q étant sensé faire arrêter le script dès que la commande à trouvé une ligne sur laquelle s'exécuter. Mais comment faire comprendre à sed qu'il s'agit de la commande Q (ou q) et non pas d'un simple caractère ????

    en utilisant la syntaxe prevue pour.

    c'est soit

    sed -i -e '/^[%$]/ima ligne\n;\Q' monfichier

    que j'ai deja vu dans des scripts.

    soit

    sed -i -e 's/^[%$]/ima ligne\n;/' monfichier

    qui va peut-etre de le faire pour chaque ligne, mais une seule fois par ligne.

    sinon dans un cas comme le tiens, je me "simplifie" la vie en remettant mon fichier sur une seule ligne (en remplacant les retour à la ligne par une chaine de caractere que je connais)

    puis en faisant le rechercher/remplacer qui ne se fera forcement sur une seule ligne,
    puis en remplacant la chaine de caractere precedemment insérée pour remettre les retours à la ligne.

    • [^] # Re: syntaxe ? ou idée à tester.

      Posté par . Évalué à 2. Dernière modification le 27/08/14 à 10:22.

      Un grand merci !

      c'est soit
      sed -i -e '/^[%$]/ima ligne\n;\Q' monfichier

      Le problème c'est que, en tout cas chez moi, avec cette syntaxe le ";\Q" est écris en toute lettre à la fin de la ligne, et non interprété comme une commande sed.

      sinon dans un cas comme le tiens, je me "simplifie" la vie en remettant mon fichier sur une seule ligne (en remplacant les retour à la ligne par une chaine de caractere que je connais)

      puis en faisant le rechercher/remplacer qui ne se fera forcement sur une seule ligne,
      puis en remplacant la chaine de caractere precedemment insérée pour remettre les retours à la ligne.

      Ha ben oui… c'est vraiment le truc auquel je n'avais pas pensé! Solution intéressante qui me permettrait de tout remettre sur une seule ligne, sans fichier temporaire, et de tout passer par des tubes…

  • # le dernier lien, en allemand traduit automatiquement

    Posté par . Évalué à 2.

    • [^] # Re: le dernier lien, en allemand traduit automatiquement

      Posté par . Évalué à 1.

      Merci.

      Ne comprenant pas l'englais, je ne puis dire si la traduction est bonne… mais le texte n'est pas très clair, et la signification du code me reste un tantinet ardu… (_)

      • [^] # Re: le dernier lien, en allemand traduit automatiquement

        Posté par . Évalué à 4.

        j'ai pas tout lu, et je parles pas allemand, enfin trop peu pour verifier la traduction.

        en gros il fait de la "programmation" avec sed

        #!/bin/sh
        sed -n '
        # si c'est la premiere ligne, la copier dans le "hold buffer"
        1h
        # si ce n'est pas la premiere ligne, l'ajouter au "pattern buffer"
        1!H
        # quand on arrive à la derniere ligne alors ...
        $ {
                # copier depuis le "hold buffer" vers le "pattern buffer"
                g
                # faire le recherche/remplace
                s/<h2.*</h2>/No title here/g
                # afficher le resultat
                p
        }
        ' sample.php > sample-edited.php;
  • # Tout ce qu'il faut savoir sur sed

    Posté par . Évalué à 5.

    Dès que je me pose une question sur sed, je me replonge là dedans:
    http://www.grymoire.com/unix/sed.html

    essaye un traducteur automatique pour une version française:
    https://translate.google.com/translate?hl=en&ie=UTF8&prev=_t&sl=en&tl=fr&u=http://www.grymoire.com/unix/sed.html

    • [^] # Re: Tout ce qu'il faut savoir sur sed

      Posté par . Évalué à 1.

      Merci beaucoup,

      Je ne connaissait pas ce site. Sympa, et surtout assez complet! Par contre la traduction google fait mal aux yeux (^_^)

      Je vais me plonger dans la lecture.

  • # Changer d'outil ?

    Posté par . Évalué à 3.

    La complexité du parsing ne justifiait-il pas l'utilisation d'un outil plus puissant ? Je pense a flex, il doit y avoir moyen de s'en sortir en quelques lignes. Et niveau perf et empreinte mémoire ça sera imbattable.

    Please do not feed the trolls

    • [^] # Re: Changer d'outil ?

      Posté par . Évalué à 2.

      Merci

      Je ne connais pas du tout flex… je cours voir!

      Mais s'il faut l'installer cela risque d'être ardu, je n’ai pas la main sur la machine qui exécute le code.

      Cela m'intéresse quand même à titre perso.

  • # Ne pas tout faire sur une ligne

    Posté par (page perso) . Évalué à 3. Dernière modification le 27/08/14 à 11:09.

    Je dois dire que je n'ai jamais essayé d'écrire des commandes après un i\ sur une même ligne, je ne sais pas si c'est possible. Par contre, en faisant un petit script sed de quelques lignes tu n'as pas ce genre de problèmes à te demander s'il faut écrire Q ou \Q (d'ailleurs, Q n'est pas portable je pense, en tous cas mon sed n'a pas cette commande, je n'ai que q).

    Si j'ai bien compris, quelque chose comme ça ferait l'affaire :

    # on laisse telle quelles les lignes blanches                                                                                     
    /^[[:space:]]*$/n                                                                                                      
    # On ajoute des lignes où il faut                                                                                                
    /^[%$]/ {                                                                                                              
      p                                                                                                                    
      i\                                                                                                                   
    ma ligne à insérer                                                                                                     
      d                                                                                                                    
    }                                                                                                                      
    # On laisse tel quel le reste                                                                                          
    : text; n; b text;
    

    Edit : s'il n'y a aucune ligne qui commence par % ou $ ce script ne fait rien, et dans ce cas tu dois adapter un peu pour ce cas particulier.

    • [^] # Re: Ne pas tout faire sur une ligne

      Posté par (page perso) . Évalué à 3.

      Je peux plus modifier, mais je crois que j'ai compris de travers quand tu disais « après toutes les lignes » j'ai compris « après chaque ligne » (parce que c'est ce qu'avait l'air de faire un de tes premiers essais). Mais bon, l'idée reste que je pense que tu pourrais utiliser les labels pour résoudre ton problème avec un seul script sed.

      • [^] # Re: Ne pas tout faire sur une ligne

        Posté par . Évalué à 1.

        Merci !

        Voie très intéressante. Je m'y plonge pour faire des tests.

        • [^] # Re: Ne pas tout faire sur une ligne

          Posté par (page perso) . Évalué à 3. Dernière modification le 28/08/14 à 09:52.

          Je pense en fait que, pour que le script que j'ai donné marche comme tu voulais (j'ai regardé plus en détail ton Makefile), il suffisait de mettre le i\ après. En gros :

          # on passe sur les lignes blanches (nouveau cycle à chaque fois)
          /^[[:space:]]*$/{p;d;}
          # On passe sur les "%!" (nouveau cycle à chaque fois)
          /^%!/{p;d;}
          i\ 
          ma ligne à insérer
          # on boucle sur le reste (sans initier de nouveaux cycles)
          : loop; n; b loop; 
          

          (il fallait {p;d;} sur la première ligne et pas n, car n ne commence pas un nouveau cycle, et donc dans la version que j'ai donné avant il ne pouvait pas y avoir plusieurs lignes blanches à la suite)

  • # À tester…

    Posté par . Évalué à 2. Dernière modification le 27/08/14 à 21:58.

    Salut,

    N'ayant pas toutes les données de ton problème et notamment un exemple concret de fichier à traiter ainsi que la sortie désirée, voilà un petit bout de code en sed à tester.

        #n
        /^%!post.*/ !p
        /^%!post/ {
            h
            :z
            n
            /^[[:space:]]*$/ {
                H
                b z
            }
            x
            s/$/\nAAA/p
            g
            G
            D
        }

    Par contre, je ne pense pas que l'insertion en début de fichier de la ligne si aucun motif correspondant n'est trouvé soit possible en une seule opération avec ce même script. Il te faudra tester auparavant si le motif existe ou pas et lancer le script sed adéquat en fonction…

    Le script est à mettre dans un fichier (script.sed) et doit être appelé comme suit :

        sed -f script.sed fichier_à_traiter

    PS. Impossible de mettre le code en forme correctement ;-(
    Si un modo (ou autre) pouvait s'en occuper, je l'en remercie par avance

    • [^] # Re: À tester…

      Posté par . Évalué à 2.

      PS. Impossible de mettre le code en forme correctement ;-(
      Si un modo (ou autre) pouvait s'en occuper, je l'en remercie par avance

      1°) ouvrir ton paragraphe de code avec ```sh pour lui dire que ce sera du shell
      2°) placer ton code
      3°) fermer ton paragraphe de code avec ```

      • [^] # Re: À tester…

        Posté par . Évalué à 1.

        Merci beaucoup pour ces explications. Le problème étant que je me suis servi des boutons présents pour le code, mais la syntaxe (avec le bouton code) ne m’apparaît jamais dans le textarea ;-(

        Je ferai ça à la main désormais ;-)

    • [^] # Re: À tester…

      Posté par . Évalué à 1.

      Merci!

      Là aussi, je vais avoir du pain sur la planche pour tout comprendre… Je ferais des tests pour assimiler…

      • [^] # Re: À tester…

        Posté par . Évalué à 3. Dernière modification le 29/08/14 à 17:39.

        De rien.

        Voilà les explications, mais comme dit dans mon premier post, sans exemples précis et concret, ce n'est peut-être pas l'idéal ;-\

        #n
        On imprime que sur demande (flag "p")

        /^%!post.*/ !p
        Si le motif ne correspond pas, on imprime la ligne

        /^%!post/ {
        Si le motif correspond, on applique les commandes entre accolades

        h
        On copie le contenu de la mémoire principale dans la mémoire secondaire en écrasant ce qui s'y trouverait éventuellement

        :z
        On pose une étiquette

        n
        On vide le contenu de la mémoire principale en le remplaçant par la ligne suivante

        /^[[:space:]]*$/ {
        On teste le contenu pour voir s'il c'est soit une ligne vide, soit une ligne vide contenant d'éventuels espaces

        H
        Si c'est le cas, on ajoute le contenu de la mémoire principale à la mémoire secondaire

        b z
        Puis on se branche à l'étiquette et on recommence

        }
        Fin des commandes pour ce motif

        x
        Donc, si le motif précédent n'est pas trouvé, on échange le contenu des mémoires

        s/$/\nAAA/p
        On ajoute notre "ligne à insérer" à la fin du contenu de la mémoire principale (qui contient le motif recherché est ses éventuelles lignes vides ou remplies d'espace), et on l'imprime (flag "p")

        g
        On écrase le contenu de la mémoire principale par celui de la mémoire secondaire (qui contient la première ligne non vide qui suit le motif initial)

        G
        On ajoute à nouveau le contenu de la mémoire secondaire à la suite de lui-même. Petite explication supplémentaire pour ça à l'instruction suivante…

        D
        On efface le contenu de la mémoire principale du début de la ligne jusqu'au premier saut de ligne rencontré (\n), ce qui a pour effet de relancer le script sed depuis le début avec le contenu actuel de la mémoire principale. Sans cette petite pirouette (G puis D) le script serait relancé mais en passant à la ligne suivante, sans traiter la ligne actuelle.

        }
        Fin des commandes

        • [^] # Re: À tester…

          Posté par . Évalué à 2.

          • [^] # Re: À tester…

            Posté par . Évalué à 1.

            Ben quoi ? Je ne voulais pas de balises code pour mes explications, je voulais juste du gras (pour chaque ligne de code) et les explications en dessous.

            C'est de ma faute si certaines syntaxes passent mal ?

            Ou j'ai raté encore un truc ?

            • [^] # Re: À tester…

              Posté par . Évalué à 3. Dernière modification le 29/08/14 à 14:30.

              Oui c'est le signe ^ qui est interprété comme "exposant", donc la balise code tu peux pas y échapper, effectivement pour mettre du gras c'est mort, à moins d'une astuce, que je ne connais pas.

              1^2^3^4 donne :

              1234

              • [^] # Re: À tester…

                Posté par . Évalué à 1.

                effectivement pour mettre du gras c'est mort, à moins d'une astuce, que je ne connais pas.

                Alors on est deux ;-)

                Merci pour les explications sur l'exposant.

        • [^] # Re: À tester…

          Posté par . Évalué à 1.

          Un super merci !!!!

          Je fais des tests avec tes instructions. Elles vont surtout me permettre de comprendre ce que je fais…

Suivre le flux des commentaires

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