Chcesz zastąpić sed tylko raz


26

Oryginalny plik

claudio
antonio
claudio
michele

Chcę zmienić tylko pierwsze wystąpienie „claudio” na „claudia”, więc wynik pliku

claudia
antonio
claudio
michele

próbowałem

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

Ale dokonaj globalnej zamiany. Dlaczego?


Spójrz tutaj linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/…, a także info sed: ( 0,/REGEXP/: Numer linii 0 może być użyty w specyfikacji adresu, 0,/REGEXP/tak aby sedpróbował dopasować REGEXP również w pierwszej linii wejściowej. Innymi słowy, 0,/REGEXP/jest podobnie 1,/REGEXP/, z tym wyjątkiem, że jeśli ADDR2 pasuje do pierwszego wiersza wejścia, formularz 0, / REGEXP / uzna, że ​​kończy on zakres, podczas gdy formularz 1, / REGEXP / dopasuje początek jego zakresu i tym samym zwiększy zakres zakresu aż do drugiego wystąpienia wyrażenia regularnego)
jimmij


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

Odpowiedzi:


23

Jeśli używasz GNU sed, spróbuj:

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

sednie uruchamia sprawdzanie regex, który kończy się zasięg aż po wierszu rozpoczynającym tego zakresu.

Z man sed(strona POSIX, moje podkreślenie):

Polecenie edycji z dwoma adresami wybiera zakres włącznie
z pierwszego miejsca na wzór, który pasuje do pierwszego adresu przez
następna przestrzeń wzoru pasująca do drugiej. 

Za pomocą awk

Zakresy awkpracy są bardziej zgodne z oczekiwaniami:

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

Wyjaśnienie:

  • NR==1,/claudio/

    Jest to zakres, który zaczyna się od linii 1, a kończy przy pierwszym wystąpieniu claudio.

  • sub(/claudio/, "claudia")

    Gdy jesteśmy w zasięgu, to polecenie zastępcze jest wykonywane.

  • 1

    Ten tajemniczy skrót tego awk do drukowania linii.


1
To zakłada GNU sed.
Stéphane Chazelas

@ StéphaneChazelas Działa również, jeśli jest ustawiony POSIXLY_CORRECT, ale myślę, że to nie znaczy tyle, ile bym chciał. Odpowiedź zaktualizowana (brakuje mi maszyn testowych BSD).
John1024

Awk może, IMO, być prostszy ze zmienną statusu boolowskiego:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman

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

Nie mogłem również z powodzeniem użyć innego ogranicznika w pierwszej części:0,/claudio/
Pat Myron,

4

Oto 2 kolejne programowe wysiłki z sed: oba czytają cały plik w jednym ciągu, a wyszukiwanie zastąpi tylko pierwszy.

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

Z komentarzem:

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

Nowa wersja GNU sedobsługuje tę -zopcję.

Zwykle sed czyta wiersz, czytając ciąg znaków do znaku końca linii (nowa linia lub znak powrotu karetki).
Wersja sed GNU dodała funkcję w wersji 4.2.2, aby zamiast tego używać znaku „NULL”. Może to być przydatne, jeśli masz pliki, które używają NULL jako separatora rekordów. Niektóre narzędzia GNU mogą generować dane wyjściowe, które używają NULL zamiast nowej linii, na przykład „find. -Print0” lub „grep -lZ”.

Możesz użyć tej opcji, jeśli chcesz sedpracować na różnych liniach.

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

zwraca

claudia
antonio
claudio
michele

1

Możesz użyć awkflagi, aby sprawdzić, czy wymiana została już wykonana. Jeśli nie, kontynuuj:

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

1

To naprawdę bardzo proste, jeśli skonfigurujesz tylko niewielkie opóźnienie - nie musisz sięgać po niewiarygodne rozszerzenia:

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

To po prostu odkłada pierwszą linię na drugą i drugą na trzecią itd.

Drukuje:

claudia
antonio
claudio
michele

1

I jeszcze jedna opcja

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

Zaletą jest to, że używa podwójnego cudzysłowu, dzięki czemu można używać zmiennych wewnątrz, tj.

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

1
Tak, masz rację. Ogólna idea jest taka sama. Ale proszę, spróbuj zastąpić pojedynczy, podwójny cudzysłów bezpośrednio i sprawdź, czy to działa. Diabeł tkwi w szczegółach. W tym przykładzie są to spacje i jedna ucieczka. Uważam, że ta kontynuacja wcześniejszych odpowiedzi może zaoszczędzić czyjś czas. I dlatego postanowiłem opublikować post.
utom

1

Można to również zrobić bez przestrzeni wstrzymania i bez powiązania wszystkich linii z przestrzenią wzorów:

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

Objaśnienie: Próbujemy znaleźć „claudio” i jeśli to zrobimy, przeskoczymy do małej pętli obciążenia drukowania między :xi bx. W przeciwnym razie drukujemy i restartujemy skrypt z następną linią.

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
Czy miałeś problem z przeczytaniem pytania?
don_crissti,

1

Sumaryczny

Składnia GNU:

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

Lub nawet (użyć tylko jednego słowa, które ma zostać zastąpione:

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

Lub w składni POSIX:

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

działa na każdym sed, przetwarza tylko tyle wierszy, ile potrzeba do znalezienia pierwszego claudio, działa nawet, jeśli claudioznajduje się w pierwszym wierszu i jest krótszy, ponieważ używa tylko jednego ciągu wyrażeń regularnych.

Szczegół

Aby zmienić tylko jedną linię , musisz wybrać tylko jedną linię.

Użycie 1,/claudio/(z twojego pytania) wybiera:

  • z pierwszej linii (bezwarunkowo)
  • do następnego wiersza zawierającego ciąg claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

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

Aby wybrać dowolny wiersz, który zawiera claudio:

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

Aby wybrać tylko pierwszy claudio plik, użyj:

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

Następnie możesz dokonać podstawienia tylko w tym wierszu:

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

Który zmieni tylko pierwsze wystąpienie dopasowania wyrażenia regularnego w linii, nawet jeśli może być więcej niż jeden, w pierwszym wierszu, który pasuje do wyrażenia regularnego.

Oczywiście /claudio/wyrażenie regularne można uprościć w celu:

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

A zatem jedyne, czego brakuje, to wydrukowanie wszystkich pozostałych wierszy niezmodyfikowanych:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.