Willst du erst beim ersten Auftreten mit sed ersetzen

26

Originaldatei

claudio
antonio
claudio
michele

Ich möchte nur das erste Vorkommen von "claudio" mit "claudia" ändern, damit die Datei das Ergebnis liefert

claudia
antonio
claudio
michele

Ich habe versucht

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

Aber führen Sie eine globale Substitution durch. Warum?

Elbarna
quelle
Schauen Sie hier linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/... und auch info sed: ( 0,/REGEXP/: Eine Zeilennummer 0 kann wie in einer Adressangabe verwendet werden , 0,/REGEXP/so dass sedversucht wird REGEXP in der ersten Eingangsleitung paßt auch mit anderen Worten. 0,/REGEXP/Ist Ähnlich 1,/REGEXP/, außer dass, wenn ADDR2 mit der allerersten Eingabezeile übereinstimmt, das Formular 0 / REGEXP / das Ende des Bereichs berücksichtigt, wohingegen das Formular 1 / REGEXP / mit dem Beginn des Bereichs übereinstimmt und daher die Bereichsspanne festlegt bis zum zweiten Vorkommen des regulären Ausdrucks)
jimmij
awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomisollte tun
Adam Katz

Antworten:

23

Wenn Sie GNU benutzen sed, versuchen Sie:

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

sedStartet die Suche nach dem regulären Ausdruck, der einen Bereich beendet, erst nach der Zeile, die diesen Bereich beginnt.

Von man sed(POSIX-Manpage, Hervorhebung von mir):

Ein Editierbefehl mit zwei Adressen soll den Inklusivbereich auswählen
aus dem ersten Musterbereich, der mit der ersten Adresse übereinstimmt, durch die
nächster Musterraum , der mit dem zweiten übereinstimmt. 

Verwenden awk

Bereiche in der awkArbeit mehr als Sie erwartet hatten:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

Erläuterung:

  • NR==1,/claudio/

    Dies ist ein Bereich, der mit Zeile 1 beginnt und mit dem ersten Auftreten von endet claudio.

  • sub(/claudio/, "claudia")

    Während wir uns im Bereich befinden, wird dieser Ersatzbefehl ausgeführt.

  • 1

    Die kryptische Abkürzung dieser Awk zum Drucken der Zeile.

John1024
quelle
1
Das setzt allerdings GNU voraus sed.
Stéphane Chazelas
@ StéphaneChazelas Es funktioniert auch, wenn POSIXLY_CORRECT gesetzt ist, aber ich denke, das bedeutet nicht so viel, wie ich möchte. Antwort aktualisiert (mir fehlen für BSD-Testmaschinen).
John1024
Die awk kann, IMO, mit einer booleschen Statusvariablen einfacher sein:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
Glenn Jackman
@glennjackman oderawk !x{x=sub(/claudio/,"claudia")}1
Ich konnte im ersten Teil auch kein anderes Trennzeichen verwenden:0,/claudio/
Pat Myron
4

Hier sind zwei weitere programmatische Versuche mit sed: Beide lesen die gesamte Datei in eine einzige Zeichenfolge, dann ersetzt die Suche nur die erste.

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

Mit Kommentar:

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
Glenn Jackman
quelle
3

Eine neue Version von GNU sedunterstützt die -zOption.

Normalerweise liest sed eine Zeile, indem es eine Zeichenfolge bis zum Zeilenende liest (neue Zeile oder Zeilenumbruch).
Die GNU-Version von sed hat eine Funktion in Version 4.2.2 hinzugefügt, um stattdessen das Zeichen "NULL" zu verwenden. Dies kann nützlich sein, wenn Sie Dateien haben, die NULL als Trennzeichen für Datensätze verwenden. Einige GNU-Dienstprogramme können eine Ausgabe erzeugen, die statt einer neuen Zeile NULL verwendet, z. B. "find. -Print0" oder "grep -lZ".

Sie können diese Option verwenden, wenn Sie sedüber verschiedene Zeilen arbeiten möchten .

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

kehrt zurück

claudia
antonio
claudio
michele
Walter A
quelle
1

Sie können awkmit einem Flag feststellen , ob der Austausch bereits durchgeführt wurde. Wenn nicht, fahren Sie fort:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele
fedorqui
quelle
1

Es ist eigentlich ganz einfach, wenn Sie nur eine kleine Verzögerung einstellen - Sie müssen nicht nach unzuverlässigen Erweiterungen greifen:

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

Das verschiebt nur die erste Zeile in die zweite und die zweite in die dritte und so weiter.

Es druckt:

claudia
antonio
claudio
michele
mikeserv
quelle
1

Und noch eine Option

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

Der Vorteil ist, dass es doppelte Anführungszeichen verwendet, so dass Sie Variablen innerhalb verwenden können, dh.

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi
utom
quelle
1
Ja, du hast recht. Die allgemeine Idee ist die gleiche. Versuchen Sie jedoch bitte, einfache und doppelte Anführungszeichen direkt zu ersetzen, und prüfen Sie, ob dies funktioniert. Der Teufel liegt im Detail. In diesem Beispiel sind dies Leerzeichen und ein Fluchtweg. Ich glaube, dass diese Fortsetzung der früheren Antworten jemandem Zeit spart. Und das ist der Grund, warum ich beschlossen habe, den Beitrag zu veröffentlichen.
utom
1

Dies kann auch ohne den Haltebereich und ohne das Konzentrieren aller Zeilen in den Musterbereich erfolgen:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

Erklärung: Wir versuchen "claudio" zu finden und springen dann in die kleine Print-Load-Schleife zwischen :xund bx. Andernfalls drucken wir das Skript und starten es mit der nächsten Zeile neu.

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
Lucas
quelle
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
jgshawkey
quelle
1
Haben Sie sich die Mühe gemacht, die Frage zu lesen?
don_crissti
1

Summe

GNU-Syntax:

sed '/claudio/{s//claudia/;:p;n;bp}' file

Oder sogar (um das zu ersetzende Wort nur einmal zu verwenden:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

Oder in POSIX-Syntax:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

Funktioniert auf jedem sed, verarbeitet nur so viele Zeilen wie nötig, um die erste zu finden. Funktioniert claudioauch, wenn claudiosich diese in der ersten Zeile befindet und ist kürzer, da nur eine reguläre Zeichenfolge verwendet wird.

Detail

Um nur eine Zeile zu ändern , müssen Sie nur eine Zeile auswählen .

Mit einem 1,/claudio/(aus Ihrer Frage) auswählen:

  • aus der ersten Zeile (unbedingt)
  • zur nächsten Zeile, die die Zeichenfolge enthält claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

Verwenden Sie zum Auswählen einer Zeile, die Folgendes enthält claudio:

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

Und um nur den ersten claudio in der Datei auszuwählen , verwenden Sie:

sed -n '/claudio/{p;q}' file
claudio 1

Dann können Sie eine Ersetzung nur in dieser Zeile vornehmen:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

Dies ändert nur das erste Vorkommen der Regex-Übereinstimmung in der Zeile, auch wenn es in der ersten Zeile mehr als eine gibt, die mit der Regex übereinstimmt.

Natürlich /claudio/könnte der reguläre Ausdruck vereinfacht werden, um:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

Dann fehlt nur noch, dass alle anderen Zeilen unverändert gedruckt werden:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Isaac
quelle