Làm thế nào tôi có thể mô hình grep tinh vi trên nhiều dòng?


24

Có vẻ như tôi đang lạm dụng grep/ egrep.

Tôi đã cố gắng tìm kiếm các chuỗi trong nhiều dòng và không thể tìm thấy kết quả khớp trong khi tôi biết rằng những gì tôi đang tìm kiếm phải khớp. Ban đầu tôi nghĩ rằng các biểu thức của tôi là sai nhưng cuối cùng tôi đã đọc được rằng các công cụ này hoạt động trên mỗi dòng (cũng như các biểu thức của tôi rất tầm thường nên không thể là vấn đề).

Vì vậy, công cụ nào người ta sẽ sử dụng để tìm kiếm các mẫu trên nhiều dòng?



1
@CiroSantilli - Tôi không nghĩ rằng Q này và cái bạn liên kết đến là trùng lặp. Q khác đang hỏi làm thế nào bạn thực hiện khớp mẫu nhiều dòng (nghĩa là tôi nên / có thể sử dụng công cụ nào để làm điều này) trong khi công cụ này đang hỏi làm thế nào để làm điều này với grep. Chúng có liên quan chặt chẽ nhưng không phải là dups, IMO.
slm

@sim những trường hợp khó quyết định: Tôi có thể thấy quan điểm của bạn. Tôi nghĩ trường hợp cụ thể này tốt hơn là một bản sao vì người dùng cho biết "grep"đề xuất động từ "to grep" và các câu trả lời hàng đầu, bao gồm cả được chấp nhận, không sử dụng grep.
Ciro Santilli 新疆 心 心 事件

Câu trả lời:


24

Đây là một hành vi sedsẽ cung cấp cho bạn grephành vi giống như bạn trên nhiều dòng:

sed -n '/foo/{:start /bar/!{N;b start};/your_regex/p}' your_file

Làm thế nào nó hoạt động

  • -n triệt tiêu hành vi mặc định của việc in mọi dòng
  • /foo/{}hướng dẫn nó khớp foovà thực hiện những gì bên trong squigglies cho các đường khớp. Thay thế foobằng phần bắt đầu của mẫu.
  • :start là một nhãn phân nhánh để giúp chúng tôi tiếp tục lặp cho đến khi chúng tôi tìm thấy kết thúc cho regex của chúng tôi.
  • /bar/!{}sẽ thực hiện những gì trong squigglies đến các dòng không khớp bar. Thay thế barbằng phần kết thúc của mẫu.
  • Nnối dòng tiếp theo vào bộ đệm hoạt động ( sedgọi đây là không gian mẫu)
  • b startsẽ phân nhánh vô điều kiện vào startnhãn mà chúng ta đã tạo trước đó để tiếp tục nối thêm dòng tiếp theo miễn là không gian mẫu không chứa bar.
  • /your_regex/pin không gian mẫu nếu nó phù hợp your_regex. Bạn nên thay thế your_regexbằng toàn bộ biểu thức bạn muốn khớp trên nhiều dòng.

1
+1 Thêm phần này vào toolikt! Cảm ơn.
wmorrison365

Lưu ý: Trên MacOS, điều này mang lạised: 1: "/foo/{:start /bar/!{N;b ...": unexpected EOF (pending }'s)
Stan James

1
Bắt sed: unterminated {lỗi
Nomaed

@Nomaed Chụp trong bóng tối ở đây, nhưng regex của bạn có chứa bất kỳ ký tự "{" nào không? Nếu vậy, bạn sẽ cần phải gạch chéo lại - thoát chúng.
Joseph R.

1
@Nomaed Có vẻ như nó phải làm với sự khác biệt giữa các lần sedthực hiện. Tôi đã cố gắng làm theo các khuyến nghị trong câu trả lời đó để làm cho kịch bản trên tuân thủ tiêu chuẩn nhưng nó nói với tôi rằng "bắt đầu" là một nhãn không xác định. Vì vậy, tôi không chắc liệu điều này có thể được thực hiện theo cách tuân thủ tiêu chuẩn hay không. Nếu bạn quản lý nó, xin vui lòng chỉnh sửa câu trả lời của tôi.
Joseph R.

19

Tôi thường sử dụng một công cụ gọi là pcregrep có thể được cài đặt trong hầu hết các hương vị linux bằng cách sử dụng yumhoặc apt.

Ví dụ.

Giả sử nếu bạn có một tệp có tên testfilenội dung

abc blah
blah blah
def blah
blah blah

Bạn có thể chạy lệnh sau:

$ pcregrep -M  'abc.*(\n|.)*def' testfile

để thực hiện khớp mẫu trên nhiều dòng.

Hơn nữa, bạn có thể làm tương tự sednhư là tốt.

$ sed -e '/abc/,/def/!d' testfile

5

Đây là một cách tiếp cận đơn giản hơn bằng cách sử dụng Perl:

perl -e '$f=join("",<>); print $& if $f=~/foo\nbar.*\n/m' file

hoặc (kể từ khi JosephR đi theo sedlộ trình , tôi sẽ xấu hổ ăn cắp đề nghị của anh ấy )

perl -n000e 'print $& while /^foo.*\nbar.*\n/mg' file

Giải trình

$f=join("",<>);: cái này đọc toàn bộ tập tin và lưu nội dung của nó (dòng mới và tất cả) vào biến $f. Sau đó, chúng tôi cố gắng khớp foo\nbar.*\nvà in nó nếu nó khớp (biến đặc biệt $&giữ kết quả khớp cuối cùng được tìm thấy). Các///m cần thiết là làm cho biểu thức chính quy khớp với các dòng mới.

Các -0bộ tách hồ sơ đầu vào. Đặt cài đặt này để 00kích hoạt mode chế độ đoạn 'trong đó Perl sẽ sử dụng các dòng mới ( \n\n) liên tiếp làm dấu phân cách bản ghi. Trong trường hợp không có dòng mới liên tiếp, toàn bộ tệp được đọc (bị nhòe) cùng một lúc.

Cảnh báo:

Đừng không làm điều này cho các tập tin lớn, nó sẽ tải toàn bộ tập tin vào bộ nhớ và có thể là một vấn đề.


2

Một cách để làm điều này là với Perl. ví dụ: đây là nội dung của một tệp có tên foo:

foo line 1
bar line 2
foo
foo
foo line 5
foo
bar line 6

Bây giờ, đây là một số Perl sẽ khớp với bất kỳ dòng nào bắt đầu bằng foo theo sau bởi bất kỳ dòng nào bắt đầu bằng thanh:

cat foo | perl -e 'while(<>){$all .= $_}
  while($all =~ /^(foo[^\n]*\nbar[^\n]*\n)/m) {
  print $1; $all =~ s/^(foo[^\n]*\nbar[^\n]*\n)//m;
}'

Perl, bị hỏng:

  • while(<>){$all .= $_} Điều này tải toàn bộ đầu vào tiêu chuẩn vào biến $all
  • while($all =~Trong khi biến allcó biểu thức chính quy ...
  • /^(foo[^\n]*\nbar[^\n]*\n)/m Regex: foo ở đầu dòng, theo sau là bất kỳ số ký tự không phải dòng mới nào, theo sau là dòng mới, ngay sau đó là "thanh" và phần còn lại của dòng có thanh trong đó. /mở cuối regex có nghĩa là "khớp trên nhiều dòng"
  • print $1 In một phần của biểu thức chính trong ngoặc đơn (trong trường hợp này là toàn bộ biểu thức chính quy)
  • s/^(foo[^\n]*\nbar[^\n]*\n)//m Xóa kết quả khớp đầu tiên cho regex, vì vậy chúng tôi có thể khớp nhiều trường hợp của regex trong tệp đang đề cập

Và đầu ra:

foo line 1
bar line 2
foo
bar line 6

3
Chỉ cần perl -n0777E 'say $& while /^foo.*\nbar.*\n/mg' foo
Joseph R.

2

Giải pháp thay thế grep Sift hỗ trợ phù hợp với nhiều dòng (từ chối trách nhiệm: Tôi là tác giả).

Giả sử testfilecó chứa:

<sách>
  <title> Lorem Ipsum </ title>
  <description> Lorem ipsum dolor ngồi amet, consectetur
  adipiscing elit, sed do eiusmod TIME incididunt ut
  labore et dolore magna aliqua </ description>
</ cuốn sách>


sift -m '<description>.*?</description>' (hiển thị các dòng có chứa mô tả)

Kết quả:

testfile: <description> Lorem ipsum dolor sit amet, consectetur
testfile: adipiscing elit, sed do eiusmod TIME incididunt ut
testfile: labore et dolore magna aliqua </ description>


sift -m '<description>(.*?)</description>' --replace 'description="$1"' --no-filename (trích xuất và định dạng lại mô tả)

Kết quả:

description = "Lorem ipsum dolor ngồi amet, consectetur
  adipiscing elit, sed do eiusmod TIME incididunt ut
  labore et dolore magna aliqua "

1
Công cụ rất đẹp. Xin chúc mừng! Cố gắng đưa nó vào các bản phân phối như Ubuntu.
Lộ Lộ

2

Đơn giản chỉ cần một grep bình thường hỗ trợ Perl-regexptham số Psẽ thực hiện công việc này.

$ echo 'abc blah
blah blah
def blah
blah blah' | grep -oPz  '(?s)abc.*?def'
abc blah
blah blah
def

(?s) được gọi là công cụ sửa đổi DOTALL làm cho dấu chấm trong regex của bạn khớp với không chỉ các ký tự mà cả các ngắt dòng.


Khi tôi thử giải pháp này, đầu ra không kết thúc ở 'def' mà đi đến cuối tệp 'blah'
buckley

có thể grep của bạn không hỗ trợ -Ptùy chọn
Avinash Raj

1

Tôi đã giải quyết cái này cho tôi bằng cách sử dụng tùy chọn grep và -A với một grep khác.

grep first_line_word -A 1 testfile | grep second_line_word

Tùy chọn -A 1 in 1 dòng sau dòng tìm thấy. Tất nhiên nó phụ thuộc vào sự kết hợp tập tin và từ của bạn. Nhưng đối với tôi đó là giải pháp nhanh nhất và đáng tin cậy.


bí danh grepp = 'grep --color = auto -B10 -A20 -i' sau đó cat somefile | grepp blah | grepp foo | thanh grepp ... vâng, những cái đó -A và -B rất tiện dụng ... bạn có câu trả lời hay nhất
Scott Stensland

1

Supppose chúng ta có tệp test.txt chứa:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Các mã sau đây có thể được sử dụng:

sed -n '/foo/,/bar/p' test.txt

Đối với đầu ra sau:

foo
here
is the
text
to keep between the 2 patterns
bar

1

Nếu chúng ta muốn có được văn bản giữa 2 mẫu không bao gồm chính họ.

Supppose chúng ta có tệp test.txt chứa:

blabla
blabla
foo
here
is the
text
to keep between the 2 patterns
bar
blabla
blabla

Các mã sau đây có thể được sử dụng:

 sed -n '/foo/{
 n
 b gotoloop
 :loop
 N
 :gotoloop
 /bar/!{
 h
 b loop
 }
 /bar/{
 g
 p
 }
 }' test.txt

Đối với đầu ra sau:

here
is the
text
to keep between the 2 patterns

Làm thế nào nó hoạt động, hãy làm cho nó từng bước

  1. /foo/{ được kích hoạt khi dòng chứa "foo"
  2. n thay thế không gian mẫu bằng dòng tiếp theo, tức là từ "ở đây"
  3. b gotoloop nhánh để nhãn "gotoloop"
  4. :gotoloop định nghĩa nhãn "gotoloop"
  5. /bar/!{ nếu mẫu không chứa "thanh"
  6. h thay thế không gian giữ bằng mẫu, vì vậy "ở đây" được lưu trong không gian giữ
  7. b loop nhánh để nhãn "vòng lặp"
  8. :loop định nghĩa nhãn "vòng lặp"
  9. N nối mô hình vào không gian giữ.
    Bây giờ giữ không gian chứa:
    "ở đây"
    "là"
  10. :gotoloop Bây giờ chúng ta đang ở bước 4 và lặp cho đến khi một dòng chứa "thanh"
  11. /bar/ vòng lặp đã kết thúc, "thanh" đã được tìm thấy, đó là không gian mẫu
  12. g không gian mẫu được thay thế bằng không gian giữ có chứa tất cả các dòng giữa "foo" và "bar" đã lưu trong vòng lặp chính
  13. p sao chép không gian mẫu vào đầu ra tiêu chuẩn

Làm xong !


Làm tốt lắm, +1. Tôi thường tránh sử dụng các lệnh này bằng cách chuyển các dòng mới vào SOH và thực hiện các lệnh sed bình thường sau đó thay thế các dòng mới.
A.Danischewski
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.