Initiation à sed

Cet article présente l'éditeur de flux sed, l'un des utilitaires les plus répandus du monde Unix, en commençant par les commandes de base fréquemment utilisées, pour aller jusqu'aux commandes les plus évoluées et souvent méconnues.

Sed, rendu célèbre par sa fonction de remplacement des groupes de caractères correspondant à une expression rationnelle par une chaîne de caractères constante ou non, offre de nombreuses autres possibilités.

Nous commencerons par présenter la notion d'éditeur de flux et l'utilisation basique de sed, puis les commandes plus évoluées et moins connues de ce mini langage. Enfin, nous évoquerons l'utilisation avancée de sed, et les problèmes de portabilité qui peuvent être rencontrés au cours de l'écriture de scripts sed.

Premiers pas avec sed

Notion d'éditeur de flux

A l'instar de l'exécution d'un programme awk, celle d'un script sed est avant tout ordonnée par les données fournies au script. Bien que ce fonctionnement puisse être altéré par différentes commandes, le principe de base est que chaque ligne de données est successivement traitée par chacune des commandes du script une fois et une seule.

Ainsi, s'il n'est pas impossible de réaliser des boucles en sed, ce n'est vraiment pas le but de cet utilitaire, qui ne met aucune des instructions de contrôle classiques (while, for, etc.) à la disposition du programmeur. En outre, aucune instruction de calcul, c'est-à-dire d'addition, soustraction, masquage binaire ou décalage de bits n'est prévue.

Ce mode de traitement, s'il n'a pas la souplesse de langages de programmation complets comme perl ou plus simples, comme awk, offre néanmoins suffisament de possibilités pour qu'un serveur HTTP ait été écrit en sed. Au rang des autres applications exotiques réécrites en sed, on trouve également la calculatrice dc et plusieurs bots irc.

Ces exercices de style poussent naturellement l'éditeur dans ses derniers retranchements, mais montrent sa puissance. En outre, la simplicité du fonctionnement associé à la facilité, pour un ordinateur, d'analyser les instructions sed mènent à des performances exceptionnelles pour un langage interprété.

Premiers exemples

Une manière simple de lancer sed est de lui fournir le script de commandes à exécuter en premier argument, suivi d'une liste de fichiers où lire successivement les données à traiter (si aucun fichier n'est spécifié, les données seront lues sur l'entrée standard). Ainsi, pour exécuter le célèbre script s/foo/bar/g, qui a pour effet de remplacer toute occurrence de foo par bar, sur le périphérique d'entrée standard, il suffit d'utiliser la commande sed s/foo/bar/ :

$ sed s/foo/bar/g

coin
coin
foobar
barbar

Dans cet exemple, après avoir tapé la commande, la ligne coin est saisie par l'utilisateur. Dans son fonctionnement par défaut, sed affiche chaque ligne traitée sur la sortie standard (cet affichage peut être désactivé à l'aide de l'option -n de la ligne de commande), et comme la ligne coin n'est pas affectée par la commande s/foo/bar/g, il affiche simplement la ligne elle-même.

La seconde ligne saisie par l'utilisateur, foobar, est transformée par la commande s/foo/bar/g, sed affiche donc le résultat du traitement de la commande, barbar.

Un second exemple simple est une programmation de la commande tr à l'aide de sed, c'est-à-dire l'écriture d'un script sed qui remplace, par exemple, tous les caractères a par b, et tous les caractères c par d :

$ sed y/ac/bd/

foobar
foobbr

Enfin, dans un script sed, plusieurs commandes peuvent être enchaînées, à condition d'être séparées par des points virgule ou des retours à la ligne :

$ sed 's/foo/bar/; y/ac/bd/'

coin
doin
foobar
bbfbbr

Sur cet exemple, le script sed s/foo/bar/; y/ac/bd/ est protégé par des simples quotes ' pour que le point virgule ne soit pas interprété par le shell. La saisie de la ligne coin par l'utilisateur n'est pas affectée par la première commande du script, s/foo/bar, mais un d est substitué au c par la seconde commande.

Ensuite, la saisie de foobar est, comme précédemment, transformée en barbar par la première commande, puis des caractères b sont substitués aux a par la seconde commande.

Syntaxe et principales commandes d'un script sed

Un script sed est une succession de commandes éventuellement précédées d'une adresse et éventuellement suivies de paramètres propres à la commande, séparées par des retours à la ligne ou des points virgule.

Ces commandes sont le plus souvent constituées d'un caractère unique, et une adresse n'est autre qu'un moyen de préciser à quelle(s) ligne(s) des données la commande s'applique (par exemple toutes les lignes contenant une certaine expression rationnelle, ou toutes les lignes entre la douzième et la quarante-deuxième). Certaines commandes ne peuvent être précédées d'une adresse, lorsque cette précision n'aurait aucun sens.

Principales commandes sed

Voici une liste des commandes les plus utilisées dans les scripts sed :

  • s/expression1/expression2/indicateurs : remplace toute chaîne correspondant à l'expression rationnelle (voire man egrep pour plus de détails sur les expressions rationnelles) expression1 par expression2. Dans expression2, peuvent apparaître les symboles \1, \2, ... \9 pour utiliser une chaîne apparaissant dans les données traitées et correspondant à la nième sous-expression de expression1 apparaissant entre \(et \). Ainsi, pour remplacer tout nombre apparaissant sur l'entrée standard par son opposé (en supposant qu'il n'y a que des nombres entiers positifs), la commande suivante sera suffisante :

    $ sed 's/\([0-9]\+\)/-\1/g'
    

    En outre, le caractère & dans expression2, non précédé d'un antislash \, est remplacé par la totalité de la chaîne de données correspondant à expression1. Plusieurs indicateurs peuvent modifier le fonctionnement de la commande s :

  • l'indicateur le plus souvent rencontré est le bien connu g, qui précise que chaque occurrence d'une chaîne correspondant à expression1 doit être remplacée; en son absence, seule le première occurrence est remplacée.
  • l'indicateur p entraîne l'affichage sur la sortie standard du résultat de la substitution si une substitution a eu lieu.
  • l'indicateur w suivi d'un caractère d'espacement et d'un nom de fichier a les mêmes effets que l'indicateur p, à la différence près que l'affichage n'a pas lieu sur la sortie standard, mais qu'elle est écrite dans le fichier spécifié.
  • si un nombre n est utilisé comme indicateur, seule la nième occurrence de l'expression rationnelle est remplacée.
  • l'indicateur I cause l'évaluation sans respect de la casse de expression1.

    Les caractères / utilisés pour séparer les expressions et les indicateurs peuvent être remplacés par n'importe quel caractère, laissé au libre choix du programmeur. D'autre part, le caractère utilisé pour marquer la séparation peut être précédé d'un antislash \ dans les différentes parties de la commande pour lui retirer sa signification spéciale.

  • q : cette commande cause la fin du script sed, et l'affichage de la dernière ligne traitée si l'option -n n'a pas été donnée en ligne de commande.
  • d : entraîne un retour immédiat au début du script sed, précédé de la lecture d'une nouvelle ligne de données.
  • p : affiche la ligne en cours de traitement.
  • n : affiche la ligne en cours de traitement si et seulement si l'option -n n'a pas été donnée en ligne de commande. Dans tous les cas, lit la ligne de données suivante, et poursuit le traitement à l'aide de cette ligne. Si plus aucune donnée n'est disponible, met fin au programme.

  • y/source/destination/ : remplace, dans la ligne en cours de traitement, tout caractère apparaissant dans source par le caractère ayant la même position dans destination (source et destination doivent être deux chaînes de même longueur).

  • = : affiche le numéro de la ligne en cours de traitement sur la sortie standard.
  • r nom de fichier : mémorise le contenu du fichier indiqué. A la prochaine lecture d'une ligne de données, ou à la fin du script, ce contenu sera affiché sur le périphérique de sortie standard.
  • w nom de fichier : imprime le contenu de la ligne en cours de traitement dans le fichier spécifié. Une petite précision doit être donnée concernant les écritures de sed, qu'il s'agisse de la commande w ou de l'indicateur w de la commande s : les fichiers ouverts en écriture par sed sont tronqués s'ils existaient au moment de l'ouverture, et créés dans le cas contraire.

    D'autre part, ils restent ouverts jusqu'à la fin du script, des écritures successives sont donc systématiquement ajoutées à la fin du fichier, et ne détruisent pas les résultats des écritures précédentes. Enfin, une ligne commençant par un signe # est un commentaire, le reste de la ligne est ignoré.

    Adresses

    La plupart des commandes peuvent être précédées d'une adresse, dont le rôle est d'indiquer à quelles lignes des données la commande concernée s'applique. Les différents formats d'adresses utilisables sont les suivants :

  • un nombre n précise que seule la nième ligne de données est concernée par la commande (les lignes de données sont numérotées à partir de 1).
  • $ correspond à la dernière ligne du dernier fichier de données.
  • /expression/ élimine, pour la commande qui la suit, toutes les lignes de données ne contenant aucune chaîne de caractères correspondant à l'expression rationnelle précisée, ce mode de sélection d'adresses peut être suivi d'un I pour indiquer une correspondance non sensible à la casse.
  • \%expression% a le même effet que le format d'adresse précédent, mais offre la possibilité d'encadrer l'expression rationnelle à l'aide de caractères différents du slash / (n'importe quel caractère peut être utilisé en lieu et place des signes %), ce mode de sélection d'adresses peut être suivi d'un I pour indiquer une correspondance non sensible à la casse.
  • adresse1, adresse2 sélectionne toutes les lignes à partir de la première sélectionnée par adresse1 et jusqu'à la première sélectionnée par adresse2, inclusivement; cependant, la recherche de adresse2 ne commencera qu'a la ligne suivant adresse1 si adresse2 est une expression rationnelle.
  • adresse! élimine toutes les lignes qui auraient été sélectionnées par adresse, et sélectionne les autres.
  • nombre1~nombre2 sélectionne toutes les lignes dont le numéro est égal à nombre1 plus un nombre entier (éventuellement nul) de fois nombre2.

    Enfin, plusieurs commandes peuvent être regroupées entre accolades, auquel cas elles partageront le même sélecteur d'adresses (l'accolade fermante doit être précédée d'un retour à la ligne ou d'un point virgule). Ceci est utile si de nombreuses commandes doivent s'appliquer aux mêmes lignes.

    Labels

    Une autre caractéristique intéressante de sed, permettant par exemple de réaliser des boucles et des tests, est l'utilisation de labels. Un label peut être défini à l'aide de la syntaxe :

    :label
    

    Deux commandes mettent une telle déclaration à profit :

  • la commande b label cause un saut immédiat à la position du script où label est défini (si label est omis, sed saute au début du script, affiche la ligne en cours de traitement si l'option -n n'a pas été donnée, et lit une nouvelle ligne de données).
  • la commande t label cause un saut à la position du script où label est défini à condition qu'une commande s au minimum ait causé une substitution depuis la lecture de la dernière ligne ou le dernier saut dû à une commande t.

    A l'aide de labels, une ébauche de script de vérification de la validité d'une liste de noms DNS peut être très simplement écrite.

    Le script suivant vérifie que les noms DNS transmis sur l'entrée standard ne commencent pas par un chiffre ou un signe -, si une telle erreur apparaît, le nom invalide est alors affiché sur la sortie standard :

    $ sed -n 's/^\([-0-9]\)/invalide : \1/;t print;d; :print;p'
    
    valid-hostname
    1nvalid-hostname
    invalide: 1nvalid-hostname
    

    Lors de la saisie de la première ligne par l'utilisateur, comme valid-hostname ne commence ni par un chiffre, ni par un signe -, aucune substitution n'est réalisée par la commande s, le saut au niveau de la commande t n'a donc pas lieu, et la commande d, qui a pour effet de lire une nouvelle ligne de données et de retourner au début du script est exécutée.

    Lors de la saisie de la seconde ligne, étant donné que 1nvalid-hostname commence par un chiffre, la commande s remplace ce chiffre par "invalide : \1", c'est-à-dire par lui-même précédé de "invalide :", et la commande t cause un saut jusqu'au label print, la commande d n'est donc pas exécutée.

    Vient enfin la commande p qui cause l'affichage de la ligne en cours de traitement, suivie de la lecture d'une nouvelle ligne et d'un retour au début du script.

    Utilisation avancée de sed

    Réordonner les données

    Nous avons jusqu'à présent considéré les données ligne par ligne. Toutefois, sed les stocke en réalité dans un tampon dit pattern space. C'est dans ce tampon que les données sont lues, puis modifiées en place par les différentes commandes.

    Certaines d'entre elles permettent d'ailleurs d'étendre le tampon au-delà d'une ligne unique. En outre, s'il n'existe pas de variables à proprement parler dans le langage sed, le pattern space peut cependant être copié vers une zone de sauvegarde dite hold space. Il peut ensuite être restauré à l'aide d'une copie en sens inverse.

    Tout ce qui a été dit jusqu'à présent concernant les lignes de données concerne en réalité le pattern space. Ainsi, la commande s permet d'appliquer des expressons rationnelles multi lignes, où le retour à la ligne est représenté par la succession de caractères \n. Par exemple, afficher une ligne de données sur deux peut être réalisé de la manière suivante :

    $ sed 'N;s/^.*\n//'
    

    La commande N a pour effet d'ajouter au pattern space un retour à la ligne suivi de la prochaine ligne de données. La première ligne de données du pattern space est ensuite remplacée par une chaîne vide à l'aide de la commande s.

    Enfin, à la fin du script sed, le pattern space est affiché, puisque l'option -n n'a pas été donnée en ligne de commande, une nouvelle ligne de données est lue est devient le nouveau pattern space, et l'exécution recommence au début du script sed, avec cette nouvelle ligne.

    Commandes supplémentaires

    Quelques commandes restent à évoquer :

  • la commande l affiche le pattern space de manière non ambiguë : les caractères non imprimables sont remplacés par des antislashs \ suivis de la valeur octale du code ascii du caractère concerné, et les lignes sont terminées par des signes $ :

    $ printf '\x12\n' | sed -n l
    
    \022$
    

  • la commande D cause l'effacement de la première ligne du pattern space. Un retour au début du script sed est ensuite exécuté. Si le pattern space est vide, une nouvelle ligne de données est lue avant ce retour.
  • la commande P affiche la première ligne du pattern space.
  • la commande x échange les contenus du pattern space et du hold space.
  • la commande g remplace le contenu du pattern space par celui du hold space.
  • la commande G ajoute un caractère nouvelle ligne et le contenu du hold space au pattern space.
  • la commande h remplace le contenu du hold space par celui du pattern space.
  • la commande H ajoute un caractère nouvelle ligne et le contenu du pattern space au hold space.
  • la commande N ajoute un caractère nouvelle ligne et une nouvelle ligne de données au pattern space (s'il n'y a plus de données à lire, l'emploi de cette commande cause la fin du programme).
  • la commande a\, qui doit être suivie d'un retour à la ligne, puis d'un certain nombre de lignes terminées par un antislash \, à l'exception de la dernière d'entre elles, mémorise ces lignes; elles seront affichées lorsque la fin du script sera atteinte ou lorsque la prochaine ligne de données sera lue.
  • la commende i\, suivie d'un retour à la ligne et de lignes au même format que pour la commande a\, affiche immédiatement ces lignes.
  • la commande c\, est suivie, elle aussi, des mêmes arguments que la commande a; elle a pour effet d'effacer le pattern space et d'afficher les lignes passées en argument. Ensuite, un saut au début du script sed précédé de la lecture d'une nouvelle ligne de données a lieu.

    Invocation de sed

    L'emploi des dernières commandes indiquées ici peut être fastidieux lors de l'écriture de scripts sed en ligne de commande, comme cet article l'a proposé jusqu'à présent. Aussi, des méthodes plus pratiques de lancement de sed vont-elles être indiquées ici.

    Tout d'abord, le script peut être composé de plusieurs parties fournies séparément à sed, ainsi, les deux commandes

    $ sed 's/foo/bar/; s/titi/toto/'
    $ sed -e s/foo/bar -e s/titi/toto/
    

    sont-elles équivalentes. Une troisième manière de procéder est de stocker certaines parties du script dans des fichiers, dont les chemins sont donnés en argument de l'option -f de sed. Ainsi, cette troisième manière d'écrire les deux commandes précédentes est :

    $ echo s/foo/bar/ > /tmp/foobar
    $ sed -f /tmp/foobar -e s/titi/toto/
    

    Un point important est que, lorsque l'une des options -e et -f est donnée, sed considère tout argument n'étant pas une option comme un nom de fichier dans lequel des données devront être lues (ces fichiers sont traités dans leur ordre d'apparition sur la ligne de commande).

    Dans le cas contraire, le premier de ces arguments est considéré comme le script à exécuter.

    Enfin, il n'est pas nécessaire d'invoquer directement sed pour exécuter un script, un fichier peut être écrit et contenir une fois pour toutes la ligne de commande de sed. Il est pour cela nécessaire que la première ligne du fichier soit la suivante :

    #!/bin/sed -f
    

    Naturellement, /bin/sed doit être remplacé dans cette ligne par le chemin d'accès à votre interpréteur sed, si celui-ci n'est pas dans /bin.

    Ecriture de scripts sed portables

    Quelques points sont à prendre en compte lorsque des scripts sed sont prévus pour être interprétés par d'autres versions de l'utilitaire que celle du programmeur :

  • le mode de sélection d'adresses nombre1~nombre2 est une extension GNU, et ne doit pas être employé dans des scripts portables.
  • il en va de même pour l'indicateur I de la commande s et du mode de sélection d'adresses par expressions rationnelles.
  • certaines implémentations de sed limitent la longueur des lignes à 4 000 caractères, bien que la norme POSIX.2 requiert le support de lignes de 8 192 caractères et que l'implémentation GNU de sed supporte des lignes de longueur illimitée.
  • l'emploi de la commande p ou de l'indicateur p de la commande s peut, dans certains cas et sur certaines implémentations, y compris l'implémentation GNU de sed, provoquer un double affichage, lorsque l'option -n n'a pas été donnée à sed, les impressions explicites en l'absence de l'option -n doivent donc être évitées (ce point n'étant pas couvert par la norme POSIX.2, aucun des deux comportements ne peut être qualifé d'anormal).
  • si la norme POSIX.2 impose le support des commentaires, certaines implémentations persistent à ne les autoriser que sur la première ligne du script.

    Linux Magazine France n°41 - Juillet 2002