Fichier d'origine
claudio
antonio
claudio
michele
Je souhaite modifier uniquement la première occurrence de "claudio" par "claudia" afin que le résultat du fichier
claudia
antonio
claudio
michele
J'ai essayé
sed -e '1,/claudio/s/claudio/claudia/' nomi
Mais effectuez une substitution globale. Pourquoi?
Si vous utilisez GNU sed
, essayez:
sed -e '0,/claudio/ s/claudio/claudia/' nomi
sed
ne commence pas à rechercher l'expression régulière qui termine une plage jusqu'à ce que après la ligne qui commence cette plage.
De man sed
(page de manuel POSIX, c'est moi qui souligne):
Une commande d'édition avec deux adresses doit sélectionner la plage inclusive du premier espace de modèle qui correspond à la première adresse jusqu'à l'espace de modèle suivant qui correspond à la seconde.
L'adresse 0
N'est cependant pas standard, c'est une extension GNU sed
non prise en charge par aucune autre implémentation sed
.
awk
Les plages dans awk
fonctionnent plus comme prévu:
$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele
Explication:
NR==1,/claudio/
Il s'agit d'une plage qui commence par la ligne 1 et se termine par la première occurrence de claudio
.
sub(/claudio/, "claudia")
Pendant que nous sommes dans la plage, cette commande de substitution est exécutée.
1
Le raccourci cryptique de cet awk pour imprimer la ligne.
Une nouvelle version de GNU sed
prend en charge le -z
option.
Normalement, sed lit une ligne en lisant une chaîne de caractères jusqu'au caractère de fin de ligne (nouvelle ligne ou retour chariot).
La version GNU de sed a ajouté une fonctionnalité dans la version 4.2.2 pour utiliser le caractère "NULL" à la place. Cela peut être utile si vous avez des fichiers qui utilisent NULL comme séparateur d'enregistrement. Certains utilitaires GNU peuvent générer une sortie qui utilise un NULL à la place d'une nouvelle ligne, comme "find. -print0" ou "grep -lZ").
Vous pouvez utiliser cette option lorsque vous souhaitez que sed
fonctionne sur différentes lignes.
echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'
retour
claudia
antonio
claudio
michele
Voici 2 autres efforts de programmation avec sed: ils lisent tous les deux le fichier entier dans une seule chaîne, puis la recherche ne remplacera que le premier.
sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file
Avec commentaire:
sed -n ' # don't implicitly print input
:a # label "a"
N # append next line to pattern space
$bb # at the last line, goto "b"
ba # goto "a"
:b # label "b"
s/\(claudi\)o/\1a/ # replace
p # and print
' file
sed -n ' # don't implicitly print input
1h # put line 1 in the hold space
1!H # for subsequent lines, append to hold space
${ # on the last line
g # put the hold space in pattern space
s/\(claudi\)o/\1a/ # replace
p # print
}
' file
Syntaxe GNU:
sed '/claudio/{s//claudia/;:p;n;bp}' file
Ou même (pour n'utiliser qu'une seule fois le mot à remplacer:
sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file
Ou, dans la syntaxe POSIX:
sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file
fonctionne sur n'importe quel sed, traite niquement autant de lignes que nécessaire pour trouver le premier claudio
, fonctionne même si claudio
est sur la première ligne et est plus court qu'il utilise une seule chaîne d'expression régulière.
Pour modifier uniquement ne ligne vous devez sélectionner une seule ligne.
Utilisant un 1,/claudio/
(à partir de votre question) sélectionne:
claudio
.$ cat file
claudio 1
antonio 2
claudio 3
michele 4
$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3
Pour sélectionner toute ligne contenant claudio
, utilisez:
$ sed -n `/claudio/{p}` file
claudio 1
claudio 3
Et pour sélectionner uniquement le premierclaudio
dans le fichier, utilisez:
sed -n '/claudio/{p;q}' file
claudio 1
Ensuite, vous pouvez effectuer une substitution uniquement sur cette ligne:
sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1
Ce qui ne changera que l'occurrence premier de la correspondance d'expression régulière sur la ligne, même s'il peut y en avoir plus d'une, sur la ligne première qui correspond à l'expression régulière.
Bien sûr, le /claudio/
regex pourrait être simplifié pour:
$ sed '/claudio/{s//claudia/;q}' file
claudia 1
Et puis, la seule chose qui manque est d'imprimer toutes les autres lignes non modifiées:
sed '/claudio/{s//claudia/;:p;n;bp}' file
sed -n '/claudia/{p;Q}'
sed -n ' # don't print input
/claudia/ # regex search
{ # when match is found do
p; # print line
Q # quit sed, don't print last buffered line
{ # end do block
Et encore une option
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi
L'avantage est qu'il utilise des guillemets doubles, vous pouvez donc utiliser des variables à l'intérieur, par exemple.
export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi
Vous pouvez utiliser awk
avec un indicateur pour savoir si le remplacement a déjà été effectué. Sinon, continuez:
$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele
Cela peut également être fait sans l'espace d'attente et sans concaténer toutes les lignes dans l'espace de motif:
sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi
Explication: Nous essayons de trouver "claudio" et si nous le faisons, nous sautons dans la petite boucle de chargement d'impression entre :x
et bx
. Sinon, nous imprimons et redémarrons le script avec la ligne suivante.
sed -n ' # do not print lines by default
/claudio/ { # on lines that match "claudio" do ...
s/o/a/ # replace "o" with "a"
bx # goto label x
} # end of do block
p # print the pattern space
b # go to the end of the script, continue with next line
:x # the label x for goto commands
p # print the pattern space
n # load the next line in the pattern space (clearing old contents)
bx # goto the label x
' nomi
C'est vraiment très facile si vous configurez juste un peu de retard - il n'est pas nécessaire d'aller chercher des extensions non fiables:
sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN
Cela reporte simplement la première ligne à la deuxième et la deuxième à la troisième et etc.
Il imprime:
claudia
antonio
claudio
michele