Khi một biểu thức chính quy chứa các nhóm, có thể có nhiều hơn một cách để khớp một chuỗi với nó: biểu thức chính quy với các nhóm không rõ ràng. Ví dụ, hãy xem xét biểu thức chính quy ^.*\([0-9][0-9]*\)$
và chuỗi a12
. Có hai khả năng:
- Trận đấu
a
với .*
và 2
chống lại [0-9]*
; 1
được kết hợp bởi [0-9]
.
- Trận đấu
a1
với .*
và chuỗi trống chống lại [0-9]*
; 2
được kết hợp bởi [0-9]
.
Sed, giống như tất cả các công cụ regrec khác ngoài đó, áp dụng quy tắc khớp dài nhất sớm nhất: trước tiên, nó cố gắng khớp phần có độ dài biến đầu tiên với một chuỗi càng dài càng tốt. Nếu nó tìm được cách khớp với phần còn lại của chuỗi so với phần còn lại của biểu thức chính quy, tốt thôi. Mặt khác, sed thử kết quả khớp dài nhất tiếp theo cho phần có độ dài biến đầu tiên và thử lại.
Ở đây, trận đấu có chuỗi dài nhất trước tiên là a1
trận đấu .*
, vì vậy nhóm chỉ khớp 2
. Nếu bạn muốn nhóm bắt đầu sớm hơn, một số công cụ regrec cho phép bạn làm cho .*
ít tham lam hơn, nhưng sed không có tính năng như vậy. Vì vậy, bạn cần phải loại bỏ sự mơ hồ với một số neo bổ sung. Chỉ định rằng hàng đầu .*
không thể kết thúc bằng một chữ số, sao cho chữ số đầu tiên của nhóm là khớp đầu tiên có thể.
Nếu nhóm chữ số không thể ở đầu dòng:
sed -n 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p'
Nếu nhóm chữ số có thể ở đầu dòng và sed của bạn hỗ trợ \?
toán tử cho các phần tùy chọn:
sed -n 's/^\(.*[^0-9]\)\?\([0-9][0-9]*\).*/\1/p'
Nếu nhóm các chữ số có thể ở đầu dòng, hãy bám vào các cấu trúc regrec tiêu chuẩn:
sed -n -e 's/^.*[^0-9]\([0-9][0-9]*\).*/\1/p' -e t -e 's/^\([0-9][0-9]*\).*/\1/p'
Nhân tiện, đó cũng chính là quy tắc khớp dài nhất sớm nhất khớp [0-9]*
với các chữ số sau số đầu tiên, thay vì quy tắc tiếp theo .*
.
Lưu ý rằng nếu có nhiều chuỗi chữ số trên một dòng, chương trình của bạn sẽ luôn trích xuất chuỗi chữ số cuối cùng, một lần nữa vì quy tắc khớp dài nhất được áp dụng cho chữ cái đầu tiên .*
. Nếu bạn muốn trích xuất chuỗi chữ số đầu tiên, bạn cần xác định rằng những gì đến trước là một chuỗi các chữ số không.
sed -n 's/^[^0-9]*\([0-9][0-9]*\).*$/\1/p'
Tổng quát hơn, để trích xuất trận đấu đầu tiên của một biểu thức chính quy, bạn cần tính toán phủ định của biểu thức chính quy đó. Mặc dù điều này luôn luôn có thể về mặt lý thuyết, kích thước của phủ định tăng theo cấp số nhân với kích thước của biểu thức chính quy bạn đang phủ định, vì vậy điều này thường không thực tế.
Hãy xem xét ví dụ khác của bạn:
sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p'
Ví dụ này thực sự thể hiện cùng một vấn đề, nhưng bạn không thấy nó trên các đầu vào điển hình. Nếu bạn cho nó ăn hello CONFIG_FOO_CONFIG_BAR
, thì lệnh trên sẽ in ra CONFIG_BAR
, không CONFIG_FOO_CONFIG_BAR
.
Có một cách để in trận đấu đầu tiên với sed, nhưng nó hơi khó:
sed -n -e 's/\(CONFIG_[a-zA-Z0-9_]*\).*/\n\1/' -e T -e 's/^.*\n//' -e p
(Giả sử hỗ trợ sed của bạn \n
có nghĩa là một dòng mới trong s
văn bản thay thế.) Này hoạt động vì sed vẻ cho trận đấu đầu tiên của regexp, và chúng tôi không cố gắng để phù hợp với những gì trước các CONFIG_…
bit. Vì không có dòng mới bên trong dòng, chúng tôi có thể sử dụng nó làm điểm đánh dấu tạm thời. Các T
lệnh nói từ bỏ nếu trước s
lệnh không khớp nhau.
Khi bạn không thể tìm ra cách để làm một cái gì đó trong sed, hãy chuyển sang awk. Lệnh sau sẽ in trận đấu dài nhất sớm nhất của biểu thức chính quy:
awk 'match($0, /[0-9]+/) {print substr($0, RSTART, RLENGTH)}'
Và nếu bạn cảm thấy muốn giữ nó đơn giản, hãy sử dụng Perl.
perl -l -ne '/[0-9]+/ && print $&' # first match
perl -l -ne '/^.*([0-9]+)/ && print $1' # last match