Vous voulez remplacer uniquement sed première apparition par sed


27

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?


Regardez ici linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… et aussi info sed: ( 0,/REGEXP/: Un numéro de ligne de 0 peut être utilisé dans une spécification d'adresse comme 0,/REGEXP/pour sedessayer de faire correspondre REGEXP dans la première ligne d'entrée également. En d'autres termes, 0,/REGEXP/est similaire à 1,/REGEXP/, sauf que si ADDR2 correspond à la toute première ligne d'entrée, le formulaire 0, / REGEXP / le considérera comme mettant fin à la plage, tandis que le formulaire 1, / REGEXP / correspondra au début de sa plage et donc étendra la plage jusqu'à la deuxième occurrence de l'expression régulière)
jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomidevrait faire
Adam Katz

Réponses:


23

Si vous utilisez GNU sed, essayez:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sedne démarre pas la vérification de la regex qui met fin à une plage jusqu'à 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
à partir du premier espace de modèle qui correspond à la première adresse via le
espace de motif suivant qui correspond au second. 

En utilisant awk

Les plages de awktravail fonctionnent plus que vous attendiez:

$ 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.


1
Cela suppose sedcependant GNU .
Stéphane Chazelas

@ StéphaneChazelas Cela fonctionne aussi si POSIXLY_CORRECT est défini mais je suppose que cela ne signifie pas autant que je le voudrais. Réponse mise à jour (je manque pour les machines de test BSD).
John1024

L'awk peut, IMO, être plus simple avec une variable d'état booléenne:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman

@glennjackman orawk !x{x=sub(/claudio/,"claudia")}1

Je n'ai pas réussi non plus à utiliser un délimiteur différent dans la première partie:0,/claudio/
Pat Myron

4

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

3

Une nouvelle version de GNU sedprend en charge l' -zoption.

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 la valeur 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 sedtravailler sur différentes lignes.

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

résultats

claudia
antonio
claudio
michele

1

Vous pouvez utiliser awkun 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

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 peu 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

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
Oui vous avez raison. L'idée générale est la même. Mais, s'il vous plaît, essayez de remplacer directement les simples par des guillemets doubles et voyez si cela fonctionne. Le diable réside dans les détails. Dans cet exemple, ce sont des espaces et une évasion. Je pense que cette continuation des réponses précédentes peut faire gagner du temps à quelqu'un. Et c'est la raison pour laquelle j'ai décidé de publier l'article.
utom

1

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 :xet 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
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
Avez-vous pris la peine de lire la question?
don_crissti

1

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 uniquement autant de lignes que nécessaire pour trouver la première claudio, fonctionne même s'il claudioest dans la première ligne et est plus court car il n'utilise qu'une seule chaîne d'expression régulière.

Détail

Pour modifier une seule ligne, vous devez sélectionner une seule ligne.

L'utilisation d'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 une ligne qui contient claudio, utilisez:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

Et pour ne sélectionner que le premier claudio du 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 le premier occurrence de la correspondance d'expression régulière sur la ligne, même s'il peut y en avoir plusieurs, sur la première ligne qui correspond à l'expression régulière.

Bien sûr, l' /claudio/expression rationnelle pourrait être simplifiée 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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.