Trận đấu không tham lam với regex SED (giả lập perl's. *?)


17

Tôi muốn sử dụng sedđể thay thế bất cứ điều gì trong một chuỗi giữa lần xuất hiện đầu tiên ABlần đầu tiênAC (bao gồm) với XXX.

dụ: tôi có chuỗi này (chuỗi này chỉ dành cho thử nghiệm):

ssABteAstACABnnACss

và tôi muốn đầu ra tương tự như thế này : ssXXXABnnACss.


Tôi đã làm điều này với perl:

$ echo 'ssABteAstACABnnACss' | perl -pe 's/AB.*?AC/XXX/'
ssXXXABnnACss

nhưng tôi muốn thực hiện nó với sed. Các mục sau (sử dụng biểu thức tương thích Perl) không hoạt động:

$ echo 'ssABteAstACABnnACss' | sed -re 's/AB.*?AC/XXX/'
ssXXXss

2
Điều này không có ý nghĩa. Bạn có một giải pháp làm việc trong Perl, nhưng bạn muốn sử dụng Sed, tại sao?
Kusalananda

Câu trả lời:


12

Regexes sed phù hợp với trận đấu dài nhất. Sed không có tương đương với không tham lam.

Rõ ràng những gì chúng ta muốn làm là phù hợp

  1. AB,
    tiếp theo là
  2. bất kỳ số lượng nào khác hơn AC,
    theo sau là
  3. AC

Thật không may, sedkhông thể làm # 2 - ít nhất là không cho biểu thức chính quy nhiều ký tự. Tất nhiên, đối với một biểu thức chính quy đơn ký tự, chẳng hạn như @(hoặc thậm chí [123]), chúng ta có thể làm [^@]*hoặc [^123]*. Và như vậy chúng ta có thể làm việc xung quanh những hạn chế sed bằng cách thay đổi tất cả các lần xuất hiện của ACđể @và sau đó tìm kiếm

  1. AB,
    tiếp theo là
  2. bất kỳ số lượng nào khác @,
    theo sau là
  3. @

như thế này:

sed 's/AC/@/g; s/AB[^@]*@/XXX/; s/@/AC/g'

Phần cuối thay đổi các trường hợp chưa từng có của @trở lại AC.

Nhưng, tất nhiên, đây là một cách tiếp cận liều lĩnh, bởi vì đầu vào có thể chứa các @ký tự, vì vậy, bằng cách khớp chúng, chúng ta có thể nhận được kết quả dương tính giả. Tuy nhiên, vì không có biến shell nào có ký tự NUL ( \x00) trong đó, NUL có thể là một nhân vật tốt để sử dụng trong công việc trên thay vì @:

$ echo 'ssABteAstACABnnACss' | sed 's/AC/\x00/g; s/AB[^\x00]*\x00/XXX/; s/\x00/AC/g'
ssXXXABnnACss

Việc sử dụng NUL yêu cầu GNU sed. (Để đảm bảo các tính năng GNU được bật, người dùng không được đặt biến shell POSIXLY_CORRECT.)

Nếu bạn đang sử dụng sed với -zcờ của GNU để xử lý đầu vào được phân tách bằng NUL, chẳng hạn như đầu ra của find ... -print0, thì NUL sẽ không nằm trong không gian mẫu và NUL là một lựa chọn tốt để thay thế ở đây.

Mặc dù NUL không thể nằm trong biến bash, nhưng có thể đưa nó vào printflệnh. Nếu chuỗi đầu vào của bạn có thể chứa bất kỳ ký tự nào, kể cả NUL, thì hãy xem câu trả lời của Stéphane Chazelas có thêm phương thức thoát thông minh.


Tôi chỉ chỉnh sửa câu trả lời của bạn để thêm một lời giải thích dài dòng; cảm thấy tự do để cắt nó hoặc cuộn lại.
G-Man nói 'Phục hồi Monica'

@ G-Man Đó là một lời giải thích tuyệt vời! Hoàn thành rất tốt. Cảm ơn bạn.
John1024

Bạn có thể echohoặc printfmột `\ 000 'tốt trong bash (hoặc đầu vào có thể đến từ một tệp). Nhưng nói chung, một chuỗi văn bản dĩ nhiên không có NUL.
ilkkachu

@ilkkachu Bạn nói đúng về điều đó. Điều tôi nên viết là không có biến hoặc tham số shell nào có thể chứa NUL. Trả lời cập nhật.
John1024

Điều này sẽ không được an toàn hơn một toàn bộ rất nhiều nếu bạn thay đổi ACđể AC@và ngược lại?
Michael Vehrs

7

Một số sedtriển khai có hỗ trợ cho điều đó. ssedcó chế độ PCRE:

ssed -R 's/AB.*?AC/XXX/g'

AT & T ast sed có sự kết hợp và phủ định khi sử dụng regexps tăng cường :

sed -A 's/AB(.*&(.*AC.*)!)AC/XXX/g'

Có thể sử dụng kỹ thuật này: thay thế chuỗi kết thúc (ở đây AC) bằng một ký tự duy nhất không xảy ra trong chuỗi đầu hoặc cuối (như :ở đây) để bạn có thể thực hiện s/AB[^:]*://và trong trường hợp ký tự đó có thể xuất hiện trong đầu vào , sử dụng cơ chế thoát không đụng độ với chuỗi bắt đầu và kết thúc.

Một ví dụ:

sed 's/_/_u/g; # use _ as the escape character, escape it
     s/:/_c/g; # escape our replacement character
     s/AC/:/g; # replace the end string
     s/AB[^:]*:/XXX/g; # actual replacement
     s/:/AC/g; # restore the remaining end strings
     s/_c/:/g; # revert escaping
     s/_u/_/g'

Với GNU sed, một cách tiếp cận là sử dụng dòng mới làm ký tự thay thế. Vì sedxử lý từng dòng một, dòng mới không bao giờ xảy ra trong không gian mẫu, do đó, người ta có thể thực hiện:

sed 's/AC/\n/g;s/AB[^\n]*\n/XXX/g;s/\n/AC/g'

Điều đó thường không hoạt động với các sedtriển khai khác vì chúng không hỗ trợ [^\n]. Với GNU, sedbạn phải đảm bảo rằng tính tương thích POSIX không được bật (như với biến môi trường POSIXLY_CORRECT).


5

Không, sed regexes không có kết hợp không tham lam.

Bạn có thể kết hợp tất cả các văn bản cho đến lần xuất hiện đầu tiên ACbằng cách sử dụng bất cứ thứ gì không có tên ACtheo sau AC, cũng giống như của Perl .*?AC. Vấn đề là, bất cứ thứ gì không chứa, ACkhông thể được biểu thị dễ dàng như một biểu thức thông thường: luôn có một biểu thức chính quy nhận ra sự phủ định của biểu thức chính quy, nhưng biểu thức phủ định trở nên phức tạp nhanh chóng. Và trong sed di động, điều này hoàn toàn không thể, bởi vì regex phủ định yêu cầu nhóm một sự thay thế có trong các biểu thức chính quy mở rộng (ví dụ như trong awk) nhưng không phải trong các biểu thức chính quy cơ bản di động. Một số phiên bản của sed, chẳng hạn như GNU sed, có các phần mở rộng cho BRE giúp nó có thể diễn đạt tất cả các biểu thức chính quy có thể.

sed 's/AB\([^A]*\|A[^C]\)*A*AC/XXX/'

Do khó khăn trong việc phủ nhận một biểu thức chính quy, điều này không khái quát tốt. Thay vào đó, những gì bạn có thể làm là thay đổi dòng tạm thời. Trong một số triển khai sed, bạn có thể sử dụng dòng mới làm điểm đánh dấu, vì chúng không thể xuất hiện trong một dòng đầu vào (và nếu bạn cần nhiều điểm đánh dấu, hãy sử dụng dòng mới theo sau là một ký tự khác nhau).

sed -e 's/AC/\
&/g' -e 's/AB[^\
]*\nAC/XXX/' -e 's/\n//g'

Tuy nhiên, hãy cẩn thận với dấu gạch chéo ngược mới không hoạt động trong một bộ ký tự với một số phiên bản sed. Cụ thể, điều này không hoạt động trong GNU sed, đó là triển khai sed trên Linux không nhúng; trong GNU sed bạn có thể sử dụng \nthay thế:

sed -e 's/AC/\
&/g' -e 's/AB[^\n]*\nAC/XXX/' -e 's/\n//g'

Trong trường hợp cụ thể này, nó đủ để thay thế đầu tiên ACbằng một dòng mới. Cách tiếp cận tôi trình bày ở trên là tổng quát hơn.

Một cách tiếp cận mạnh mẽ hơn trong sed là lưu dòng vào không gian giữ, loại bỏ tất cả trừ phần thú vị đầu tiên của dòng, trao đổi không gian giữ và không gian mẫu hoặc nối không gian mẫu vào không gian giữ và lặp lại. Tuy nhiên, nếu bạn bắt đầu làm những việc phức tạp như vậy, bạn thực sự nên nghĩ về việc chuyển sang awk. Awk cũng không có kết hợp không tham lam, nhưng bạn có thể tách một chuỗi và lưu các phần thành các biến.


@ilkkachu Không, không. s/\n//gloại bỏ tất cả các dòng mới.
Gilles 'SO- ngừng trở nên xấu xa'

asdf. Phải, xấu của tôi.
ilkkachu

3

sed - kết hợp không tham lam của Christoph Sieghart

Mẹo để có được kết hợp không tham lam trong sed là khớp tất cả các ký tự loại trừ ký tự kết thúc trận đấu. Tôi biết, một người không có trí tuệ, nhưng tôi đã lãng phí những phút quý giá cho nó và các kịch bản shell nên, sau tất cả, nhanh chóng và dễ dàng. Vì vậy, trong trường hợp người khác có thể cần nó:

Phù hợp tham lam

% echo "<b>foo</b>bar" | sed 's/<.*>//g'
bar

Không tham lam phù hợp

% echo "<b>foo</b>bar" | sed 's/<[^>]*>//g'
foobar


2
Thuật ngữ không có người trí não là mơ hồ. Trong trường hợp này, không rõ ràng rằng bạn (hoặc Christoph Sieghart) đã nghĩ điều này thông qua. Đặc biệt, sẽ rất tuyệt nếu bạn chỉ ra cách giải quyết vấn đề cụ thể trong câu hỏi (trong đó biểu thức không có nhiều hơn được theo sau bởi nhiều hơn một ký tự ) . Bạn có thể thấy rằng câu trả lời này không hoạt động tốt trong trường hợp đó.
Scott

Cái lỗ thỏ sâu hơn nhiều so với cái nhìn đầu tiên của tôi. Bạn đã đúng, cách giải quyết đó không hoạt động tốt cho biểu thức chính quy nhiều ký tự.
gresolio

0

Trong trường hợp của bạn, bạn chỉ có thể phủ nhận đóng char theo cách này:

echo 'ssABteAstACABnnACss' | sed 's/AB[^C]*AC/XXX/'

2
Câu hỏi cho biết, tôi muốn thay thế bất cứ điều gì giữa ABlần xuất hiện đầu tiên và lần đầu tiên xảy ra ACvới XXX,, và đưa ra ssABteAstACABnnACsslàm ví dụ đầu vào. Câu trả lời này hoạt động cho ví dụ đó , nhưng không trả lời câu hỏi nói chung. Ví dụ, ssABteCstACABnnACsscũng sẽ mang lại đầu ra aaXXXABnnACss, nhưng lệnh của bạn chuyển dòng này qua không thay đổi.
G-Man nói 'Phục hồi Monica'
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.