285 votes

Comment utiliser sed pour remplacer uniquement la première occurrence dans un fichier ?

Je voudrais mettre à jour un grand nombre de fichiers source C++ avec une directive d'inclusion supplémentaire avant les #includes existants. Pour ce genre de tâche, j'utilise normalement un petit bash script avec sed pour réécrire le fichier.

Comment puis-je obtenir sed pour remplacer uniquement la première occurrence d'une chaîne de caractères dans un fichier plutôt que de remplacer chaque occurrence ?

Si j'utilise

sed s/#include/#include "newfile.h"\n#include/

il remplace tous les #includes.

Les suggestions alternatives permettant d'atteindre le même objectif sont également les bienvenues.

355voto

tim Points 651

A sed script qui ne remplacera que la première occurrence de "Apple" par "Banana".

Exemple

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

C'est le simple script : Note de l'éditeur : travaille avec GNU sed seulement.

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Les deux premiers paramètres 0 y /Apple/ sont le spécificateur de plage. Le site s/Apple/Banana/ est ce qui est exécuté dans cette plage. Donc, dans ce cas, "dans la plage du début ( 0 ) jusqu'à la première instance de Apple , remplacer Apple con Banana . Seul le premier Apple seront remplacés.

Le contexte : Dans les systèmes traditionnels sed le spécificateur de plage est également "commencer ici" et "finir ici" (inclus). Cependant, le "début" le plus bas est la première ligne (ligne 1), et si le "fin ici" est une regex, alors elle est seulement tentée de correspondre à la ligne suivante après le "début", donc la fin la plus proche possible est la ligne 2. Donc, puisque la plage est inclusive, la plus petite plage possible est "2 lignes" et la plus petite plage de départ est à la fois les lignes 1 et 2 (c'est-à-dire que s'il y a une occurrence sur la ligne 1, les occurrences sur la ligne 2 seront également modifiées, ce qui n'est pas souhaité dans ce cas). GNU sed ajoute sa propre extension en permettant de spécifier start comme "pseudo". line 0 afin que la fin de la plage puisse être line 1 en lui permettant une plage de "seulement la première ligne" si l'expression rationnelle correspond à la première ligne.

Ou une version simplifiée (un RE vide comme // signifie réutiliser celui qui est spécifié avant lui, donc c'est équivalent) :

sed '0,/Apple/{s//Banana/}' input_filename

Et les accolades sont en option pour le s ce qui est également équivalent :

sed '0,/Apple/s//Banana/' input_filename

Tous ces logiciels fonctionnent sous GNU sed seulement.

Vous pouvez également installer GNU sed sur OS X en utilisant homebrew brew install gnu-sed .

191 votes

Traduit en langage humain : commencez à la ligne 0, continuez jusqu'à ce que vous trouviez 'Apple', exécutez la substitution entre crochets. cfr : grymoire.com/Unix/Sed.html#uh-29

9 votes

Sous OS X, j'obtiens sed: 1: "…": bad flag in substitute command: '}'

5 votes

@ELLIOTTCABLE sur OS X, utilisez sed -e '1s/Apple/Banana/;t' -e '1,/Apple/s//Banana/' . D'après la réponse de @MikhailVS (actuellement) tout en bas.

173voto

Ben Hoffstein Points 44398
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

ou, si vous préférez : Note de l'éditeur : travaille avec GNU sed seulement.

sed '0,/foo/s//bar/' file 

Source :

108 votes

Je pense que je préfère la solution "ou si vous préférez". Il serait également bon d'expliquer les réponses - et de faire en sorte que la réponse réponde directement à la question, puis de généraliser, plutôt que de généraliser seulement. Mais bonne réponse.

11 votes

Pour info, pour les utilisateurs de Mac, vous devez remplacer le 0 par un 1, donc : sed '1,/RE/s/to_that/' file

1 votes

@mhost Non, il ne remplacera que si le motif est trouvé dans la ligne #1 et pas si le motif est à une autre ligne, mais c'est toujours le premier motif trouvé. PS : il faut préciser que ce 0, ne fonctionne qu'avec gnu sed

62voto

Sushil Points 181
sed '0,/pattern/s/pattern/replacement/' filename

cela a fonctionné pour moi.

exemple

sed '0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<\/Menu>/' try.txt > abc.txt

Note de l'éditeur : les deux travaillent avec GNU sed seulement.

2 votes

@Landys cela remplace également les instances dans les autres lignes ; pas seulement la première instance.

1 votes

@sarat Oui, vous avez raison. sed '1,/pattern/s/pattern/replacement/' filename ne fonctionne que si "le motif n'apparaît pas sur la première ligne" sur Mac. Je vais supprimer mon commentaire précédent car il n'est pas exact. Le détail peut être trouvé ici ( linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/ ). La réponse d'Andy ne fonctionne que pour GNU sed, mais pas pour celui sur Mac.

0 votes

La réponse la plus simple ! Félicitations.

32voto

richq Points 29694

Vous pouvez utiliser awk pour faire quelque chose de similaire

awk '/#include/ && !done { print "#include \"newfile.h\""; done=1;}; 1;' file.c

Explication :

/#include/ && !done

Exécute l'instruction d'action entre {} lorsque la ligne correspond à "#include" et que nous ne l'avons pas déjà traitée.

{print "#include \"newfile.h\""; done=1;}

Cela imprime #include "newfile.h", nous devons échapper les guillemets. Ensuite, nous mettons la variable done à 1, afin de ne pas ajouter d'autres includes.

1;

Cela signifie "imprimer la ligne" - une action vide entraîne par défaut print $0, qui imprime la ligne entière. Une seule ligne et plus facile à comprendre que sed IMO :-)

4 votes

Cette réponse est plus portable que les solutions sed, qui s'appuient sur gnu sed etc. (par exemple, sed sous OS-X est nul !)

1 votes

C'est vraiment plus compréhensible, mais pour moi cela ajoute une ligne au lieu de la remplacer ; commande utilisée : awk '/version/ && !done {print " \"version\": \"'${NEWVERSION}'\""; done=1;}; 1;' package.json

0 votes

Même chose ici, commande la plus compréhensible, mais elle ajoute une ligne au-dessus de la chaîne trouvée au lieu de la remplacer

10voto

unexist Points 1647

Il suffit d'ajouter le nombre d'occurrences à la fin :

sed s/#include/#include "newfile.h"\n#include/1

8 votes

Malheureusement, cela ne fonctionne pas. Elle remplace juste la première occurrence sur chaque ligne du fichier et non la première occurrence dans le fichier.

1 votes

De plus, il s'agit d'une extension de GNU sed, et non d'une fonctionnalité standard de sed.

10 votes

Hmmm... le temps passe. POSIX 2008/2013 pour sed spécifie la commande de substitution avec : [2addr]s/BRE/replacement/flags et note que "La valeur des drapeaux doit être égale ou supérieure à zéro : n Remplacer la nième occurrence seulement du BRE trouvé dans l'espace du motif." Ainsi, au moins dans la norme POSIX 2008, la terminaison 1 n'est pas une licence GNU sed extension. En effet, même dans le SUS/POSIX 1997 standard, cela a été soutenu, donc j'étais vraiment à côté de la plaque en 2008.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X