Sed ile yalnızca ilk oluşumunu değiştirmek ister misiniz


26

Orijinal dosya

claudio
antonio
claudio
michele

Dosya sonucu sadece "claudio" nın ilk oluşumunu "claudia" ile değiştirmek istiyorum

claudia
antonio
claudio
michele

denedim

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

Ancak küresel bir ikame gerçekleştirin. Neden?


Buraya bak linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/... ve ayrıca info sed: ( 0,/REGEXP/: 0 ise satır numarası gibi bir adres şartnamede kullanılabilecek 0,/REGEXP/şekilde sedçok ilk giriş satırına Regexp'i eşleştirmeye çalışacaktır Başka bir deyişle,. 0,/REGEXP/Olan benzer şekilde 1,/REGEXP/, eğer ADDR2 girişin ilk satırına denk gelirse, 0, / REGEXP / form aralığı sonlandırmayı düşünürken, 1, / REGEXP / form aralığının başlangıcıyla eşleşir ve böylece aralık düzenli ifadenin ikinci oluşumuna kadar )
jimmij


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

Yanıtlar:


23

GNU kullanıyorsanız sed, şunu deneyin:

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

sedkadar bir dizi biter regex kontrol başlamaz sonra o başlar aralığında bu hat.

Gönderen man sed(POSIX manpage, benimkini vurgula):

İki adresli bir düzenleme komutu kapsayıcı menzili seçmelidir
aracılığıyla ilk adresiyle eşleşen ilk desen uzaydanikincisi ile eşleşen bir 
sonraki kalıp alanı . 

kullanma awk

Beklediğinizden awkdaha fazla çalışma alanı:

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

Açıklama:

  • NR==1,/claudio/

    Bu, satır 1 ile başlayan ve ilk oluşumu ile biten bir aralıktır claudio.

  • sub(/claudio/, "claudia")

    Biz menzil içindeyken, bu değiştirme komutu yerine getirilir.

  • 1

    Bu awk'ın şifreli kestirme çizgisi yazdırılıyor.


1
Bu sedolsa GNU varsayar .
Stéphane Chazelas

@ StéphaneChazelas POSIXLY_CORRECT ayarlanmışsa da çalışır ancak sanırım istediğim kadar demek değil. Cevap güncellendi (BSD test makineleri için yetersizim).
John1024

Awk can, IMO, bir boolean durum değişkeni ile daha basit olabilir:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman

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

Ayrıca ilk bölümde farklı bir sınırlayıcıyı başarıyla kullanamadım:0,/claudio/
Pat Myron

4

İşte sed ile ilgili 2 programatik çaba daha: ikisi de tüm dosyayı tek bir dizgede okudular, sonra arama sadece birincisini değiştirecek.

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

Yorum ile:

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

GNU'nun yeni bir sürümü sedbu -zseçeneği desteklemektedir .

Normalde sed, satır sonundaki karaktere kadar (yeni satır veya satır başı) bir karakter dizisi okuyarak satır okur.
Sed'nin GNU sürümü, bunun yerine "NULL" karakterini kullanmak için sürüm 4.2.2'de bir özellik ekledi. NULL bir kayıt ayırıcı olarak kullanan dosyalarınız varsa, bu yararlı olabilir. Bazı GNU yardımcı programları, "find. -Print0" veya "grep -lZ" gibi yeni bir satır yerine NULL kullanan çıktı üretebilir.

sedFarklı çizgiler üzerinde çalışmak istediğinizde bu seçeneği kullanabilirsiniz .

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

döner

claudia
antonio
claudio
michele

1

awkDeğişikliğin yapılıp yapılmadığını bilmek için bir bayrakla kullanabilirsiniz . Eğer değilse, devam edin:

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

1

Sadece biraz gecikme ayarladıysanız gerçekten çok kolay - güvenilmez uzantılara ulaşmaya gerek yok:

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

Bu sadece birinci satırı ikinciye, ikinciyi üçüncü ve diğerlerine karşı korur.

Yazdırır:

claudia
antonio
claudio
michele

1

Ve bir seçenek daha

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

Bunun avantajı, çift tırnak kullanmasıdır, yani içerisindeki değişkenleri kullanabilirsiniz.

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

1
Evet, haklısın. Genel fikir aynı. Ancak, lütfen, tekil yerine, doğrudan çift tırnak içine koymaya çalışın ve çalışıp çalışmadığını görün. Şeytan ayrıntılarda yatıyor. Bu örnekte bunlar uzaylar ve bir kaçış. Daha önceki cevapların bu devamının birinin zaman kazanabileceğine inanıyorum. İşte bu yüzden yazıyı yayınlamaya karar verdim.
16'da

1

Bu, bekletme alanı olmadan ve tüm çizgileri desen alanıyla birleştirmeden de yapılabilir:

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

Açıklama: Biz, "claudio" bulmaya ve bunu yaparsak biz arasındaki küçük baskı-yük döngü içine atlamak :xve bx. Aksi takdirde bir sonraki satırla birlikte betiği basar ve yeniden başlatırız.

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
Soruyu okumaktan rahatsız oldunuz mu?
don_crissti

1

Sumary

GNU sözdizimi:

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

Veya bile (değiştirilecek kelimeyi yalnızca bir kez kullanmak için:

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

Veya POSIX sözdiziminde:

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

Herhangi bir sed üzerinde çalışır, yalnızca ilkini bulmak için gereken kadar satır işlerclaudio işler claudio, ilk satırda olsa bile çalışır ve yalnızca bir regex dizesi kullandığından daha kısadır.

detay

Yalnızca bir satırı değiştirmek için seçmeniz gerekir sadece bir satır.

Bir kullanarak 1,/claudio/(sorunuzdan) seçer:

  • ilk satırdan (koşulsuz)
  • için bir sonraki dizeyi içeren çizgi claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

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

İçerdiği herhangi bir satırı seçmek için şunu claudiokullanın:

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

Ve yalnızca dosyadaki ilkini seçmek için şunu claudiokullanın:

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

Ardından, yalnızca bu satırda değişiklik yapabilirsiniz:

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

Hangi sadece değişecek ilk Birden fazla olabilir bile üzerine, hat üzerinde regex maçın oluşumunu ilk satırda.

Tabii ki, /claudio/regex basitleştirilebilir:

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

Ve sonra, eksik olan tek şey değiştirilmemiş diğer tüm satırları yazdırmaktır:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.