Làm thế nào để giảm bớt sự tham lam của một biểu thức thông thường trong AWK?


14

Tôi muốn làm mẫu không tham lam (biểu thức chính quy) phù hợp trong awk. Đây là một ví dụ:

echo "@article{gjn, Author =   {Grzegorz J. Nalepa}, " | awk '{ sub(/@.*,/,""); print }'

Có thể viết một biểu thức chính quy chọn chuỗi ngắn hơn không?

@article{gjn,

thay vì chuỗi dài này?:

@article{gjn, Author =   {Grzegorz J. Nalepa},

Tôi muốn nhận được kết quả này:

 Author =   {Grzegorz J. Nalepa},



Tôi có một ví dụ khác:

tiếng vang " , bài viết {gjn, Tác giả = {Grzegorz J. Nalepa}," | awk '{phụ (/ , [^,] *, /, ""); in} '
      ↑ ↑ ^^ ^ ^ ^ ^

Lưu ý rằng tôi đã thay đổi các @ký tự thành ký tự dấu phẩy ( ,) ở vị trí đầu tiên của cả chuỗi đầu vào và biểu thức chính quy (và cũng thay đổi .*thành [^,]*). Có thể viết một biểu thức chính quy chọn chuỗi ngắn hơn không?

, Author =   {Grzegorz J. Nalepa},

thay vì chuỗi dài hơn?:

,article{gjn, Author =   {Grzegorz J. Nalepa},

Tôi muốn nhận được kết quả này:

,article{gjn

4
Giống như regex không đủ để phân tích cú pháp HTML mạnh mẽ, có lẽ họ sẽ không thể thực hiện loại phân tích ngữ pháp nhạy cảm theo ngữ cảnh này. Tuy nhiên, nếu bộ đầu vào của bạn khá hạn chế và được định dạng tốt, bạn có thể thoát khỏi regex miễn là bạn khai báo những hạn chế của mình là gì. Ví dụ: bạn có thể tìm kiếm Authortheo dấu phẩy và khoảng trắng, theo sau là khoảng trắng theo sau =là khoảng trắng theo {sau là bất kỳ dấu vết nào }theo sau }, mặc dù điều này đòi hỏi (trong số những thứ khác) mà bạn không thể lồng {}vào bên trong = { ... }phần đó.
jw013

@ jw013, cảm ơn bạn đã giải thích. Tuy nhiên, tôi sẽ chờ đề xuất của người dùng khác.
nowy1

Câu trả lời:


18

Nếu bạn muốn chọn @và lên đến đầu tiên ,sau đó, bạn cần chỉ định nó là@[^,]*,

Đó là @sau đó bất kỳ số ( *) của phi dấu phẩy ( [^,]) theo sau bởi một dấu phẩy ( ,).

Cách tiếp cận đó hoạt động tương đương @.*?,, nhưng không phải cho những thứ như @.*?string, đó là nơi mà sau đó không chỉ là một nhân vật. Phủ định một ký tự thì dễ, nhưng phủ định chuỗi trong regexps thì khó hơn rất nhiều .

Một cách tiếp cận khác là xử lý trước dữ liệu đầu vào của bạn để thay thế hoặc thêm vào stringmột ký tự không xảy ra trong đầu vào của bạn:

gsub(/string/, "\1&") # pre-process
gsub(/@[^\1]*\1string/, "")
gsub(/\1/, "") # revert the pre-processing

Nếu bạn không thể đảm bảo rằng đầu vào sẽ không chứa ký tự thay thế của bạn ( \1ở trên), một cách tiếp cận là sử dụng cơ chế thoát:

gsub(/\1/, "\1\3") # use \1 as the escape character and escape itself as \1\3
                   # in case it's present in the input
gsub(/\2/, "\1\4") # use \2 as our maker character and escape it
                   # as \1\4 in case it's present in the input
gsub(/string/, "\2&") # mark the "string" occurrences

gsub(/@[^\2]*\2string/, "")

# then roll back the marking and escaping
gsub(/\2/, "")
gsub(/\1\4/, "\2")
gsub(/\1\3/, "\1")

Điều đó làm việc cho strings cố định nhưng không cho regexps tùy ý như cho tương đương @.*?foo.bar.


Cảm ơn bạn rất nhiều vì đã phản hồi tốt. Trong phần chỉnh sửa của mình, tôi đã hỏi một ví dụ khác (xem phần chỉnh sửa của tôi).
nowy1

6

Hiện đã có một số câu trả lời tốt cung cấp các giải pháp cho việc awkkhông thể thực hiện các trận đấu không tham lam, vì vậy tôi đang cung cấp một số thông tin về một cách khác để thực hiện bằng cách sử dụng Biểu thức tương thích thông thường (PCRE) của Perl . Lưu ý rằng hầu hết awkcác tập lệnh "khớp và in" đơn giản có thể dễ dàng được triển khai lại perlbằng cách sử dụng -ntùy chọn dòng lệnh và các tập lệnh phức tạp hơn có thể được chuyển đổi với trình dịch a2p Awk sang Perl.

Perl có một toán tử không tham lam có thể được sử dụng trong các tập lệnh Perl và bất cứ thứ gì sử dụng PCRE. Ví dụ, cũng được triển khai trong -Ptùy chọn của GNU grep .

PCRE không giống với biểu thức thông thường của Perl, nhưng nó rất gần. Đây là một lựa chọn phổ biến của thư viện biểu thức chính quy cho nhiều chương trình, bởi vì nó rất nhanh và các cải tiến Perl cho các biểu thức chính quy mở rộng rất hữu ích.

Từ trang man perlre (1) :

   By default, a quantified subpattern is "greedy", that is, it will match
   as many times as possible (given a particular starting location) while
   still allowing the rest of the pattern to match.  If you want it to
   match the minimum number of times possible, follow the quantifier with
   a "?".  Note that the meanings don't change, just the "greediness":

       *?        Match 0 or more times, not greedily
       +?        Match 1 or more times, not greedily
       ??        Match 0 or 1 time, not greedily
       {n}?      Match exactly n times, not greedily (redundant)
       {n,}?     Match at least n times, not greedily
       {n,m}?    Match at least n but not more than m times, not greedily

3

Đây là một bài viết cũ, nhưng thông tin sau đây có thể hữu ích cho những người khác.

Có một cách, thừa nhận thô thiển, để thực hiện kết hợp RE không tham lam trong awk. Ý tưởng cơ bản là sử dụng hàm so khớp (chuỗi, RE) và giảm dần kích thước của chuỗi cho đến khi khớp không thành công, đại loại như (chưa được kiểm tra):

if (match(string, RE)) {
    rstart = RSTART
    for (i=RLENGTH; i>=1; i--)
        if (!(match(substr(string,1,rstart+i-1), RE))) break;
    # At this point, the non-greedy match will start at rstart
    #  for a length of i+1
}

2

Đối với các biểu thức chung, điều này có thể được sử dụng như một kết hợp không tham lam:

function smatch(s, r) {
    if (match(s, r)) {
        m = RSTART
        do {
            n = RLENGTH
        } while (match(substr(s, m, n - 1), r))
        RSTART = m
        RLENGTH = n
        return RSTART
    } else return 0
}

Tôi đang sử dụng điều này dựa trên câu trả lời của @ JimMellander. smatchcư xử như thế nào match, trở về:

vị trí trong s đó biểu thức chính quy rxảy ra hoặc 0 nếu không. Các biến RSTARTRLENGTHđược đặt thành vị trí và độ dài của chuỗi khớp.


1

Không có cách nào trong awk để thực hiện kết hợp không tham lam. Bạn có thể có được đầu ra mong muốn, mặc dù. đề nghị của sch sẽ làm việc cho dòng đó. Nếu bạn không thể dựa vào dấu phẩy, nhưng "Tác giả" luôn là khởi đầu của những gì bạn muốn, bạn có thể làm điều này:

awk '{ sub(/@.*Author/,"Author"); print }'

Nếu số lượng ký tự trước Tác giả luôn giống nhau, bạn có thể làm điều này:

awk '{ sub(/@.{21}/,""); print }'

Bạn chỉ cần biết dữ liệu của bạn trông như thế nào trên toàn bộ tập hợp.


0

Luôn có một cách. Vấn đề đã cho có thể được giải quyết khá dễ dàng bằng cách sử dụng dấu phẩy làm dấu phân cách.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk -F, '{sub(/^[ \t]/, "", $2); print $2}'

Khi số lượng các lĩnh vực thay đổi một cái gì đó tốt hơn một chút thường là cần thiết. Trong trường hợp như vậy, việc tìm từ dừng thường được đền đáp, vì bạn có thể cắt bất cứ thứ gì ra khỏi dòng bằng cách sử dụng chúng. Trong bối cảnh của ví dụ ở đây, ý của tôi là dừng từ.

echo "@article{gjn2010jucs, Author =   {Grzegorz J. Nalepa}, " |
awk  '{sub(/.*Author/, "Author", $0); sub(/},.*/, "}", $0); print $0}'

0

Tôi biết đây là một bài viết cũ. Nhưng đây là một cái gì đó chỉ sử dụng awk như OP theo yêu cầu:
A = @ article {gjn2010jucs, Author = {Grzegorz J. Nalepa},
echo $ A | awk 'sub (/ @ [^,] * /, "")'

Đầu ra :
, Tác giả = {Grzegorz J. Nalepa},


1
Câu trả lời đó là sai vì khoảng năm lý do.
Scott

3
Bạn có thể vui lòng giúp tôi hiểu những gì là sai? Đầu ra có vẻ phù hợp với những gì được yêu cầu. Cố gắng để hiểu tại sao câu trả lời là đúng / không đúng.
VINAY NAIR
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.