Làm thế nào tôi có thể sử dụng sed để thay thế một chuỗi nhiều dòng?


243

Tôi đã nhận thấy rằng, nếu tôi thêm \nvào một mẫu để thay thế bằng cách sử dụng sed, nó không phù hợp. Thí dụ:

$ cat > alpha.txt
This is
a test
Please do not
be alarmed

$ sed -i'.original' 's/a test\nPlease do not/not a test\nBe/' alpha.txt

$ diff alpha.txt{,.original}

$ # No differences printed out

Làm thế nào tôi có thể làm điều này để làm việc?


Giải pháp thay thế thông minh tại đây: unix.stackexchange.com/a/445666/61742 . Tất nhiên đó không phải là biểu diễn! Các tùy chọn tốt khác để thực hiện thay thế theo nhu cầu của bạn có thể là awk, perl và python. Có nhiều cái khác, nhưng tôi tin rằng awk là phổ biến nhất trong các bản phân phối Linux khác nhau (ví dụ). Cảm ơn!
Eduardo Lucio

Câu trả lời:


235

Trong cách gọi đơn giản nhất của sed , nó có một dòng văn bản trong không gian mẫu, tức là. 1 dòng \nvăn bản được phân tách từ đầu vào. Dòng duy nhất trong không gian mẫu không có \n... Đó là lý do tại sao regex của bạn không tìm thấy gì.

Bạn có thể đọc nhiều dòng vào không gian mẫu và điều khiển mọi thứ một cách đáng ngạc nhiên, nhưng với nỗ lực hơn bình thường .. Sed có một bộ lệnh cho phép loại điều này ... Dưới đây là liên kết đến Tóm tắt lệnh cho sed . Nó là thứ tốt nhất tôi tìm thấy và khiến tôi lăn lộn.

Tuy nhiên, hãy quên ý tưởng "một lớp lót" khi bạn bắt đầu sử dụng các lệnh vi mô của sed. Thật hữu ích khi đặt nó ra như một chương trình có cấu trúc cho đến khi bạn cảm nhận được nó ... Nó đơn giản đến bất ngờ, và không kém phần bất thường. Bạn có thể coi nó là "ngôn ngữ biên dịch" của chỉnh sửa văn bản.

Tóm tắt: Sử dụng sed cho những thứ đơn giản, và có thể hơn một chút, nhưng nói chung, khi nó vượt ra ngoài hoạt động với một dòng duy nhất, hầu hết mọi người thích một thứ khác ...
Tôi sẽ để người khác đề xuất một cái gì đó khác .. Tôi thực sự không chắc chắn sự lựa chọn tốt nhất sẽ là gì (tôi sẽ sử dụng sed, nhưng đó là vì tôi không biết rõ về perl.)


sed '/^a test$/{
       $!{ N        # append the next line when not on the last line
         s/^a test\nPlease do not$/not a test\nBe/
                    # now test for a successful substitution, otherwise
                    #+  unpaired "a test" lines would be mis-handled
         t sub-yes  # branch_on_substitute (goto label :sub-yes)
         :sub-not   # a label (not essential; here to self document)
                    # if no substituion, print only the first line
         P          # pattern_first_line_print
         D          # pattern_ltrunc(line+nl)_top/cycle
         :sub-yes   # a label (the goto target of the 't' branch)
                    # fall through to final auto-pattern_print (2 lines)
       }    
     }' alpha.txt  

Đây là cùng một kịch bản, cô đọng vào những gì rõ ràng là khó đọc và làm việc hơn, nhưng một số người sẽ gọi một cách đơn giản là một lớp lót

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt

Đây là lệnh của tôi "cheat-sheet"

:  # label
=  # line_number
a  # append_text_to_stdout_after_flush
b  # branch_unconditional             
c  # range_change                     
d  # pattern_delete_top/cycle          
D  # pattern_ltrunc(line+nl)_top/cycle 
g  # pattern=hold                      
G  # pattern+=nl+hold                  
h  # hold=pattern                      
H  # hold+=nl+pattern                  
i  # insert_text_to_stdout_now         
l  # pattern_list                       
n  # pattern_flush=nextline_continue   
N  # pattern+=nl+nextline              
p  # pattern_print                     
P  # pattern_first_line_print          
q  # flush_quit                        
r  # append_file_to_stdout_after_flush 
s  # substitute                                          
t  # branch_on_substitute              
w  # append_pattern_to_file_now         
x  # swap_pattern_and_hold             
y  # transform_chars                   

167
Bắn tôi ngay. Cú pháp tệ nhất từng có!
Gili

53
Đây là một lời giải thích tuyệt vời, nhưng tôi có khuynh hướng đồng ý với @Gili.
gatoatigrado

11
Cheat-sheet của bạn có tất cả.
konsolebox

3
Bạn không cần một nhãn để sử dụng tlệnh ở đây, khi không được cấp nhãn, nó mặc định sẽ phân nhánh đến cuối tập lệnh. Vì vậy, sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txtchính xác giống như lệnh của bạn trong mọi trường hợp. Tất nhiên đối với tệp cụ thể này , sed '/test/{N;s/.*/not a test\nBe/}' alpha.txtcũng thực hiện điều tương tự, nhưng ví dụ đầu tiên của tôi là tương đương logic cho tất cả các tệp có thể. Cũng lưu ý rằng \ntrong một chuỗi thay thế không tạo ra một dòng mới; bạn cần một dấu gạch chéo ngược `\` theo sau là một dòng mới thực sự để làm điều đó.
tự đại diện

9
Lưu ý rằng cú pháp đó là đặc thù của GNU ( #lệnh không được tách biệt với cú pháp trước đó, \ntrong RHS của s). Với GNU, sedbạn cũng có thể sử dụng -zđể sử dụng các bản ghi được phân tách bằng NUL (và sau đó luồn lách trong toàn bộ đầu vào nếu văn bản đó (theo định nghĩa không chứa NUL)).
Stéphane Chazelas

181

Sử dụng perlthay vì sed:

$ perl -0777 -i.original -pe 's/a test\nPlease do not/not a test\nBe/igs' alpha.txt
$ diff alpha.txt{,.original}
2,3c2,3
< not a test
< Be
---
> a test
> Please do not

-pi -elà chuỗi dòng lệnh "thay thế tại chỗ" tiêu chuẩn của bạn và -0777 gây ra lỗi toàn bộ cho các tệp. Xem perldoc perlrun để tìm hiểu thêm về nó.


3
Cảm ơn! Đối với công việc đa dòng, perl thắng tay! Tôi đã kết thúc bằng cách sử dụng `$ perl -pi -e 's / bar / baz /' fileA` để thay đổi tệp tại chỗ.
Nicholas Tolley Cottrell

3
Điều rất phổ biến là người đăng ban đầu yêu cầu sedvà trả lời bằng awk hoặc perl xuất hiện. Tôi nghĩ rằng nó không phải là về chủ đề, do đó, xin lỗi, nhưng tôi đã bắn một điểm trừ.
Rho Phi

68
+1 và không đồng ý với Roberto. Các câu hỏi thường được đặt ra đặc biệt cho sự thiếu hiểu biết về các phương pháp tốt hơn. Khi không có sự khác biệt theo ngữ cảnh thực tế (như ở đây), các giải pháp tối ưu sẽ nhận được ít nhất là nhiều hồ sơ như các câu hỏi cụ thể.
geotheory

56
Tôi nghĩ rằng sedcâu trả lời ở trên chứng tỏ rằng câu trả lời của Perl là về chủ đề.
rebierpost

7
Dễ dàng hơn một chút: Với "-p0e", "-0777" là không cần thiết. unix.stackexchange.com/a/181215/197502
Weidenrinde

96

Tôi nghĩ, tốt hơn là thay thế \nbiểu tượng bằng một số biểu tượng khác, và sau đó hoạt động như bình thường:

ví dụ mã nguồn không hoạt động:

cat alpha.txt | sed -e 's/a test\nPlease do not/not a test\nBe/'

có thể thay đổi thành:

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test\rPlease do not/not a test\rBe/'  | tr '\r' '\n'

Nếu bất cứ ai không biết, \nlà kết thúc dòng UNIX, \r\n- windows, \r- Mac OS cổ điển. Văn bản UNIX thông thường không sử dụng \rký hiệu, vì vậy an toàn khi sử dụng nó cho trường hợp này.

Bạn cũng có thể sử dụng một số biểu tượng kỳ lạ để tạm thời thay thế \ n. Ví dụ: \ f (biểu tượng nguồn cấp dữ liệu). Bạn có thể tìm thấy nhiều biểu tượng ở đây .

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'

11
+1 cho hack thông minh này! Đặc biệt hữu ích là lời khuyên về việc sử dụng một biểu tượng kỳ lạ để tạm thời thay thế dòng mới trừ khi bạn hoàn toàn chắc chắn về nội dung của tệp bạn đang chỉnh sửa.
L0j1k

Điều này không hoạt động như được viết trên OS X. Thay vào đó, người ta cần thay thế tất cả các trường hợp \rtrong đối số sedbằng $(printf '\r').
abeboparebop

@abeboparebop: tìm tuyệt vời! Cách khác, cài đặt GNU sed bằng homebrew: stackoverflow.com/a/30005262
ssc

@abeboparebop, Trên OSX, bạn chỉ cần thêm một $chuỗi trước chuỗi sed để ngăn nó chuyển đổi \rthành một r. Ví dụ ngắn : sed $'s/\r/~/'. Ví dụ đầy đủ:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wvducky

40

Tất cả mọi thứ được xem xét, ngấu nghiến toàn bộ tập tin có thể là cách nhanh nhất để đi.

Cú pháp cơ bản như sau:

sed -e '1h;2,$H;$!d;g' -e 's/__YOUR_REGEX_GOES_HERE__...'

Xin lưu ý, việc ngấu nghiến toàn bộ tệp có thể không phải là một lựa chọn nếu tệp quá lớn. Đối với những trường hợp như vậy, các câu trả lời khác được cung cấp ở đây cung cấp các giải pháp tùy chỉnh được đảm bảo hoạt động trên một dấu chân bộ nhớ nhỏ.

Đối với tất cả các tình huống hack và slash khác, chỉ cần chuẩn bị trước -e '1h;2,$H;$!d;g'theo sau bởi sedđối số regex ban đầu của bạn sẽ hoàn thành công việc.

ví dụ

$ echo -e "Dog\nFox\nCat\nSnake\n" | sed -e '1h;2,$H;$!d;g' -re 's/([^\n]*)\n([^\n]*)\n/Quick \2\nLazy \1\n/g'
Quick Fox
Lazy Dog
Quick Snake
Lazy Cat

Không gì -e '1h;2,$H;$!d;g'làm gì?

Các 1, 2,$, $!phụ tùng là dòng specifiers rằng giới hạn mà dòng lệnh trực tiếp sau chạy trên.

  • 1: Chỉ dòng đầu tiên
  • 2,$: Tất cả các dòng bắt đầu từ thứ hai
  • $!: Mỗi dòng khác nhau

Vì vậy, mở rộng, đây là những gì xảy ra trên mỗi dòng của đầu vào N dòng.

  1: h, d
  2: H, d
  3: H, d
  .
  .
N-2: H, d
N-1: H, d
  N: H, g

Các glệnh không được đưa ra một dòng specifier, nhưng trước dlệnh có một điều khoản đặc biệt " Bắt đầu chu kỳ tiếp theo. ", Và điều này ngăn gchạy trên tất cả các dòng ngoại trừ người cuối cùng.

Đối với ý nghĩa của từng lệnh:

  • Là người đầu tiên htiếp theo Hs trên mỗi dòng bản cho biết dòng đầu vào sedcủa không gian giữ . (Hãy suy nghĩ bộ đệm văn bản tùy ý.)
  • Sau đó, dloại bỏ từng dòng để ngăn những dòng này được ghi vào đầu ra. Các không gian tổ chức tuy nhiên được bảo tồn.
  • Cuối cùng, trên dòng cuối cùng, gkhôi phục sự tích lũy của mỗi dòng từ không gian giữ để sedcó thể chạy biểu thức chính của nó trên toàn bộ đầu vào (thay vì theo kiểu một thời điểm), và do đó có thể phù hợp trên \ns.

38

sedcó ba lệnh để quản lý hoạt động đa dòng: N, DP(so sánh chúng với bình thường n , dp).

Trong trường hợp này, bạn có thể khớp dòng đầu tiên của mẫu, sử dụng Nđể nối dòng thứ hai vào không gian mẫu và sau đó sử dụng sđể thay thế.

Cái gì đó như:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}

2
Điều này thật tuyệt! Đơn giản hơn câu trả lời được chấp nhận và vẫn hiệu quả.
jeyk

Và tất cả những người liên quan đến không gian giữ ( G, H, x...). Nhiều dòng có thể được thêm vào không gian mẫu bằng slệnh.
Stéphane Chazelas


giải pháp này không hoạt động với trường hợp sau "Đây là \ na test \ na test \ n Xin đừng lo lắng"
Mug896

@ Mug896 rất có thể bạn cần nhiều Nlệnh
loa_in_

15

Bạn có thể nhưng nó khó khăn . Tôi khuyên bạn nên chuyển sang một công cụ khác. Nếu có một biểu thức chính quy không bao giờ khớp với bất kỳ phần nào của văn bản bạn muốn thay thế, bạn có thể sử dụng nó như một dấu tách bản ghi awk trong GNU awk.

awk -v RS='a' '{gsub(/hello/, "world"); print}'

Nếu không bao giờ có hai dòng mới liên tiếp trong chuỗi tìm kiếm của bạn, bạn có thể sử dụng "chế độ đoạn" của awk (một hoặc nhiều dòng ghi riêng biệt).

awk -v RS='' '{gsub(/hello/, "world"); print}'

Một giải pháp dễ dàng là sử dụng Perl và tải tập tin đầy đủ vào bộ nhớ.

perl -0777 -pe 's/hello/world/g'

1
Làm thế nào để áp dụng lệnh perl cho một tập tin?
sebix 2/2/2016

2
@sebix perl -0777 -pe '…' <input-file >output-file. Để sửa đổi một tập tin tại chỗ,perl -0777 -i -pe '…' filename
Gilles

3
Xem thêm tùy chọn sedcủa GNU -z(được thêm vào năm 2012 sau khi câu trả lời đó được đăng) : seq 10 | sed -z 's/4\n5/a\nb/'.
Stéphane Chazelas

7

Tôi nghĩ rằng đây là giải pháp sed cho 2 dòng phù hợp.

sed -n '$!N;s@a test\nPlease do not@not a test\nBe@;P;D' alpha.txt

Nếu bạn muốn kết hợp 3 dòng thì ...

sed -n '1{$!N};$!N;s@aaa\nbbb\nccc@xxx\nyyy\nzzz@;P;D'

Nếu bạn muốn kết hợp 4 dòng thì ...

sed -n '1{$!N;$!N};$!N;s@ ... @ ... @;P;D'

Nếu phần thay thế trong dòng "s" co lại thì phức tạp hơn một chút như thế này

# aaa\nbbb\nccc shrink to one line "xxx"

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@xxx@;$!N;$!N};P;D'

Nếu phần thay thế phát triển các dòng thì phức tạp hơn một chút như thế này

# aaa\nbbb\nccc grow to five lines vvv\nwww\nxxx\nyyy\nzzz

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@vvv\nwww\nxxx\nyyy\nzzz@;P;s/.*\n//M;P;s/.*\n//M};P;D'

Điều này sẽ làm cho nó lên đến đỉnh! Tôi chỉ sử dụng "-i" thay vì "-n" cho thay thế hai dòng, bởi vì đó là những gì tôi cần, và tình cờ, nó cũng nằm trong ví dụ của người hỏi.
Nagev

5
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

Ở đây /a test/,/Please do not/được coi là một khối văn bản (nhiều dòng), clệnh thay đổi theo sau là văn bản mớinot a test \nBe

Trong trường hợp văn bản được thay thế là rất dài, tôi sẽ đề xuất cú pháp ex .


Rất tiếc, vấn đề là sed sẽ thay thế tất cả văn bản cuối cùng giữa / một bài kiểm tra / và / Xin đừng / cũng ... :(
noonex

4
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

Chỉ cần mở rộng cửa sổ của bạn vào đầu vào một chút.

Nó khá dễ. Bên cạnh sự thay thế tiêu chuẩn; bạn chỉ cần $!N, PDở đây.


4

Ngoài Perl, một cách tiếp cận chung và tiện dụng để chỉnh sửa đa dòng cho các luồng (và các tệp cũng vậy) là:

Trước tiên, hãy tạo một số dấu tách dòng UNIITE mới như bạn muốn

$ S=__ABC__                     # simple
$ S=__$RANDOM$RANDOM$RANDOM__   # better
$ S=$(openssl rand -hex 16)     # ultimate

Sau đó, trong lệnh sed của bạn (hoặc bất kỳ công cụ nào khác), bạn thay thế \ n bằng $ {S}, như

$ cat file.txt | awk 1 ORS=$S |  sed -e "s/a test${S}Please do not/not a test\nBe/" | awk 1 RS=$S > file_new.txt

(awk thay thế dấu tách dòng ASCII bằng dấu tách của bạn và ngược lại.)


2

Đây là một sửa đổi nhỏ trong câu trả lời thông minh của xara để làm cho nó hoạt động trên OS X (Tôi đang sử dụng 10.10):

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test$(printf '\r')Please do not/not a test$(printf '\r')Be/'  | tr '\r' '\n'

Thay vì sử dụng rõ ràng \r, bạn phải sử dụng $(printf '\r').


1
Mặc dù printf '\r'(hoặc echo -e '\r') hoạt động đúng, xin lưu ý rằng bạn chỉ có thể sử dụng cú pháp shell $'\r'để chỉ các chữ đã thoát. Ví dụ, echo hi$'\n'theresẽ lặp lại một dòng mới giữa hithere. Tương tự, bạn có thể quấn toàn bộ chuỗi để mỗi dấu gạch chéo ngược \ sẽ thoát khỏi ký tự tiếp theo của nó:echo $'hi\nthere'
Dejay Clayton

1

Tôi muốn thêm một vài dòng HTML vào một tệp bằng sed, (và kết thúc tại đây). Thông thường tôi chỉ sử dụng perl, nhưng tôi đã ở trong hộp có sed, bash và không nhiều thứ khác. Tôi thấy rằng nếu tôi thay đổi chuỗi thành một dòng duy nhất và để bash / sed nội suy \ t \ n thì mọi thứ đều hoạt động:

HTML_FILE='a.html' #contains an anchor in the form <a name="nchor" />
BASH_STRING_A='apples'
BASH_STRING_B='bananas'
INSERT="\t<li>$BASH_STRING_A<\/li>\n\t<li>$BASH_STRING_B<\/li>\n<a name=\"nchor\"\/>"
sed -i "s/<a name=\"nchor"\/>/$INSERT/" $HTML_FILE

Sẽ tốt hơn nếu có một chức năng để thoát khỏi dấu ngoặc kép và dấu gạch chéo về phía trước, nhưng đôi khi sự trừu tượng là kẻ trộm thời gian.


1

GNU sedcó một -ztùy chọn cho phép sử dụng cú pháp mà OP đã cố áp dụng. ( trang nam )

Thí dụ:

$ cat alpha.txt
This is
a test
Please do not
be alarmed
$ sed -z 's/a test\nPlease do not\nbe/not a test\nBe/' -i alpha.txt
$ cat alpha.txt
This is
not a test
Be alarmed

Lưu ý: Nếu bạn sử dụng ^$bây giờ chúng khớp với đầu và cuối của dòng được phân cách bằng ký tự NUL (không \n). Và, để đảm bảo các trận đấu trên tất cả các \ndòng ( tách riêng) của bạn được thay thế, đừng quên sử dụng gcờ cho các thay thế toàn cầu (ví dụ:s/.../.../g ).


Tín dụng: @ stéphane-chazelas lần đầu tiên được đề cập -z trong một nhận xét ở trên.


0

Sed phá vỡ đầu vào trên dòng mới. Nó chỉ giữ một dòng trên mỗi vòng lặp.
Do đó, không có cách nào khớp với \n(dòng mới) nếu không gian mẫu không chứa nó.

Có một cách, mặc dù, bạn có thể làm cho sed giữ hai dòng liên tiếp trong không gian mẫu bằng cách sử dụng vòng lặp:

sed 'N;l;P;D' alpha.txt

Thêm bất kỳ xử lý cần thiết giữa N và P (thay thế l).

Trong trường hợp này (2 dòng):

$ sed 'N;s/a test\nPlease do not/not a test\nBe/;P;D' alpha.txt
This is
not a test
Be
be alarmed

Hoặc, cho ba dòng:

$ sed -n '1{$!N};$!N;s@a test\nPlease do not\nbe@not a test\nDo\nBe@;P;D' alpha.txt 
This is
not a test
Do
Be alarmed

Đó là giả sử cùng một lượng dòng được thay thế.

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.