Muốn thay thế chỉ xảy ra lần đầu tiên với sed


26

Tập tin gốc

claudio
antonio
claudio
michele

Tôi muốn chỉ thay đổi lần xuất hiện đầu tiên của "claudio" bằng "claudia" để kết quả tập tin

claudia
antonio
claudio
michele

Tôi đã thử

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

Nhưng thực hiện một sự thay thế toàn cầu. Tại sao?


Nhìn đây linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/... và cũng info sed: ( 0,/REGEXP/: Một số dòng từ 0 có thể được sử dụng trong một đặc điểm kỹ thuật địa chỉ như 0,/REGEXP/vậy mà sedsẽ cố gắng để phù hợp với REGEXP trong dòng đầu vào đầu tiên quá Nói cách khác,. 0,/REGEXP/Là tương tự 1,/REGEXP/, ngoại trừ nếu ADDR2 khớp với dòng đầu tiên đầu tiên thì 0, / REGEXP / biểu mẫu sẽ xem xét nó kết thúc phạm vi, trong khi biểu mẫu 1, / REGEXP / sẽ khớp với đầu phạm vi của nó và do đó tạo ra khoảng phạm vi cho đến lần xuất hiện thứ hai của biểu thức chính quy)
jimmij


awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nominên làm
Adam Katz

Câu trả lời:


23

Nếu bạn đang sử dụng GNU sed, hãy thử:

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

sedkhông bắt đầu kiểm tra regex kết thúc một phạm vi cho đến sau khi dòng bắt đầu phạm vi đó.

Từ man sed(POSIX manpage, nhấn mạnh của tôi):

Lệnh chỉnh sửa có hai địa chỉ sẽ chọn phạm vi bao gồm
từ không gian mô hình đầu tiên mà phù hợp với địa chỉ đầu tiên thông qua các
không gian mô hình tiếp theo phù hợp với thứ hai. 

Sử dụng awk

Phạm vi trong awkcông việc nhiều hơn bạn mong đợi:

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

Giải trình:

  • NR==1,/claudio/

    Đây là một phạm vi bắt đầu với dòng 1 và kết thúc bằng lần xuất hiện đầu tiên của claudio.

  • sub(/claudio/, "claudia")

    Trong khi chúng ta ở trong phạm vi, lệnh thay thế này được thực thi.

  • 1

    Tốc ký mật mã này của awk để in dòng.


1
Điều đó giả định GNU sedmặc dù.
Stéphane Chazelas

@ StéphaneChazelas Nó cũng hoạt động nếu POSIXLY_CORRECT được đặt nhưng tôi đoán điều đó không có ý nghĩa nhiều như tôi muốn. Trả lời cập nhật (Tôi thiếu cho các máy kiểm tra BSD).
John1024

Awk có thể, IMO, đơn giản hơn với biến trạng thái boolean:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman

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

Tôi cũng không thể sử dụng thành công một dấu phân cách khác trong phần đầu tiên:0,/claudio/
Pat Myron

4

Dưới đây là 2 nỗ lực lập trình nữa với sed: cả hai đều đọc toàn bộ tệp thành một chuỗi, sau đó tìm kiếm sẽ chỉ thay thế chuỗi đầu tiên.

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

Với lời bình luận:

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

Một phiên bản mới của GNU sedhỗ trợ -ztùy chọn.

Thông thường, sed đọc một dòng bằng cách đọc một chuỗi các ký tự cho đến ký tự cuối dòng (dòng mới hoặc trả về vận chuyển).
Phiên bản GNU của sed đã thêm một tính năng trong phiên bản 4.2.2 để sử dụng ký tự "NULL". Điều này có thể hữu ích nếu bạn có các tệp sử dụng NULL làm dấu tách bản ghi. Một số tiện ích GNU có thể tạo đầu ra sử dụng NULL thay vì một dòng mới, chẳng hạn như "find. -Print0" hoặc "grep -lZ".

Bạn có thể sử dụng tùy chọn này khi bạn muốn sedlàm việc trên các dòng khác nhau.

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

trả lại

claudia
antonio
claudio
michele

1

Bạn có thể sử dụng awkvới một cờ để biết thay thế đã được thực hiện. Nếu không, tiến hành:

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

1

Nó thực sự dễ dàng nếu bạn chỉ cần thiết lập một chút chậm trễ - không cần phải tiếp cận các tiện ích mở rộng không đáng tin cậy:

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

Điều đó chỉ trì hoãn dòng đầu tiên đến thứ hai và thứ hai đến thứ ba và vv

Nó in:

claudia
antonio
claudio
michele

1

Và thêm một lựa chọn

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

Ưu điểm là nó sử dụng báo giá kép, vì vậy bạn có thể sử dụng các biến bên trong, tức là.

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

1
Uh, đúng vậy. Ý tưởng chung là như nhau. Nhưng, xin vui lòng, hãy thử thay thế trực tiếp, thành dấu ngoặc kép trực tiếp và xem nếu nó hoạt động. Ma quỷ nằm trong các chi tiết. Trong ví dụ này đây là không gian và một lối thoát. Tôi tin rằng việc tiếp tục các câu trả lời trước đó có thể tiết kiệm thời gian của ai đó. Và đó là lý do tại sao tôi quyết định xuất bản bài viết.
utom

1

Điều này cũng có thể được thực hiện mà không có không gian giữ và không kết hợp tất cả các dòng vào không gian mẫu:

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

Giải thích: Chúng tôi cố gắng tìm "claudio" và nếu chúng tôi làm điều đó, chúng tôi sẽ nhảy vào vòng lặp tải-in nhỏ giữa :xbx. Nếu không, chúng tôi in và khởi động lại tập lệnh với dòng tiếp theo.

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
Bạn đã bận tâm đọc câu hỏi?
don_crissti

1

Tổng hợp

Cú pháp GNU:

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

Hoặc thậm chí (chỉ sử dụng một lần từ được thay thế:

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

Hoặc, theo cú pháp POSIX:

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

hoạt động trên bất kỳ sed, chỉ xử lý nhiều dòng cần thiết để tìm đầu tiên claudio, hoạt động ngay cả khiclaudio ở dòng đầu tiên và ngắn hơn vì nó chỉ sử dụng một chuỗi regex.

Chi tiết

Để chỉ thay đổi một dòng, bạn chỉ cần chọn một dòng.

Sử dụng một 1,/claudio/(từ câu hỏi của bạn) chọn:

  • từ dòng đầu tiên (vô điều kiện)
  • đến dòng tiếp theo chứa chuỗi claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

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

Để chọn bất kỳ dòng nào có chứa claudio, sử dụng:

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

Và để chỉ chọn đầu tiên claudio trong tệp, sử dụng:

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

Sau đó, bạn chỉ có thể thay thế trên dòng đó:

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

Điều này sẽ chỉ thay đổi lần xuất hiện đầu tiên của trận đấu regex trên dòng, ngay cả khi có thể có nhiều hơn một, trên lần đầu tiên dòng khớp với biểu thức chính quy.

Tất nhiên, /claudio/regex có thể được đơn giản hóa thành:

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

Và, sau đó, điều duy nhất còn thiếu là in tất cả các dòng khác chưa được sửa đổi:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.