Ingin mengganti kejadian pertama dengan sed

26

File asli

claudio
antonio
claudio
michele

Saya hanya ingin mengubah kemunculan pertama "claudio" dengan "claudia" jadi hasilnya file

claudia
antonio
claudio
michele

saya telah mencoba

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

Tetapi lakukan substitusi global. Mengapa?

elbarna
sumber
Lihat di sini linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… dan juga info sed: ( 0,/REGEXP/: Sejumlah baris 0 dapat digunakan dalam spesifikasi alamat seperti 0,/REGEXP/sehingga sedakan mencoba mencocokkan REGEXP pada baris input pertama juga. Dengan kata lain, 0,/REGEXP/adalah mirip dengan 1,/REGEXP/, kecuali bahwa jika ADDR2 cocok dengan baris pertama input 0, / REGEXP / form akan menganggapnya untuk mengakhiri rentang, sedangkan bentuk 1, / REGEXP / akan cocok dengan awal rentangnya dan karenanya membuat rentang rentang hingga kemunculan kedua dari ekspresi reguler)
jimmij
awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomiharus dilakukan
Adam Katz
stackoverflow.com/questions/148451/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:

23

Jika Anda menggunakan GNU sed, coba:

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

sedtidak mulai memeriksa regex yang mengakhiri rentang sampai setelah garis yang memulai rentang itu.

Dari man sed(Halaman manual POSIX, beri penekanan pada tambang):

Perintah pengeditan dengan dua alamat harus memilih rentang inklusif
dari ruang pola pertama yang cocok dengan alamat pertama melalui para
ruang pola selanjutnya yang cocok dengan yang kedua. 

Menggunakan awk

Kisaran dalam awkpekerjaan lebih banyak seperti yang Anda harapkan:

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

Penjelasan:

  • NR==1,/claudio/

    Ini adalah rentang yang dimulai dengan baris 1 dan berakhir dengan kemunculan pertama claudio.

  • sub(/claudio/, "claudia")

    Sementara kita berada dalam jangkauan, perintah pengganti ini dijalankan.

  • 1

    Steno samar cryptic ini untuk mencetak baris.

John1024
sumber
1
Itu mengasumsikan GNU sed.
Stéphane Chazelas
@ StéphaneChazelas Ini juga berfungsi jika POSIXLY_CORRECT diset tetapi saya kira itu tidak berarti sebanyak yang saya inginkan. Jawaban diperbarui (Saya kurang untuk mesin uji BSD).
John1024
Awk can, IMO, lebih sederhana dengan variabel status boolean:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman
@glennjackman atauawk !x{x=sub(/claudio/,"claudia")}1
Saya juga tidak berhasil menggunakan pembatas yang berbeda di bagian pertama:0,/claudio/
Pat Myron
4

Berikut adalah 2 upaya lebih terprogram dengan sed: mereka berdua membaca seluruh file menjadi satu string, maka pencarian hanya akan menggantikan yang pertama.

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

Dengan komentar:

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
sumber
3

Versi baru GNU sedmendukung -zopsi ini.

Biasanya, sed membaca sebuah baris dengan membaca serangkaian karakter hingga karakter end-of-line (baris baru atau carriage return).
Versi sed GNU menambahkan fitur dalam versi 4.2.2 untuk menggunakan karakter "NULL" sebagai gantinya. Ini bisa bermanfaat jika Anda memiliki file yang menggunakan NULL sebagai pemisah rekaman. Beberapa utilitas GNU dapat menghasilkan output yang menggunakan NULL sebagai gantinya baris baru, seperti "find. -Print0" atau "grep -lZ".

Anda dapat menggunakan opsi ini ketika Anda ingin sedbekerja di baris yang berbeda.

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

kembali

claudia
antonio
claudio
michele
Walter A
sumber
1

Anda dapat menggunakan awkdengan bendera untuk mengetahui apakah penggantian sudah dilakukan. Jika tidak, lanjutkan:

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

Ini sebenarnya sangat mudah jika Anda hanya mengatur sedikit keterlambatan - tidak perlu menjangkau ekstensi yang tidak dapat diandalkan:

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

Itu hanya mempertahankan baris pertama ke kedua dan kedua ke ketiga dan lain-lain

Mencetak:

claudia
antonio
claudio
michele
mikeserv
sumber
1

Dan satu opsi lagi

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

Keuntungannya adalah menggunakan kutip ganda, sehingga Anda dapat menggunakan variabel di dalamnya, yaitu.

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi
utom
sumber
1
Ya kamu benar. Ide umumnya sama. Tapi, tolong, cobalah untuk mengganti tunggal, menjadi tanda kutip ganda secara langsung, dan lihat apakah itu berfungsi. Iblis terletak pada detailnya. Dalam contoh ini, ini adalah ruang dan satu jalan keluar. Saya percaya bahwa kelanjutan dari jawaban sebelumnya dapat menghemat waktu seseorang. Dan itulah alasan mengapa saya memutuskan untuk menerbitkan posting.
utom
1

Ini juga dapat dilakukan tanpa ruang pegang dan tanpa menggabungkan semua garis ke dalam ruang pola:

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

Penjelasan: Kami mencoba menemukan "claudio" dan jika kami melakukannya, kami beralih ke loop print-load-kecil antara :xdan bx. Kalau tidak, kita mencetak dan memulai ulang skrip dengan baris berikutnya.

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
sumber
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
sumber
1
Pernahkah Anda repot membaca pertanyaan?
don_crissti
1

Ringkasan

Sintaks GNU:

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

Atau genap (hanya menggunakan satu kali kata yang akan diganti:

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

Atau, dalam sintaks POSIX:

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

bekerja pada sed apa saja, proses hanya sebanyak garis yang diperlukan untuk menemukan yang pertama claudio, bekerja bahkan jika claudioberada di baris pertama dan lebih pendek karena hanya menggunakan satu string regex.

Detail

Untuk mengubah hanya satu baris, Anda hanya perlu memilih satu baris.

Menggunakan 1,/claudio/(dari pertanyaan Anda) memilih:

  • dari baris pertama (tanpa syarat)
  • ke baris berikutnya yang berisi string claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

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

Untuk memilih setiap baris yang berisi claudio, gunakan:

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

Dan untuk memilih hanya yang pertama claudio dalam file, gunakan:

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

Kemudian, Anda hanya dapat melakukan substitusi pada baris itu:

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

Yang hanya akan mengubah kemunculan pertama pertandingan regex di telepon, meskipun mungkin ada lebih dari satu, di baris pertama yang cocok dengan regex.

Tentu saja, /claudio/regex dapat disederhanakan untuk:

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

Dan, kemudian, satu-satunya hal yang hilang adalah mencetak semua baris lain yang tidak dimodifikasi:

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