Insérer une ligne après le premier match avec sed

245

Pour une raison quelconque, je n'arrive pas à trouver une réponse simple à cela et je suis sur un peu de temps à l'heure actuelle. Comment pourrais-je insérer une ligne de texte de choix après la première ligne correspondant à une chaîne spécifique à l'aide de la sedcommande. J'ai ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

Et je veux insérer une ligne après la CLIENTSCRIPT=ligne résultant en ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
user2150250
la source

Réponses:

379

Essayez de faire cela en utilisant GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

si vous souhaitez remplacer sur place , utilisez

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Production

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

Doc

  • voir sed doc et recherche \a(ajouter)
Gilles Quenot
la source
Merci! Et pour le faire réécrire dans le fichier sans imprimer le contenu du fichier?
user2150250
11
Notez que les deux supposent GNU sed. Ce n'est pas une sedsyntaxe standard et ne fonctionnera avec aucune autre sedimplémentation.
Stéphane Chazelas
J'ai eu du mal à obtenir que cela fonctionne pour moi parce que j'utilisais un délimiteur différent. Après RTFM, j'ai réalisé que je devais démarrer l'expression régulière avec un \, puis le délimiteur.
Christy
10
Comment cela s'applique-t-il UNIQUEMENT au premier match? il est clair qu'il ajoute du texte après la correspondance, mais comment sait-il uniquement la première correspondance?
Mohammed Noureldin
7
Cela ajoute le texte après chaque match, pour moi.
schoppenhauer
49

Notez la sedsyntaxe standard (comme dans POSIX, donc supportée par toutes les sedimplémentations conformes autour (GNU, OS / X, BSD, Solaris ...)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

Ou sur une seule ligne:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

(les -expressions (et le contenu des -ffichiers) sont associés à des nouvelles lignes pour constituer les sedinterpréteurs de script sed ).

L' -ioption d'édition sur place est également une extension GNU, d'autres implémentations (comme FreeBSD) le supportent -i ''.

Alternativement, pour la portabilité, vous pouvez utiliser à la perlplace:

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

Ou vous pouvez utiliser edou ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Stéphane Chazelas
la source
2
Je peux me tromper, mais le courant sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' fileéchappe à la citation à la fin du premier paramètre et rompt la commande.
Panier abandonné
1
@AbandonedCart, dans les coquilles de la Bourne, cshou rcfamille, '...'sont des citations fortes à l'intérieur desquelles la barre oblique inversée n'est pas spéciale. La seule exception que je connaisse est le fishshell.
Stephane Chazelas
1
Terminal sur MacOS (et par la suite un script exécuté dans le terminal) est également une exception, apparemment. J'ai trouvé une syntaxe alternative, mais merci quand même.
Panier abandonné
1
@AbandonedCart, c'est autre chose. C'est macOS qui sedn'est pas compatible POSIX ici. Cela rend ma déclaration sur le fait qu'il soit portable incorrect (pour la variante d'une ligne). Je vais demander à l'opengroup de confirmer s'il s'agit bien d'une non-conformité ou d'une mauvaise interprétation de la norme de ma part.
Stephane Chazelas
19

Un conforme POSIX utilisant la scommande:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
la source
1
... qui prend même en charge l'insertion de nouvelles lignes. J'aime cela!
Stephan Henningsen du
1
Voir aussi sed -e '/.../s/$/\' -e 'CLI.../'ce qui éviterait des problèmes avec des lignes contenant des séquences d'octets ne formant pas de caractères valides.
Stéphane Chazelas
15

Commande Sed qui fonctionne sur MacOS (au moins, OS 10) et Unix (c.-à-d. Ne nécessite pas gnu sed comme celui de Gilles (actuellement accepté)):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

Cela fonctionne dans bash et peut-être aussi dans d'autres shells qui connaissent le style de devis d'évaluation $ '\ n'. Tout peut être sur une seule ligne et fonctionner dans des commandes sed plus anciennes / POSIX. S'il peut y avoir plusieurs lignes correspondant à CLIENTSCRIPT = "foo" (ou votre équivalent) et que vous ne souhaitez ajouter la ligne supplémentaire que la première fois, vous pouvez la retravailler comme suit:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(cela crée une boucle après le code d'insertion de ligne qui fait simplement défiler le reste du fichier, sans jamais revenir à la première commande sed).

Vous remarquerez peut-être que j'ai ajouté un '^ *' au modèle correspondant au cas où cette ligne apparaît dans un commentaire, par exemple, ou est en retrait. Ce n'est pas parfait à 100% mais couvre d'autres situations susceptibles d'être courantes. Ajustez au besoin ...

Ces deux solutions contournent également le problème (pour la solution générique consistant à ajouter une ligne): si votre nouvelle ligne insérée contient des barres obliques inverses ou des esperluettes, elles seront interprétées par sed et ne sortiront probablement pas de la même manière, tout comme l' \nest - par exemple. \0serait la première ligne correspondante. Particulièrement pratique si vous ajoutez une ligne qui provient d'une variable où vous auriez autrement à tout échapper en premier en utilisant $ {var //} avant, ou une autre instruction sed, etc.

Cette solution est un peu moins compliquée dans les scripts (que les guillemets et \ n ne sont pas faciles à lire cependant), lorsque vous ne voulez pas mettre le texte de remplacement de la commande a au début d'une ligne, par exemple, dans une fonction avec des lignes en retrait. J'ai profité du fait que $ '\ n' est évalué à une nouvelle ligne par le shell, ce n'est pas dans des valeurs simples '\ n' entre guillemets.

Cela devient cependant assez long pour que je pense que perl / même awk pourrait gagner en raison de sa lisibilité.

Breezer
la source
1
Merci pour la réponse! Le premier fonctionne également comme un charme sur AIX OS.
abhishek
comment faites-vous cela, mais pour l'insertion sur plusieurs lignes?
tatsu
1
POSIX sed prend en charge les sauts de ligne littéraux dans la chaîne de remplacement si vous les "échappez" avec une barre oblique inverse ( source ). Donc: s/CLIENTSCRIPT="foo"/&\ [Entrée] CLIENTSCRIPT2="hello"/ . La barre oblique inversée doit être le tout dernier caractère de la ligne (pas d'espace après), contrairement à ce à quoi elle ressemble dans ce commentaire.
TheDudeAbides
1
@tatsu Les retours à la ligne dans la chaîne de remplacement de s///, ainsi que ceux des commandes aet ipeuvent être "échappés", en utilisant la même méthode que je décris dans mon commentaire ci-dessus.
TheDudeAbides
4

Peut-être un peu tard pour poster une réponse à cela, mais j'ai trouvé certaines des solutions ci-dessus un peu lourdes.

J'ai essayé le remplacement simple des cordes dans sed et cela a fonctionné:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Le signe & reflète la chaîne correspondante, puis vous ajoutez \ n et la nouvelle ligne.

Comme mentionné, si vous souhaitez le faire sur place:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Autre chose. Vous pouvez faire correspondre en utilisant une expression:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

J'espère que cela aide quelqu'un

siwesam
la source
Cela fonctionne très bien sur Linux, où GNU sed est par défaut, car il comprend \ndans la chaîne de remplacement. Plain-vanilla (POSIX) sedne comprend pas\n dans la chaîne de remplacement, cependant, cela ne fonctionnera pas sur BSD ou macOS - à moins que vous n'ayez installé GNU sed d'une manière ou d'une autre. Avec vanilla sed, vous pouvez incorporer des sauts de ligne en les «échappant», c'est-à-dire en terminant la ligne par une barre oblique inverse, puis en appuyant sur [Entrée] ( référence , sous l'en-tête pour [2addr]s/BRE/replacement/flags). Cela signifie que votre commande sed doit s'étendre sur plusieurs lignes dans le terminal.
TheDudeAbides
Une autre option pour obtenir une nouvelle ligne littérale dans la chaîne de remplacement consiste à utiliser la fonction de citation ANSI-C dans Bash, comme mentionné dans l' une des autres réponses .
TheDudeAbides
1

J'ai eu une tâche similaire et je n'ai pas pu faire fonctionner la solution Perl ci-dessus.

Voici ma solution:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Explication:

Utilise une expression régulière pour rechercher une ligne dans mon fichier /etc/mysql/my.cnf qui la contenait uniquement [mysqld]et l'a remplacée par

[mysqld] collation-server = utf8_unicode_ci

ajouter efficacement la collation-server = utf8_unicode_ciligne après la ligne contenant [mysqld].

Caleb
la source
0

J'ai dû le faire récemment pour les systèmes d'exploitation Mac et Linux et après avoir parcouru de nombreux messages et essayé beaucoup de choses, à mon avis, je ne suis jamais arrivé à l'endroit où je voulais ce qui est: une solution assez simple pour comprendre en utilisant bien connu et des commandes standard avec des motifs simples, une doublure, portable, extensible pour ajouter plus de contraintes. Ensuite, j'ai essayé de le regarder avec une perspective différente, c'est alors que j'ai réalisé que je pouvais me passer de l'option "un liner" si un "2 liner" répondait au reste de mes critères. À la fin, j'ai trouvé cette solution que j'aime et qui fonctionne à la fois sur Ubuntu et sur Mac que je voulais partager avec tout le monde:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

Dans la première commande, grep recherche les numéros de ligne contenant "foo", cut / head sélectionne la 1ère occurrence, et l'op arithmétique incrémente ce premier numéro de ligne d'occurrence de 1 car je veux insérer après l'occurrence. Dans la deuxième commande, il s'agit d'une modification de fichier sur place, "i" pour l'insertion: un ansi-c citant une nouvelle ligne, "bar", puis une autre nouvelle ligne. Le résultat est l'ajout d'une nouvelle ligne contenant "bar" après la ligne "foo". Chacune de ces 2 commandes peut être étendue à des opérations et des correspondances plus complexes.

momonari8
la source