Trích xuất một biểu thức khớp với 'sed' mà không in các ký tự xung quanh


24

Gửi tất cả các bác sĩ 'sed' ngoài kia:

Làm thế nào bạn có thể lấy 'sed' để trích xuất một biểu thức chính quy mà nó đã khớp trong một dòng?

Nói cách khác, tôi chỉ muốn chuỗi tương ứng với biểu thức chính quy với tất cả các ký tự không khớp từ dòng chứa bị loại bỏ.

Tôi đã thử sử dụng tính năng tham chiếu ngược như bên dưới

regular expression to be isolated 
         gets `inserted` 
              here     
               |
               v  
 sed -n 's/.*\( \).*/\1/p 

điều này làm việc cho một số biểu thức như

 sed -n 's/.*\(CONFIG_[a-zA-Z0-9_]*\).*/\1/p 

trong đó trích xuất gọn gàng tất cả các tên macro bắt đầu bằng 'CONFIG_ ....' (được tìm thấy trong một số tệp '* .h') và in chúng ra từng dòng một

          CONFIG_AT91_GPIO
          CONFIG_DRIVER_AT91EMAC
                   .
                   .   
          CONFIG_USB_ATMEL
          CONFIG_USB_OHCI_NEW
                   .
                 e.t.c. 

NHƯNG những điều trên bị phá vỡ cho một cái gì đó như

  sed -n 's/.*\([0-9][0-9]*\).*/\1/p 

cái này luôn trả về các chữ số đơn như

                 7
                 9
                 .
                 .  
                 6

thay vì trích xuất một trường số liền kề như.

              8908078
              89670890  
                 .
                 .  
                 .
               23019   
                 .
               e.t.c.  

Tái bút: Tôi sẽ rất biết ơn những phản hồi về cách đạt được điều này trong 'sed'. Tôi biết cách thực hiện điều này với 'grep' và 'awk' Tôi muốn tìm hiểu xem sự hiểu biết của tôi - mặc dù có giới hạn - có 'lỗ hổng' không và có cách nào để thực hiện điều này trong 'sed' mà tôi
có chỉ đơn giản là bỏ qua.

Câu trả lời:


22

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 avới .*2chống lại [0-9]*; 1được kết hợp bởi [0-9].
  • Trận đấu a1vớ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à a1trậ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 \ncó nghĩa là một dòng mới trong svă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 Tlệnh nói từ bỏ nếu trước slệ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

22

Mặc dù không sed, một trong những điều thường bị bỏ qua cho điều này là grep -o, mà theo tôi là công cụ tốt hơn cho nhiệm vụ này.

Ví dụ: nếu bạn muốn nhận tất cả các CONFIG_tham số từ cấu hình kernel, bạn sẽ sử dụng:

# grep -Eo 'CONFIG_[A-Z0-9_]+' config
CONFIG_64BIT
CONFIG_X86_64
CONFIG_X86
CONFIG_INSTRUCTION_DECODER
CONFIG_OUTPUT_FORMAT

Nếu bạn muốn có được các dãy số liền kề nhau:

$ grep -Eo '[0-9]+' foo

7
sed '/\n/P;//!s/[0-9]\{1,\}/\n&\n/;D'

... sẽ làm điều này mà không làm phiền bất kỳ ai, mặc dù bạn có thể cần các dòng mới theo nghĩa đen thay cho ns trong trường thay thế bên phải. Và, nhân tiện, mọi .*CONFIGthứ sẽ chỉ hoạt động nếu chỉ có một trận đấu trên đường - nó sẽ luôn luôn chỉ nhận được lần cuối cùng.

Bạn có thể thấy điều này để biết mô tả về cách thức hoạt động của nó, nhưng điều này sẽ in trên một dòng riêng biệt chỉ khớp với số lần xuất hiện trên một dòng.

Bạn có thể sử dụng cùng một chiến lược để có được sự [num]xuất hiện thứ trên một dòng. Ví dụ: nếu bạn chỉ muốn in trận đấu CONFIG nếu đó là lần thứ ba như vậy trên một dòng:

sed '/\n/P;//d;s/CONFIG[[:alnum:]]*/\n&\n/3;D'

... mặc dù điều đó giả định rằng các CONFIGchuỗi được phân tách bằng ít nhất một ký tự không chữ và số cho mỗi lần xuất hiện.

Tôi cho rằng - đối với điều số - điều này cũng sẽ hoạt động:

sed -n 's/[^0-9]\{1,\}/\n/g;s/\n*\(.*[0-9]\).*/\1/p

... Với cùng một cảnh báo như trước về tay phải \n. Điều này thậm chí sẽ nhanh hơn lần đầu tiên, nhưng không thể áp dụng như nói chung, rõ ràng.

Đối với điều CONFIG bạn có thể sử dụng P;...;Dvòng lặp ở trên với mẫu của bạn hoặc bạn có thể làm:

sed -n 's/[^C]*\(CONFIG[[:alnum:]]*\)\{0,1\}C\{0,1\}/\1\n/g;s/\(\n\)*/\1/g;/C/s/.$//p'

... chỉ liên quan nhiều hơn một chút và hoạt động bằng cách sắp xếp chính xác sedmức độ ưu tiên tham chiếu. Nó cũng cách ly tất cả các trận đấu CONFIG trên một dòng trong một lần - mặc dù nó cũng đưa ra giả định như trước - rằng mỗi trận đấu CONFIG sẽ được phân tách bằng ít nhất một ký tự không chữ và số. Với GNU sedbạn có thể viết nó:

sed -En 's/[^C]*(CONFIG\w*)?C?/\1\n/g;s/(\n)*/\1/g;/C/s/.$//p'
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.