Comment insérer une nouvelle ligne devant un motif?

138

Comment insérer une nouvelle ligne avant un motif dans une ligne?

Par exemple, cela insérera une nouvelle ligne derrière le modèle regex.

sed 's/regex/&\n/g'

Comment puis-je faire la même chose mais devant le motif?

Étant donné cet exemple de fichier d'entrée, le modèle à associer est le numéro de téléphone.

some text (012)345-6789

Devrait devenir

some text
(012)345-6789
Dennis
la source
1
@NilsvonBarth, pourquoi une question simple est-elle une mauvaise question?
Josh

Réponses:

177

Cela fonctionne sous bashet zsh, testé sur Linux et OS X:

sed 's/regexp/\'$'\n/g'

En général, pour $suivi d'un littéral de chaîne entre guillemets simples basheffectue une substitution de barre oblique inverse de style C, par exemple $'\t'est traduit en une tabulation littérale. De plus, sed veut que votre littéral de nouvelle ligne soit échappé avec une barre oblique inverse, d'où l' \avant $. Et enfin, le signe dollar lui-même ne doit pas être cité pour qu'il soit interprété par le shell, donc nous fermons la cotation avant le $puis l'ouvrons à nouveau.

Edit : Comme suggéré dans les commentaires de @ mklement0, cela fonctionne également:

sed $'s/regexp/\\\n/g'

Ce qui se passe ici est: la commande sed entière est maintenant une chaîne de style C, ce qui signifie que la barre oblique inverse que sed doit être placée avant le nouveau littéral de ligne doit maintenant être échappée avec une autre barre oblique inverse. Bien que plus lisible, dans ce cas, vous ne serez pas en mesure de faire des substitutions de chaînes shell (sans le rendre à nouveau laid.)

mojuba
la source
7
Cela me donne "une nouvelle ligne sans échappée dans un motif de substitution" sur OSX.
Matt Gibson
@Matt Gibson c'est très étrange car "un retour à la ligne sans échappement" n'est donné que lorsque vous avez une vraie nouvelle ligne sans barre oblique inverse dans le motif de substitution. Mon code ci-dessus fonctionne, en fait, dans d'autres shells aussi, par exemple zsh, ksh.
mojuba
3
@Matt Gibson ... ou si vous oubliez la barre oblique inverse avant '$' \ n dans mon code.
mojuba
7
Telles qu'elles sont écrites, ces expressions remplacent complètement l'expression régulière par une nouvelle ligne, plutôt que d'insérer une nouvelle ligne au milieu d'une ligne existante comme demandé. Voici comment je une forme modifiée de cette réponse pour insérer une nouvelle ligne entre deux modèles adaptés: sed '\(first match\)\(second match\)/\1\'$'\n''\2/g'. Notez les deux guillemets simples après le \ n. Le premier ferme la $section " " de sorte que le reste de la ligne n'en soit pas affecté. Sans ces guillemets, le \ 2 a été ignoré.
David Ravetti
12
Une autre option consiste à utiliser une seule chaîne entre guillemets C ANSI : sed $'s/regexp/\\\n/g'qui améliore la lisibilité - la seule mise en garde est que vous devez alors doubler tous les littéraux \ caractères.
mklement0
43

Certaines des autres réponses n'ont pas fonctionné pour ma version de sed. Changer la position de &et \na fonctionné.

sed 's/regexp/\n&/g' 

Edit: Cela ne semble pas fonctionner sur OS X, sauf si vous installez gnu-sed.

Dennis
la source
9
Je ne suis pas sûr que cela fonctionne dans toutes les versions de sed. J'ai essayé ceci sur mon Mac et le \ n obtient juste la sortie "n"
Todd Gamblin
3
J'ai passé 15 minutes sur le mac à mon travail, avant de lire votre réponse. Allez Apple!
Rick77
1
Pour ceux qui utilisent l'homebrew: brew install gnu-sedsuivi degsed 's/regexp/\n&/g'
aaaaaa
1
... suivi deecho 'alias sed=gsed' >> ~/.bashrc
Proximo
36

Dans sed, vous ne pouvez pas facilement ajouter de nouvelles lignes dans le flux de sortie. Vous devez utiliser une ligne de continuation, ce qui est gênant, mais cela fonctionne:

$ sed 's/regexp/\
&/'

Exemple:

$ echo foo | sed 's/.*/\
&/'

foo

Voir ici pour plus de détails. Si vous voulez quelque chose d'un peu moins gênant, vous pouvez essayer d'utiliser perl -peavec des groupes de correspondance au lieu de sed:

$ echo foo | perl -pe 's/(.*)/\n$1/'

foo

$1 fait référence au premier groupe correspondant dans l'expression régulière, où les groupes sont entre parenthèses.

Todd Gamblin
la source
Pourquoi dites-vous que vous ne pouvez pas ajouter de nouvelles lignes? Vous pouvez simplement faire sed 's / regexp / & \ n / g' Ça y est
Andres
2
C'est la chose la moins piratée que vous puissiez faire sur un Mac pour insérer une nouvelle ligne (\ n ne fonctionne pas sur un mac)
Pylinux
La version perl peut être modifiée pour faire une édition sur placeperl -pi -e 's/(.*)/\n$1/' foo
Éponyme
2
@Andres: (La plupart du temps) les implémentations de Sed avec fonctionnalités POSIX uniquement, telles que la version BSD fournie également avec OS X, ne prennent pas en charge les séquences d'échappement de caractères de contrôle dans la partie substitution d'un sappel de fonction (contrairement à l' implémentation GNU Sed, qui le fait) . La réponse ci-dessus fonctionne avec les deux implémentations; pour un aperçu de toutes les différences, voir ici .
mklement0
29

Sur mon mac, ce qui suit insère un seul 'n' au lieu d'une nouvelle ligne:

sed 's/regexp/\n&/g'

Cela remplace par une nouvelle ligne:

sed "s/regexp/\\`echo -e '\n\r'`/g"
Roar Skullestad
la source
J'étais en train de modifier en ligne sed -i '' -e ...et j'avais des problèmes avec un ^Msigne d'insertion M (ctrl + m) pour être écrit dans le fichier. J'ai fini par utiliser perl avec les mêmes paramètres.
Steve Tauber
2
Veuillez noter que le deuxième code insère un code spécial de nouvelle ligne LF CR (inverse du MS-DOS CR LF)! Les systèmes d'exploitation de type Unix et Mac OS X utilisent uniquement LF ( \n).
pabouk
Quelque chose d'autre dans mon expression sed causait tellement de malheur (même si cela fonctionnait bien sans le echo...et la nouvelle ligne) que je viens de le faire dans vim.
Ahmed Fasih
1
Ou simplement: sed "s/regexp/`echo`/g"- cela produira un seul LF au lieu de LF-CR
mojuba
2
@mojuba: Non: `echo`donnera la chaîne vide , car les substitutions de commandes coupent invariablement toutes les nouvelles lignes de fin. Il n'y a aucun moyen d'utiliser une substitution de commande pour insérer directement une seule nouvelle ligne (et l'insertion \n\r- c'est-à-dire, un CR supplémentaire - est une idée terrible).
mklement0
15
echo one,two,three | sed 's/,/\
/g'
vivek
la source
1
+1 fonctionnait parfaitement et assez droit / facile à retenir
gMale
2
Cette réponse est en fait une solution sed plutôt qu'une solution bash . Tout ce qui utilise des constructions comme $'\n's'appuie sur le shell pour générer la nouvelle ligne. Ces solutions peuvent ne pas être portables. Celui-ci est. Bien sûr, c'est aussi une copie du deuxième exemple de la réponse de tgamblin de 2009.
ghoti
10

Dans ce cas, je n'utilise pas sed. J'utilise tr.

cat Somefile |tr ',' '\012' 

Cela prend la virgule et la remplace par le retour chariot.

user1612632
la source
1
J'ai trouvé que cela fonctionne aussi: cat Somefile | tr ',' '\n'YMMV
LS
9

Vous pouvez utiliser des one-liners perl comme vous le faites avec sed, avec l'avantage du support complet des expressions régulières perl (qui est beaucoup plus puissant que ce que vous obtenez avec sed). Il y a aussi très peu de variation entre les plates-formes * nix - perl est généralement perl. Ainsi, vous pouvez arrêter de vous soucier de la façon dont la version de sed de votre système particulier fait ce que vous voulez.

Dans ce cas, vous pouvez faire

perl -pe 's/(regex)/\n$1/'

-pe met perl dans une boucle "exécuter et imprimer", un peu comme le mode de fonctionnement normal de sed.

' cite tout le reste pour que le shell n'interfère pas

()entourant l'expression régulière est un opérateur de regroupement. $1sur le côté droit de la substitution imprime tout ce qui correspond à l'intérieur de ces parenthèses.

Enfin, \nest une nouvelle ligne.

Que vous utilisiez ou non des parenthèses comme opérateur de regroupement, vous devez échapper toutes les parenthèses que vous essayez de faire correspondre. Ainsi, une expression régulière correspondant au modèle que vous avez énuméré ci-dessus serait quelque chose comme

\(\d\d\d\)\d\d\d-\d\d\d\d

\(ou \)correspond à un paren littéral et \dcorrespond à un chiffre.

Mieux:

\(\d{3}\)\d{3}-\d{4}

J'imagine que vous pouvez comprendre ce que font les nombres entre accolades.

De plus, vous pouvez utiliser des délimiteurs autres que / pour votre regex. Donc, si vous avez besoin de correspondre / vous n'aurez pas besoin d'y échapper. L'un ou l'autre des éléments ci-dessous est équivalent à l'expression régulière au début de ma réponse. En théorie, vous pouvez remplacer n'importe quel caractère pour le standard /.

perl -pe 's#(regex)#\n$1#'
perl -pe 's{(regex)}{\n$1}'

Quelques dernières réflexions.

utiliser -neau lieu de -peagit de la même manière, mais ne s'imprime pas automatiquement à la fin. Cela peut être pratique si vous souhaitez imprimer vous-même. Par exemple, voici un grep-alike ( m/foobar/est une correspondance regex):

perl -ne 'if (m/foobar/) {print}'

Si vous trouvez que le traitement des nouvelles lignes est gênant et que vous voulez que cela soit géré par magie pour vous, ajoutez -l. Pas utile pour l'OP, qui travaillait avec des nouvelles lignes, cependant.

Astuce bonus - si vous avez installé le paquet pcre, il est livré avec pcregrep, qui utilise des expressions régulières entièrement compatibles avec Perl.

Dan Pritts
la source
4

Hmm, les nouvelles lignes juste échappées semblent fonctionner dans les versions plus récentes de sed(j'ai GNU sed 4.2.1),

dev:~/pg/services/places> echo 'foobar' | sed -r 's/(bar)/\n\1/;'
foo
bar
gatoatigrado
la source
1
Comme mentionné, cela fonctionne avec différentes versions de GNU sed, mais pas avec le sed inclus avec macOS.
LS
4
echo pattern | sed -E -e $'s/^(pattern)/\\\n\\1/'

a bien fonctionné sur El Captitan avec le ()soutien

Quanlong
la source
Cela a très bien fonctionné et vous donnez même une commande complète pour tester et extrapoler pour se spécialiser à ses propres fins. Bon travail!
jxramos
3

Pour insérer une nouvelle ligne dans le flux de sortie sous Linux, j'ai utilisé:

sed -i "s/def/abc\\\ndef/" file1

file1était:

def

Avant le remplacement sur place du sed, et:

abc
def

Après le remplacement sur place du sed. Veuillez noter l'utilisation de \\\n. Si les motifs ont un à l' "intérieur, évitez d'utiliser \".

Karthik
la source
Pour moi, le code ci-dessus ne fonctionne pas. sedinsère \nau lieu de LF car il obtient \\nle paramètre du shell. --- Ce code fonctionne: sed -i "s/def/abc\ndef/" file1. --- GNU sed version 4.2.1, GNU bash, version 4.1.2(1) / 4.2.25(1)(CentOS version 6.4 / Ubuntu 12.04.3).
pabouk
2

dans sed, vous pouvez référencer des groupes dans votre motif avec "\ 1", "\ 2", .... donc si le motif que vous recherchez est "PATTERN", et que vous voulez insérer "BEFORE" devant lui , vous pouvez utiliser, sans échapper

sed 's/(PATTERN)/BEFORE\1/g'

c'est à dire

  sed 's/\(PATTERN\)/BEFORE\1/g'
Steve B.
la source
Je viens de faire: testfile contents = "ABC ABC ABC". Ran "sed 's / \ (ABC \) / \ n \ 1 / g' testfile, obtenu les nouvelles lignes. Expérimentez avec les échappements, essayez d'ajouter 1 chose à la fois à votre modèle, par exemple, assurez-vous que vous correspondez au motif, puis vérifiez la correspondance de groupe, puis ajoutez la vérification de nouvelle ligne.
Steve B.
J'ai juste essayé exactement cela et j'ai obtenu "nABC nABC nABC". Utilisez-vous une autre version de sed?
Todd Gamblin
shell s'échapper entrave probablement les tentatives de tgamblin. mettre les arguments sed complets entre guillemets simples comme l'a fait Steve B devrait résoudre ce problème. Possible cependant que les différentes versions de sed ne comprennent pas le \ n pour la nouvelle ligne.
Dan Pritts
2

Vous pouvez également le faire avec awk, en utilisant -vpour fournir le modèle:

awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' file

Cela vérifie si une ligne contient un motif donné. Si tel est le cas, il ajoute une nouvelle ligne au début de celui-ci.

Voir un exemple de base:

$ cat file
hello
this is some pattern and we are going ahead
bye!
$ awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' file
hello
this is some 
pattern and we are going ahead
bye!

Notez que cela affectera tous les modèles d'une ligne:

$ cat file
this pattern is some pattern and we are going ahead
$ awk -v patt="pattern" '$0 ~ patt {gsub(patt, "\n"patt)}1' d
this 
pattern is some 
pattern and we are going ahead
fedorqui 'Alors arrêtez de nuire'
la source
1
que fait le 1?
whatahitson le
1
@whatahitson 1est utilisé dans Awk comme un raccourci vers {print $0}. La raison en est que toute condition évaluée à True déclenche l'action par défaut d'Awk, qui consiste à imprimer l'enregistrement en cours.
fedorqui 'SO arrêtez de nuire'
1

Cela fonctionne sous MAC pour moi

sed -i.bak -e 's/regex/xregex/g' input.txt sed -i.bak -e 's/qregex/\'$'\nregex/g' input.txt

Dono si c'est parfait ...

sam
la source
1

Après avoir lu toutes les réponses à cette question, il m'a encore fallu de nombreuses tentatives pour obtenir la syntaxe correcte dans l'exemple de script suivant:

#!/bin/bash
# script: add_domain
# using fixed values instead of command line parameters $1, $2
# to show typical variable values in this example
ipaddr="127.0.0.1"
domain="example.com"
# no need to escape $ipaddr and $domain values if we use separate quotes.
sudo sed -i '$a \\n'"$ipaddr www.$domain $domain" /etc/hosts

Le script ajoute une nouvelle ligne \nsuivie d'une autre ligne de texte à la fin d'un fichier à l'aide d'une seule sedcommande.

Xavier
la source
1
sed -e 's/regexp/\0\n/g'

\ 0 est la valeur nulle, donc votre expression est remplacée par une valeur nulle (rien), puis ...
\ n est la nouvelle ligne

Sur certaines versions d'Unix ne fonctionne pas, mais je pense que c'est la solution à votre problème.

echo "Hello" | sed -e 's/Hello/\0\ntmow/g'
Hello
tmow
tmow
la source
0

Dans vi sur Red Hat, j'ai pu insérer des retours chariot en utilisant uniquement le caractère \ r. Je crois que cela exécute en interne «ex» au lieu de «sed», mais c'est similaire, et vi peut être un autre moyen de faire des modifications en masse telles que des correctifs de code. Par exemple. J'entoure un terme de recherche avec une instruction if qui insiste sur les retours chariot après les accolades:

:.,$s/\(my_function(.*)\)/if(!skip_option){\r\t\1\r\t}/

Notez que je lui ai également fait insérer des onglets pour que les choses s'alignent mieux.

Robert Casey
la source