HervéRenault.fr

aide-mémoire à propos d'une ligne de commande

Ajouter une balise avec sed

J'ai eu besoin d'ajouter une balise <div> autour d'un petit bloc de lignes dans une floppée de fichiers HTML, et j'ai mis du temps à retrouver comment faire ça avec la commande sed, alors je le note ici pour ne plus oublier. OK, j'avoue, modifier du HTML ligne à ligne, c'est mal : XSLT a été inventé pour manipuler du XML et du HTML. Il y a aussi le DOM dans de nombreux langages de programmation (par exemple PHP). Mais dans un cas simple comme celui que j'avais, sed convenait, et ça m'amuse d'utiliser ce logiciel minimaliste. Enfin, c'est rapide quand on n'a pas oublié comment faire avec sed…

J'avais donc des blocs de ce style :

    <p>bidule</p>
    <ul>
        <li>foo</li>
        <li>baaaar</li>
    </ul>

Que je voulais transformer comme ça :

    <div>
        <p>bidule</p>
        <ul>
            <li>foo</li>
            <li>baaaar</li>
        </ul>
    </div>

Sed permet d'imbriquer des commandes, c'est ça que j'avais oublié. Il suffit donc de dire :

Ce qui s'écrit :

sed '/<p>bidule/,/<\/ul>/ { s/<p>bidule/<div>\n&/; s#</ul>#&\n</div># }'

(avec utilisation de # dans la deuxième substitution pour éviter d'avoir à backslasher le slash de </ul>)
(sur une seule ligne parce que j'aime bien les one-liners)

Il manque l'indentation. Pour ça, j'ajoute 4 espaces à chaque début de ligne avant de faire la substitution, puis j'incorpore ces 4 espaces dans la première substitution :

sed '/<p>bidule/,/<\/ul>/ { s/^/    /; s/    <p>bidule/<div>\n&/; s#</ul>#&\n</div># }'

Il ne me reste plus qu'à traiter tous les fichier concernés avec un find ou un grep, comme ceci :

for fichier in $( grep -lr '<p>bidule' ) ; do sed -i 'la commande sed qui va bien' "$fichier" ; done

Ça se complique…

Une autre fois, j'ai eu besoin d'ajouter une balise <ul> autour d'un lot de <li class="machin">. C'est plus compliqué parce qu'il est impossible de repérer le premier li ou le dernier li avec des adresses comme /premier/,/dernier/. Ils sont tous pareils. La seule chose que je sais, c'est qu'ils ont cet attribut de classe.

J'avais donc des blocs de ce style :

<ul>
    <li>foo</li>
    <li class="machin">bar</li>
    <li class="machin">truc</li>
    <li class="machin">bidule</li>
    <li>chose</li>
</ul>

Que je voulais transformer comme ça :

<ul>
    <li>foo</li>
    <ul>
        <li class="machin">bar</li>
        <li class="machin">truc</li>
        <li class="machin">bidule</li>
    </ul>
    <li>chose</li>
</ul>

Je vais devoir demander à sed d'ajouter <ul> devant la première occurence de <li class="machin">, ce qui s'écrit (en simplifiant un peu) avec cette substitution : 0,/.*class="machin".*/s//<ul>\n&/
0 pour le cas où on aurait une occurence sur la première ligne du fichier. Aucune chance que ça arrive ici, mais il faut retenir le principe.

Je vais devoir ensuite demander à sed de passer toutes les occurences de <li class="machin"> avec un instruction de branchement sur une étiquette, que je vais nommer A en majuscule pour qu'elle soit bien visible sur la ligne de commande : /class="machin"/{:A;n;/class="machin"/bA
La commande n fait lire la ligne suivante, et si elle matche encore ce motif, on se branche sur l'étiquette A.

Et enfin, quand on arrive sur une ligne qui ne matche pas ce motif, on demande à sed d'insérer </ul>. On peut le faire avec l'instruction i suivie du texte, suivi d'un retour chariot. Mais ce retour chariot, je ne trouve pas ça pratique sur la ligne de commande, donc j'utilise l'option -e de sed qui permet de briser une suite de commande et permet de repérer la fin du texte inséré par la commande i comme ceci : sed -e '/class="machin"/{:A;n;/class="machin"/bA;i</ul>' -e '}'

Voici donc le script sed complet, sur une ligne :

sed -e '0,/.*class="machin".*/s//<ul>\n&/; /class="machin"/{:A;n;/class="machin"/bA;i</ul>' -e '}'

Si je veux parfaire, j'ajoute l'indentation (je mets les espaces sur fond vert pour les voir) :

sed -e '0,/.*class="machin".*/s//    <ul>\n &/; /class="machin"/{:A;n;/class="machin"/{s/^/    /;bA};i\    </ul>' -e '}'
Note : obligé d'ajouter un backslash entre la commande i et le texte sinon sed ignore les espaces.