Làm thế nào để tìm các mẫu trên nhiều dòng bằng grep?


208

Tôi muốn tìm các tệp có "abc" VÀ "efg" theo thứ tự đó và hai chuỗi đó nằm trên các dòng khác nhau trong tệp đó. Ví dụ: một tệp có nội dung:

blah blah..
blah blah..
blah abc blah
blah blah..
blah blah..
blah blah..
blah efg blah blah
blah blah..
blah blah..

Nên được kết hợp.


Câu trả lời:


225

Grep là không đủ cho hoạt động này.

pcregrep được tìm thấy trong hầu hết các hệ thống Linux hiện đại có thể được sử dụng như

pcregrep -M  'abc.*(\n|.)*efg' test.txt

trong đó -M, --multiline cho phép các mẫu khớp với nhiều hơn một dòng

Có một pcre2grep mới hơn cũng có. Cả hai đều được cung cấp bởi dự án PCRE .

pcre2grep có sẵn cho Mac OS X thông qua Cổng Mac như một phần của cổng pcre2:

% sudo port install pcre2 

và thông qua Homebrew như:

% brew install pcre

hoặc cho pcre2

% brew install pcre2

pcre2grep cũng có sẵn trên Linux (Ubuntu 18.04+)

$ sudo apt install pcre2-utils # PCRE2
$ sudo apt install pcregrep    # Older PCRE

11
@StevenLu -M, --multiline- Cho phép các mẫu khớp với nhiều hơn một dòng.
người mang nhẫn

7
Lưu ý rằng. * (\ N |.) * Tương đương với (\ n |.) * Và cái sau ngắn hơn. Ngoài ra, trên hệ thống của tôi, "pcre_exec () error -8" xảy ra khi tôi chạy phiên bản dài hơn. Vì vậy, hãy thử 'abc (\ n |.) * Efg'!
daveagp

6
Bạn cần làm cho biểu hiện không tham lam trong ví dụ trường hợp đó:'abc.*(\n|.)*?efg'
người mang nhẫn

4
và bạn có thể bỏ qua cái đầu tiên .*-> 'abc(\n|.)*?efg'để làm cho regex ngắn hơn (và trở thành mô phạm)
Michi

6
pcregrepkhông làm mọi thứ dễ dàng hơn, nhưng grepcũng sẽ làm việc Ví dụ: xem stackoverflow.com/a/7167115/123695
Michael Mior

113

Tôi không chắc có thể với grep không, nhưng sed làm cho nó rất dễ dàng:

sed -e '/abc/,/efg/!d' [file-with-content]

4
Điều này không tìm thấy các tệp, nó trả về phần phù hợp từ một tệp duy nhất
shiggity

11
@Lj. xin vui lòng bạn có thể giải thích lệnh này? Tôi quen thuộc sed, nhưng nếu chưa bao giờ thấy một biểu hiện như vậy trước đây.
Anthony

1
@Anthony, Nó được ghi lại trong trang man của sed, theo địa chỉ. Điều quan trọng là phải nhận ra rằng / abc / & / efg / là một địa chỉ.
Mực

49
Tôi nghi ngờ câu trả lời này sẽ hữu ích nếu nó có thêm một chút giải thích và trong trường hợp đó, tôi sẽ bình chọn nó thêm một lần nữa. Tôi biết một chút sed, nhưng không đủ để sử dụng câu trả lời này để tạo ra một mã thoát có ý nghĩa sau nửa giờ nghịch ngợm. Mẹo: 'RTFM' hiếm khi được bình chọn lên StackOverflow, như nhận xét trước đó của bạn cho thấy.
Michael Scheper

25
Giải thích nhanh bằng ví dụ: sed '1,5d': xóa các dòng trong khoảng từ 1 đến 5. sed '1,5! D': xóa các dòng không nằm trong khoảng từ 1 đến 5 (tức là giữ các dòng giữa) sau đó thay vì một số, bạn có thể tìm kiếm một dòng với / mẫu /. Xem thêm phần đơn giản dưới đây: sed -n '/ abc /, / efg / p' p dành cho in ấn và cờ -n không hiển thị tất cả các dòng
phil_w

86

Đây là một giải pháp lấy cảm hứng từ câu trả lời này :

  • nếu 'abc' và 'efg' có thể nằm trên cùng một dòng:

    grep -zl 'abc.*efg' <your list of files>
  • nếu 'abc' và 'efg' phải nằm trên các dòng khác nhau:

    grep -Pzl '(?s)abc.*\n.*efg' <your list of files>

Param:

  • -zCoi đầu vào là một tập hợp các dòng, mỗi dòng được kết thúc bằng một byte 0 thay vì một dòng mới. tức là grep coi đầu vào là một dòng lớn.

  • -l tên in của mỗi tệp đầu vào mà từ đó đầu ra thường được in.

  • (?s)kích hoạt PCRE_DOTALL, có nghĩa là '.' tìm thấy bất kỳ nhân vật hoặc dòng mới.


@syntaxerror Không, tôi nghĩ đó chỉ là chữ thường l. AFAIK không có -1tùy chọn số .
Sparhawk

Dường như bạn đúng sau tất cả, có lẽ tôi đã mắc lỗi đánh máy khi kiểm tra. Trong mọi trường hợp xin lỗi vì đã đặt một dấu vết sai.
cú pháp

6
Thật tuyệt vời. Tôi chỉ có một câu hỏi liên quan đến điều này. Nếu các -ztùy chọn chỉ định grep để xử lý các dòng mới zero byte charactersthì tại sao chúng ta cần (?s)trong regex? Nếu nó đã là một nhân vật không phải dòng mới, không nên .kết hợp trực tiếp với nó?
Durga Swaroop

1
-z (còn gọi là - dữ liệu đầy đủ) và (? s) chính xác là những gì bạn cần để khớp nhiều dòng với một grep tiêu chuẩn. Mọi người trên MacOS, vui lòng để lại nhận xét về tính khả dụng của các tùy chọn -z hoặc --null-data trên hệ thống của bạn!
Zeke nhanh

4
-z chắc chắn không có sẵn trên MacOS
Dylan Nicholson

33

sed nên đủ như poster LJ đã nêu ở trên,

thay vì! d bạn chỉ cần sử dụng p để in:

sed -n '/abc/,/efg/p' file

15

Tôi phụ thuộc rất nhiều vào pcregrep, nhưng với grep mới hơn, bạn không cần phải cài đặt pcregrep cho nhiều tính năng của nó. Chỉ dùnggrep -P .

Trong ví dụ về câu hỏi của OP, tôi nghĩ các tùy chọn sau hoạt động tốt, với kết quả phù hợp thứ hai với cách tôi hiểu câu hỏi:

grep -Pzo "abc(.|\n)*efg" /tmp/tes*
grep -Pzl "abc(.|\n)*efg" /tmp/tes*

Tôi đã sao chép văn bản dưới dạng / tmp / test1 và xóa 'g' và lưu dưới dạng / tmp / test2. Đây là kết quả đầu ra cho thấy chuỗi đầu tiên hiển thị chuỗi trùng khớp và chuỗi thứ hai chỉ hiển thị tên tệp (điển hình -o là hiển thị khớp và điển hình -l là chỉ hiển thị tên tệp). Lưu ý rằng 'z' là cần thiết cho đa dòng và '(. | \ N)' có nghĩa là khớp với 'bất kỳ thứ gì khác ngoài dòng mới' hoặc 'dòng mới' - tức là mọi thứ:

user@host:~$ grep -Pzo "abc(.|\n)*efg" /tmp/tes*
/tmp/test1:abc blah
blah blah..
blah blah..
blah blah..
blah efg
user@host:~$ grep -Pzl "abc(.|\n)*efg" /tmp/tes*
/tmp/test1

Để xác định xem phiên bản của bạn có đủ mới hay không, hãy chạy man grepvà xem nếu một cái gì đó tương tự như thế này xuất hiện gần đầu trang:

   -P, --perl-regexp
          Interpret  PATTERN  as a Perl regular expression (PCRE, see
          below).  This is highly experimental and grep -P may warn of
          unimplemented features.

Đó là từ GNU grep 2.10.


14

Điều này có thể được thực hiện dễ dàng bằng cách sử dụng đầu tiên trđể thay thế các dòng mới bằng một số ký tự khác:

tr '\n' '\a' | grep -o 'abc.*def' | tr '\a' '\n'

Ở đây, tôi đang sử dụng ký tự báo động, \a(ASCII 7) thay cho dòng mới. Điều này gần như không bao giờ được tìm thấy trong văn bản của bạn và grepcó thể khớp nó với một .hoặc khớp cụ thể với nó \a.


1
Đây là cách tiếp cận của tôi nhưng tôi đã sử dụng \0và do đó cần thiết grep -avà phù hợp với trên \x00Bạn đã giúp tôi đơn giản hóa! echo $log | tr '\n' '\0' | grep -aoE "Error: .*?\x00Installing .*? has failed\!" | tr '\0' '\n'bây giờecho $log | tr '\n' '\a' | grep -oE "Error: .*?\aInstalling .*? has failed\!" | tr '\a' '\n'
Charlie Gorichanaz

1
Sử dụng grep -o.
kyb

7

awk one-liner:

awk '/abc/,/efg/' [file-with-content]

4
Điều này sẽ vui vẻ in từ abcđến hết tệp nếu mẫu kết thúc không có trong tệp hoặc mẫu kết thúc cuối cùng bị thiếu. Bạn có thể khắc phục điều đó nhưng nó sẽ làm phức tạp kịch bản khá đáng kể.
tripleee

Làm thế nào để loại trừ /efg/khỏi đầu ra?
kyb

6

Bạn có thể làm điều đó rất dễ dàng nếu bạn có thể sử dụng Perl.

perl -ne 'if (/abc/) { $abc = 1; next }; print "Found in $ARGV\n" if ($abc && /efg/); }' yourfilename.txt

Bạn cũng có thể làm điều đó với một biểu thức chính quy duy nhất, nhưng điều đó liên quan đến việc lấy toàn bộ nội dung của tệp thành một chuỗi, điều này có thể chiếm quá nhiều bộ nhớ với các tệp lớn. Để hoàn thiện, đây là phương pháp:

perl -e '@lines = <>; $content = join("", @lines); print "Found in $ARGV\n" if ($content =~ /abc.*efg/s);' yourfilename.txt

Tìm thấy câu trả lời thứ hai rất hữu ích để trích xuất toàn bộ một khối nhiều dòng với các kết quả khớp trên một vài dòng - phải sử dụng kết hợp không tham lam ( .*?) để có được kết quả khớp tối thiểu.
RichVel

5

Tôi không biết làm thế nào tôi sẽ làm điều đó với grep, nhưng tôi sẽ làm một cái gì đó như thế này với awk:

awk '/abc/{ln1=NR} /efg/{ln2=NR} END{if(ln1 && ln2 && ln1 < ln2){print "found"}else{print "not found"}}' foo

Bạn cần phải cẩn thận làm thế nào bạn làm điều này, mặc dù. Bạn có muốn regex khớp với chuỗi con hoặc toàn bộ từ không? thêm các thẻ \ w nếu thích hợp. Ngoài ra, trong khi điều này hoàn toàn phù hợp với cách bạn nêu ví dụ, nó không hoạt động khi abc xuất hiện lần thứ hai sau efg. Nếu bạn muốn xử lý điều đó, hãy thêm một if nếu thích hợp trong / abc / case, v.v.


3

Đáng buồn thay, bạn không thể. Từ các greptài liệu:

grep tìm kiếm các FILE đầu vào được đặt tên (hoặc đầu vào tiêu chuẩn nếu không có tệp nào được đặt tên hoặc nếu một dấu gạch nối đơn (-) được đặt dưới dạng tên tệp) cho các dòng chứa khớp với MẪU đã cho.


những gì vềgrep -Pz
Navaro

3

Nếu bạn sẵn sàng sử dụng bối cảnh, điều này có thể đạt được bằng cách gõ

grep -A 500 abc test.txt | grep -B 500 efg

Điều này sẽ hiển thị mọi thứ giữa "abc" và "efg", miễn là chúng nằm trong phạm vi 500 dòng của nhau.


3

Nếu bạn cần cả hai từ gần nhau, ví dụ không quá 3 dòng, bạn có thể làm điều này:

find . -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Ví dụ tương tự nhưng chỉ lọc các tệp * .txt:

find . -name *.txt -exec grep -Hn -C 3 "abc" {} \; | grep -C 3 "efg"

Và bạn cũng có thể thay thế greplệnh bằng egreplệnh nếu bạn cũng muốn tìm bằng các biểu thức thông thường.


3

Tôi đã phát hành một thay thế grep vài ngày trước, hỗ trợ điều này trực tiếp, thông qua kết hợp đa dòng hoặc sử dụng các điều kiện - hy vọng nó hữu ích cho một số người tìm kiếm ở đây. Đây là những gì các lệnh cho ví dụ sẽ trông như thế nào:

Đa dòng:

sift -lm 'abc.*efg' testfile

Điều kiện:

sift -l 'abc' testfile --followed-by 'efg'

Bạn cũng có thể chỉ định rằng 'efg' phải tuân theo 'abc' trong một số dòng nhất định:

sift -l 'abc' testfile --followed-within 5:'efg'

Bạn có thể tìm thêm thông tin trên sift-tool.org .


Tôi không nghĩ rằng ví dụ đầu tiên sift -lm 'abc.*efg' testfilehoạt động, bởi vì trận đấu là tham lam và ngấu nghiến tất cả các dòng cho đến cuối cùng efgtrong tệp.
Tiến sĩ Alex RE

2

Mặc dù tùy chọn sed là đơn giản và dễ dàng nhất, nhưng một chiếc áo lót của LJ thật đáng buồn không phải là thiết bị di động nhất. Những người bị mắc kẹt với một phiên bản của C Shell sẽ cần phải thoát khỏi tiếng nổ của họ:

sed -e '/abc/,/efg/\!d' [file]

Thật không may, điều này không hoạt động trong bash et al.


1
#!/bin/bash
shopt -s nullglob
for file in *
do
 r=$(awk '/abc/{f=1}/efg/{g=1;exit}END{print g&&f ?1:0}' file)
 if [ "$r" -eq 1 ];then
   echo "Found pattern in $file"
 else
   echo "not found"
 fi
done

1

bạn có thể sử dụng grep trong trường hợp bạn không quan tâm đến trình tự của mẫu.

grep -l "pattern1" filepattern*.* | xargs grep "pattern2"

thí dụ

grep -l "vector" *.cpp | xargs grep "map"

grep -lsẽ tìm thấy tất cả các tệp khớp với mẫu đầu tiên và xargs sẽ grep cho mẫu thứ hai. Hi vọng điêu nay co ich.


1
Tuy nhiên, điều đó sẽ bỏ qua thứ tự "mẫu1" và "mẫu2" xuất hiện trong tệp - OP chỉ định cụ thể rằng chỉ những tệp có "mẫu2" xuất hiện SAU "mẫu1" mới được khớp.
Emil Lundberg

1

Với người tìm kiếm bạc :

ag 'abc.*(\n|.)*efg'

tương tự như câu trả lời của người mang nhẫn, nhưng với ag thay thế. Lợi thế về tốc độ của người tìm kiếm bạc có thể có thể tỏa sáng ở đây.


1
Điều này dường như không hoạt động. (echo abctest; echo efg)|ag 'abc.*(\n|.)*efg'không khớp
phiresky 23/2/2016

1

Tôi đã sử dụng điều này để trích xuất một chuỗi fasta từ một tệp multi fasta bằng tùy chọn -P cho grep:

grep -Pzo ">tig00000034[^>]+"  file.fasta > desired_sequence.fasta
  • P cho các tìm kiếm dựa trên perl
  • z để tạo một dòng kết thúc bằng 0 byte thay vì char dòng mới
  • o chỉ để nắm bắt những gì phù hợp vì grep trả về toàn bộ dòng (trong trường hợp này vì bạn đã làm -z là toàn bộ tệp).

Cốt lõi của biểu thức chính là [^>]dịch "không lớn hơn ký hiệu"


0

Để thay thế cho câu trả lời Balu Mohan, người ta có thể thực thi sắc lệnh của các mô hình duy nhất sử dụng grep, headtail:

for f in FILEGLOB; do tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep "pattern2" &>/dev/null && echo $f; done

Điều này không phải là rất đẹp, mặc dù. Định dạng dễ đọc hơn:

for f in FILEGLOB; do
    tail $f -n +$(grep -n "pattern1" $f | head -n1 | cut -d : -f 1) 2>/dev/null \
    | grep -q "pattern2" \
    && echo $f
done

Điều này sẽ in tên của tất cả các file trong đó "pattern2"xuất hiện sau "pattern1", hoặc trong trường hợp cả hai xuất hiện trên cùng một dòng :

$ echo "abc
def" > a.txt
$ echo "def
abc" > b.txt
$ echo "abcdef" > c.txt; echo "defabc" > d.txt
$ for f in *.txt; do tail $f -n +$(grep -n "abc" $f | head -n1 | cut -d : -f 1) 2>/dev/null | grep -q "def" && echo $f; done
a.txt
c.txt
d.txt

Giải trình

  • tail -n +i- in tất cả các dòng sau ith, bao gồm
  • grep -n - thêm dòng phù hợp với số dòng của họ
  • head -n1 - chỉ in hàng đầu tiên
  • cut -d : -f 1- in cột cắt đầu tiên bằng cách sử dụng :dấu phân cách
  • 2>/dev/null- tailđầu ra lỗi im lặng xảy ra nếu $()biểu thức trả về sản phẩm nào
  • grep -q- im lặng grepvà quay lại ngay lập tức nếu tìm thấy kết quả khớp, vì chúng tôi chỉ quan tâm đến mã thoát

Bất cứ ai có thể xin vui lòng giải thích &>? Tôi cũng đang sử dụng nó, nhưng tôi chưa bao giờ thấy nó được ghi lại ở bất cứ đâu. BTW, tại sao chúng ta phải im lặng theo cách đó, thực sự? grep -qSẽ không làm điều đó là tốt?
cú pháp

1
&>báo bash để chuyển hướng cả đầu ra tiêu chuẩn và lỗi tiêu chuẩn, xem GIẢM GIÁ trong hướng dẫn sử dụng bash. Bạn rất đúng khi chúng ta có thể làm tốt grep -q ...thay vì grep ... &>/dev/nullbắt tốt!
Emil Lundberg

Cũng nghĩ vậy. Sẽ lấy đi nỗi đau của rất nhiều gõ thêm vụng về. Cảm ơn đã giải thích - vì vậy tôi phải bỏ qua một chút trong hướng dẫn. (Đã tìm kiếm một cái gì đó liên quan từ xa trong đó một thời gian trước đây.) --- Bạn thậm chí có thể xem xét thay đổi nó trong câu trả lời của mình. :)
cú pháp

0

Điều này cũng nên làm việc?!

perl -lpne 'print $ARGV if /abc.*?efg/s' file_list

$ARGVchứa tên của tệp hiện tại khi đọc từ file_list /stìm kiếm sửa đổi trên dòng mới.


0

Các filepotype *.shrất quan trọng để ngăn chặn các thư mục được kiểm tra. Tất nhiên một số thử nghiệm có thể ngăn chặn điều đó quá.

for f in *.sh
do
  a=$( grep -n -m1 abc $f )
  test -n "${a}" && z=$( grep -n efg $f | tail -n 1) || continue 
  (( ((${z/:*/}-${a/:*/})) > 0 )) && echo $f
done

Các

grep -n -m1 abc $f 

tìm kiếm tối đa 1 kết hợp và trả về (-n) vải lanh. Nếu một trận đấu được tìm thấy (test -n ...), hãy tìm trận đấu cuối cùng của efg (tìm tất cả và lấy điểm cuối cùng với đuôi -n 1).

z=$( grep -n efg $f | tail -n 1)

khác tiếp tục.

Vì kết quả là một cái gì đó giống như 18:foofile.sh String alf="abc";chúng ta cần cắt bỏ từ ":" cho đến cuối dòng.

((${z/:*/}-${a/:*/}))

Sẽ trả về kết quả dương nếu trận đấu cuối cùng của biểu thức thứ 2 vượt qua trận đấu đầu tiên của trận đấu đầu tiên.

Sau đó chúng tôi báo cáo tên tệp echo $f.


0

Tại sao không phải là một cái gì đó đơn giản như:

egrep -o 'abc|efg' $file | grep -A1 abc | grep efg | wc -l

trả về 0 hoặc một số nguyên dương.

egrep -o (Chỉ hiển thị kết quả trùng khớp, mẹo: nhiều kết quả trùng khớp trên cùng một dòng tạo ra đầu ra nhiều dòng như thể chúng ở trên các dòng khác nhau)

  • grep -A1 abc (in abc và dòng sau nó)

  • grep efg | wc -l (Số lượng 0-n dòng efg được tìm thấy sau abc trên cùng dòng hoặc dòng sau, kết quả có thể được sử dụng trong 'nếu ")

  • grep có thể được thay đổi thành egrep, v.v ... nếu cần kết hợp mẫu


0

Nếu bạn có một số ước tính về khoảng cách giữa 2 chuỗi 'abc' và 'efg' bạn đang tìm kiếm, bạn có thể sử dụng:

grep -r . -e 'abc' -A num1 -B num2 | grep 'efg'

Theo cách đó, grep đầu tiên sẽ trả về dòng có 'abc' cộng với # num1 dòng sau nó và # num2 sau nó, và grep thứ hai sẽ lọc qua tất cả những dòng đó để có được 'efg'. Sau đó, bạn sẽ biết tập tin nào chúng xuất hiện cùng nhau.


0

Với ugrep được phát hành vài tháng trước:

ugrep 'abc(\n|.)+?efg'

Công cụ này được tối ưu hóa cao cho tốc độ. Nó cũng tương thích GNU / BSD / PCRE-grep.

Lưu ý rằng chúng ta nên sử dụng sự lặp lại lười biếng +?, trừ khi bạn muốn khớp tất cả các dòng với efgnhau cho đến cuối cùng efgtrong tệp.


-3

Điều này sẽ làm việc:

cat FILE | egrep 'abc|efg'

Nếu có nhiều hơn một trận đấu, bạn có thể lọc ra bằng grep -v


2
Trong khi đoạn mã này được chào đón và có thể cung cấp một số trợ giúp, nó sẽ được cải thiện rất nhiều nếu nó bao gồm một lời giải thích về cách thứclý do tại sao điều này giải quyết vấn đề. Hãy nhớ rằng bạn đang trả lời câu hỏi cho độc giả trong tương lai, không chỉ là người hỏi bây giờ! Vui lòng chỉnh sửa câu trả lời của bạn để thêm giải thích và đưa ra dấu hiệu về những hạn chế và giả định được áp dụng.
Toby Speight

1
Điều đó không thực sự tìm kiếm trên nhiều dòng , như đã nêu trong câu hỏi.
16:51
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.