Thay thế một chuỗi chứa các ký tự dòng mới


10

Với bashshell, trong một tệp có các hàng như sau

first "line"
<second>line and so on

Tôi muốn thay thế một hoặc nhiều lần xuất hiện "line"\n<second>với other charactersvà nhận được mỗi lần:

first other characters line and so on

Vì vậy, tôi phải thay thế một chuỗi cả bằng các ký tự đặc biệt như "<và bằng một ký tự dòng mới.

Sau khi tìm kiếm giữa các câu trả lời khác, tôi thấy rằng sedcó thể chấp nhận các dòng mới ở phía bên phải của lệnh (vì vậy, other characterschuỗi), nhưng không phải ở bên trái.

Có cách nào (đơn giản hơn thế này ) để có được kết quả này với sedhay grepkhông?


bạn đang làm việc với máy mac à? các \ntuyên bố ewline bạn thực hiện là lý do tại sao tôi hỏi. mọi người hiếm khi hỏi liệu họ có thể làm s//\n/như bạn có thể với GNU hay không sed, mặc dù hầu hết những người khác sedsẽ từ chối lối thoát đó ở phía bên tay phải. Tuy nhiên, \nlối thoát sẽ hoạt động ở bên trái trong bất kỳ POSIX nào sedvà bạn có thể dịch chúng một cách hợp lý như thế y/c/\n/mặc dù nó sẽ có tác dụng tương tự s/c/\n/gvà vì vậy không phải lúc nào cũng hữu ích.
mikeerv

Câu trả lời:


3

Ba sedlệnh khác nhau :

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Cả ba đều xây dựng trên s///lệnh ubstlation cơ bản :

s/"[^"]*"\n<[^>]*>/other characters /

Tất cả họ cũng cố gắng cẩn thận trong việc xử lý dòng cuối cùng, vì seds có xu hướng khác nhau về đầu ra của họ trong các trường hợp cạnh. Đây là ý nghĩa của $!một địa chỉ phù hợp với mọi dòng !không phải là $cuối cùng.

Tất cả chúng cũng sử dụng Nlệnh ext để nối dòng đầu vào tiếp theo vào không gian mẫu theo \nký tự ewline. Bất cứ ai đã làm sedviệc trong một thời gian sẽ học được cách dựa vào \nnhân vật ewline - bởi vì cách duy nhất để có được một người rõ ràng là đặt nó ở đó.

Cả ba đều thực hiện một số nỗ lực để đọc càng ít đầu vào càng tốt trước khi thực hiện hành động - sedhành động ngay khi có thể và không cần đọc trong toàn bộ tệp đầu vào trước khi thực hiện.

Mặc dù họ làm tất cả N, cả ba đều khác nhau về phương pháp đệ quy.

Lệnh đầu tiên

Lệnh đầu tiên sử dụng một N;P;Dvòng lặp rất đơn giản . Ba lệnh này được tích hợp sẵn cho bất kỳ tương thích POSIX nào sedvà chúng bổ sung cho nhau một cách độc đáo.

  • N- như đã đề cập, nối thêm Ndòng đầu vào ext vào không gian mẫu theo \ndấu phân cách ewline được chèn .
  • P- thích p; nó Pgợi ý không gian mẫu - nhưng chỉ tối đa đến \nký tự ewline xuất hiện đầu tiên . Và do đó, đưa ra đầu vào / lệnh sau:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Pgợi ý chỉ một . Tuy nhiên, với ...

  • D- thích d; nó Dxóa bỏ không gian mẫu và bắt đầu một chu trình dòng khác. Không giống như d , Dchỉ xóa tối đa \newline xuất hiện đầu tiên trong không gian mẫu. Nếu có nhiều hơn trong không gian mẫu theo \nký tự ewline, hãy sedbắt đầu chu trình dòng tiếp theo với những gì còn lại. Nếu dví dụ trước được thay thế bằng a D, ví dụ, sedsẽ Print cả mộthai .

Lệnh này chỉ đệ quy cho các dòng không khớp với s///tuyên bố ubstlation. Bởi vì s///ubstlation loại bỏ \newline được thêm vào N, không bao giờ có bất cứ điều gì còn lại khi sed Dxóa bỏ không gian mẫu.

Các thử nghiệm có thể được thực hiện để áp dụng Pvà / hoặc Dchọn lọc, nhưng có các lệnh khác phù hợp hơn với chiến lược đó. Bởi vì đệ quy được triển khai để xử lý các dòng liên tiếp chỉ khớp với một phần của quy tắc thay thế, các chuỗi liên tiếp khớp với cả hai đầu của s///ubstlation không hoạt động tốt.:

Cho đầu vào này:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... nó in ...

first other characters "line"
<second>other characters line and so on

Nó, tuy nhiên, xử lý

first "line"
second "line"
<second>line

...bình thường.

Bộ chỉ huy thứ hai

Lệnh này rất giống với lệnh thứ ba. Cả hai đều sử dụng nhãn :branch / test (như được thể hiện trong câu trả lời của Joeseph R. ở đây ) và tái diễn lại với điều kiện nhất định.

  • -e :n -e- sedtập lệnh di động sẽ phân định một :định nghĩa nhãn bằng \newline hoặc -ecâu lệnh xecut nội tuyến mới .
    • :n- định nghĩa một nhãn có tên n. Điều này có thể được trả lại cho bất cứ lúc nào với một trong hai bnhoặc tn.
  • tn- tlệnh est trở về nhãn đã chỉ định (hoặc, nếu không được cung cấp, thoát khỏi tập lệnh cho chu kỳ dòng hiện tại) nếu bất kỳ s///ubstlation nào kể từ khi nhãn được xác định hoặc do lần cuối được gọi là tests thành công.

Trong lệnh này, đệ quy xảy ra cho các dòng khớp. Nếu sedthay thế thành công mẫu bằng các ký tự khác , sedquay lại :nnhãn và thử lại. Nếu một s///ubstlation không được thực hiện tự động sedin không gian mẫu và bắt đầu chu kỳ dòng tiếp theo.

Điều này có xu hướng xử lý các chuỗi liên tiếp tốt hơn. Trường hợp cuối cùng thất bại, bản in này:

first other characters other characters other characters line and so on

Bộ ba

Như đã đề cập, logic ở đây rất giống với cuối cùng, nhưng thử nghiệm rõ ràng hơn.

  • /"$/bn- đây là sedbài kiểm tra. Vì blệnh ranch là một chức năng của địa chỉ này, nên sedsẽ chỉ bquay lại :nsau khi một \newline được nối thêm và không gian mẫu vẫn kết thúc bằng một "trích dẫn kép.

Có rất ít được thực hiện giữa Nbcàng tốt - theo cách này sedcó thể nhanh chóng thu thập chính xác càng nhiều đầu vào càng cần thiết để đảm bảo rằng dòng sau không thể phù hợp với quy tắc của bạn. Các s///ubstlation khác nhau ở đây ở chỗ nó sử dụng gcờ thùy - và do đó, nó sẽ thực hiện tất cả các thay thế cần thiết cùng một lúc. Cho đầu vào giống hệt lệnh này đầu ra giống hệt đến cuối cùng.


Xin lỗi cho câu hỏi tầm thường, nhưng ý nghĩa của nó là gì DATAvà làm thế nào để bạn nhận được văn bản nhập?
BowPark

@BowPark - Trong ví dụ <<\DATA\ntext input\nDATA\nnày được đưa vào, nhưng đó chỉ là văn bản được chuyển đến sedbởi trình bao trong tài liệu ở đây . Nó sẽ làm việc tốt như sed 'script' filenamehoặc process that writes to stdout | sed 'script'. cái đó có giúp ích không?
mikeerv

Có nó, cảm ơn bạn! Tại sao không có Dmỗi dòng sửa đổi là gấp đôi? (Bạn đã sử dụng nó khi cần thiết; có thể tôi không biết rõ sedlắm)
BowPark

1
@BowPark - bạn nhận được gấp đôi khi bỏ qua DDnếu không thì bỏ qua Dđầu ra những gì bạn thấy bây giờ tăng gấp đôi. Tôi vừa thực hiện một chỉnh sửa - và tôi cũng có thể sớm mở rộng về điều đó.
mikeerv

1
@BowPark - ok, tôi đã cập nhật nó và cung cấp các tùy chọn. Bây giờ có thể dễ đọc / dễ hiểu hơn một chút. Tôi cũng giải quyết rõ ràng Dđiều này.
mikeerv

7

Chà, tôi có thể nghĩ ra một vài cách đơn giản nhưng không liên quan grep(dù sao cũng không thay thế) hoặc sed.

  1. Perl

    Để thay thế từng xảy ra "line"\n<second>với other characters, sử dụng:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    Hoặc, để xử lý nhiều lần xuất hiện liên tiếp "line"\n<second>như một và thay thế tất cả chúng bằng một lần duy nhất other characters, sử dụng:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Thí dụ:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    Các -00nguyên nhân gây Perl để đọc các tập tin trong "chế độ đoạn" có nghĩa là "dòng" được định nghĩa bởi \n\nthay vì \n, về cơ bản, mỗi đoạn được coi là một đường thẳng. Do đó, sự thay thế phù hợp trên một dòng mới.

  2. ôi

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    Cùng một ý tưởng cơ bản, chúng tôi đặt dấu phân cách bản ghi ( RS) để \n\nlàm mờ toàn bộ tệp, sau đó phân tách bản ghi đầu ra thành không có gì (nếu không thì một dòng mới bổ sung được in) và sau đó sử dụng sub()chức năng để thay thế.


2
@mikeerv? Cái nào? Thứ hai được cho là, OP cho biết họ muốn "thay thế một hoặc nhiều lần xuất hiện", vì vậy việc ăn đoạn văn cũng có thể là điều họ mong đợi.
terdon

điểm rất tốt. Tôi đoán rằng tôi đã tập trung nhiều hơn vào và có được mỗi lần , nhưng tôi đoán không rõ liệu đó có phải là một lần thay thế cho mỗi lần xuất hiện hay một lần thay thế cho mỗi lần xuất hiện không ... @BowPark?
mikeerv

Nó là cần thiết một thay thế cho mỗi lần xuất hiện.
BowPark

@BowPark OK, sau đó cách tiếp cận perl đầu tiên hoặc awk nên hoạt động. Họ không cung cấp cho bạn đầu ra mong muốn?
terdon

Nó hoạt động, cảm ơn bạn, nhưng dòng thứ ba awknên được print;}' file. Tôi cần tránh Perl và tốt nhất là sử dụng sed, dù sao bạn cũng đề xuất các lựa chọn thay thế tốt.
BowPark

6

đọc toàn bộ tập tin và thực hiện thay thế toàn cầu:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last

Đúng. Nó hoạt động, nhưng nếu tôi có nhiều lần xuất hiện thì sao?
BowPark

Hừ, phải rồi. Đã sửa lỗi
glenn jackman

1
xin lỗi cho nitpick một lần nữa, nhưng ${cmds}là đặc thù của GNU - hầu hết các seds khác sẽ yêu cầu một \newline hoặc -enghỉ giữa p}. Bạn có thể tránh các dấu ngoặc hoàn toàn - và có thể di chuyển - và thậm chí tránh chèn thêm một \nký tự ewline trên dòng đầu tiên như:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeerv

Tôi đã thử nó và nó dường như không thể cầm tay được. Nó in thêm một dòng mới ở đầu ra, nhưng kết quả là đúng trên GNU.
BowPark

Để xóa dòng mới hàng đầu: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- tuy nhiên điều này đang trở nên không thể nhầm lẫn.
glenn jackman

3

Đây là một biến thể của câu trả lời của glenn sẽ hoạt động nếu bạn có nhiều lần xuất hiện liên tiếp (chỉ hoạt động với GNU sed):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

Đây :xchỉ là một nhãn cho phân nhánh. Về cơ bản, những gì nó làm, là nó kiểm tra dòng sau khi thay thế và nếu nó vẫn khớp "line", nó phân nhánh trở lại :xnhãn (đó là những gì bx) và thêm một dòng khác vào bộ đệm và bắt đầu xử lý nó.


@mikeerv Vui lòng nói cụ thể về ý của bạn. Nó làm việc cho tôi.
Joseph R.

@mikeerv Tôi xin lỗi, tôi thực sự không biết bạn đang nói về cái gì. Tôi đã sao chép dòng mã trên trở lại thiết bị đầu cuối của mình và nó hoạt động chính xác.
Joseph R.

1
rút lại - điều này rõ ràng hoạt động trong GNU sed, việc xử lý nhãn không phải POSIX của nó đủ xa để chấp nhận một khoảng trắng làm dấu phân cách để khai báo nhãn. Mặc dù vậy, bạn cần lưu ý rằng mọi thứ khác sedsẽ thất bại ở đó - và sẽ thất bại N. GNU sedphá vỡ các hướng dẫn POSIX để in không gian mẫu trước khi thoát khỏi Ndòng trên dòng cuối cùng, nhưng POSIX làm rõ rằng nếu một Nlệnh được đọc trên dòng cuối cùng thì không nên in gì.
mikeerv

Nếu bạn chỉnh sửa bài đăng để chỉ định GNU, tôi sẽ đảo ngược phiếu bầu của mình và xóa những bình luận này. Ngoài ra, có thể đáng để tìm hiểu về vlệnh của GNU phá vỡ mọi thứ khác sednhưng không có gì trong phiên bản GNU 4 trở lên.
mikeerv

1
trong trường hợp đó tôi sẽ cung cấp thêm một - điều này có thể được thực hiện một cách hợp lý như : sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeerv
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.