web-dev-qa-db-fra.com

Vous voulez remplacer uniquement la première occurrence par sed

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?

33
elbarna

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.

Utilisation de 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.

30
John1024

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
5
Walter A

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
5
glenn jackman

Résumé

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.

Détail

Pour modifier uniquement ne ligne vous devez sélectionner une seule ligne.

Utilisant un 1,/claudio/ (à partir de votre question) sélectionne:

  • à partir de la première ligne (sans condition)
  • à la ligne suivante qui contient la chaîne 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
1
Isaac
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
1
jgshawkey

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
1
utom

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
1
fedorqui

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
1
Lucas

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
1
mikeserv