Có thể đầu ra grep chỉ nhóm cụ thể phù hợp?


291

Nói rằng tôi có một tập tin:

# file: 'test.txt'
foobar bash 1
bash
foobar happy
foobar

Tôi chỉ muốn biết những từ nào xuất hiện sau "foobar", vì vậy tôi có thể sử dụng regex này:

"foobar \(\w\+\)"

Dấu ngoặc chỉ ra rằng tôi có mối quan tâm đặc biệt đến từ này ngay sau foobar. Nhưng khi tôi làm một grep "foobar \(\w\+\)" test.txt, tôi nhận được toàn bộ các dòng khớp với toàn bộ regex, thay vì chỉ "từ sau foobar":

foobar bash 1
foobar happy

Tôi rất thích rằng đầu ra của lệnh đó trông như thế này:

bash
happy

Có cách nào để nói với grep chỉ xuất ra các mục khớp với nhóm (hoặc một nhóm cụ thể) trong một biểu thức thông thường không?


4
cho những người không cần grep:perl -lne 'print $1 if /foobar (\w+)/' < test.txt
vault

Câu trả lời:


326

GNU grep có -Ptùy chọn cho các biểu thức kiểu perl và -otùy chọn chỉ in những gì phù hợp với mẫu. Chúng có thể được kết hợp bằng cách sử dụng các xác nhận nhìn xung quanh (được mô tả trong Mẫu mở rộng trong trang chủ perlre ) để xóa một phần của mẫu grep khỏi những gì được xác định là phù hợp với mục đích -o.

$ grep -oP 'foobar \K\w+' test.txt
bash
happy
$

Đây \Klà dạng ngắn (và dạng hiệu quả hơn) (?<=pattern)mà bạn sử dụng như một xác nhận nhìn phía sau có độ rộng bằng không trước văn bản bạn muốn xuất ra. (?=pattern)có thể được sử dụng như một xác nhận về phía trước có độ rộng bằng không sau văn bản bạn muốn xuất.

Chẳng hạn, nếu bạn muốn ghép từ giữa foobar, bạn có thể sử dụng:

$ grep -oP 'foo \K\w+(?= bar)' test.txt

hoặc (đối xứng)

$ grep -oP '(?<=foo )\w+(?= bar)' test.txt

3
Làm thế nào bạn làm điều đó nếu regex của bạn có nhiều hơn một nhóm? (như tiêu đề ngụ ý?)
barracel

4
@barracel: Tôi không tin bạn có thể. Thời gian chosed(1)
camh 22/03/13

1
@camh Tôi vừa kiểm tra grep -oP 'foobar \K\w+' test.txtkết quả đầu ra không có gì với OP test.txt. Phiên bản grep là 2.5.1. Điều gì có thể sai ? O_O
SOUser

@XichenLi: Tôi không thể nói. Tôi mới xây dựng phiên bản 2.5.1 của grep (nó khá cũ - từ năm 2006) và nó đã hoạt động với tôi.
camh

@SOUser: Tôi đã trải nghiệm tương tự - đầu ra không có gì để nộp. Tôi đã gửi yêu cầu chỉnh sửa để bao gồm '>' trước tên tệp để gửi đầu ra vì điều này làm việc cho tôi.
rjchicago

39

Grep tiêu chuẩn không thể làm điều này, nhưng các phiên bản gần đây của GNU grep có thể . Bạn có thể chuyển sang sed, awk hoặc perl. Dưới đây là một vài ví dụ thực hiện những gì bạn muốn trên đầu vào mẫu của bạn; họ cư xử hơi khác nhau trong các trường hợp góc.

Thay thế foobar word other stuffbằng word, chỉ in nếu thay thế được thực hiện.

sed -n -e 's/^foobar \([[:alnum:]]\+\).*/\1/p'

Nếu từ đầu tiên là foobar, in từ thứ hai.

awk '$1 == "foobar" {print $2}'

Bỏ qua foobarnếu đó là từ đầu tiên và bỏ qua dòng khác; sau đó tước mọi thứ sau khoảng trắng đầu tiên và in.

perl -lne 's/^foobar\s+// or next; s/\s.*//; print'

Tuyệt vời! Tôi nghĩ rằng tôi có thể làm điều này với sed, nhưng tôi đã không sử dụng nó trước đây và hy vọng tôi có thể sử dụng quen thuộc của mình grep. Nhưng cú pháp cho các lệnh này thực sự trông rất quen thuộc vì tôi đã quen với tìm kiếm kiểu vim & thay thế + regexes. Cảm ơn rất nhiều.
Cory Klein

1
Không đúng, Gilles. Xem câu trả lời của tôi cho một giải pháp GNU grep.
camh

1
@camh: Ah, tôi không biết GNU grep hiện đã có hỗ trợ PCRE đầy đủ. Tôi đã sửa câu trả lời của mình, cảm ơn.
Gilles

1
Câu trả lời này đặc biệt hữu ích cho Linux nhúng vì Busybox grepkhông có hỗ trợ PCRE.
Craig McQueen

Rõ ràng có nhiều cách để hoàn thành cùng một nhiệm vụ được trình bày, tuy nhiên, nếu OP yêu cầu sử dụng grep, tại sao bạn lại trả lời một cái gì đó khác? Ngoài ra, đoạn đầu tiên của bạn không chính xác: có grep có thể làm điều đó.
fcm

32
    sed -n "s/^.*foobar\s*\(\S*\).*$/\1/p"

-n     suppress printing
s      substitute
^.*    anything before foobar
foobar initial search match
\s*    any white space character (space)
\(     start capture group
\S*    capture any non-white space character (word)
\)     end capture group
.*$    anything after the capture group
\1     substitute everything with the 1st capture group
p      print it

1
+1 cho ví dụ sed, có vẻ như là một công cụ tốt hơn cho công việc hơn grep. Một nhận xét, ^$không liên quan vì .*là một trận đấu tham lam. Tuy nhiên, bao gồm cả chúng có thể giúp làm rõ ý định của regex.
Tony

18

Chà, nếu bạn biết rằng foobar luôn là từ đầu tiên hoặc dòng, thì bạn có thể sử dụng cắt. Thích như vậy:

grep "foobar" test.file | cut -d" " -f2

Việc -ochuyển đổi trên grep được triển khai rộng rãi (moreso so với các tiện ích mở rộng Gnu grep), do đó, việc grep -o "foobar" test.file | cut -d" " -f2này sẽ làm tăng hiệu quả của giải pháp này, mang tính di động cao hơn so với sử dụng các xác nhận lookbehind.
dubiousjim

Tôi tin rằng bạn sẽ cần grep -o "foobar .*"hoặc grep -o "foobar \w+".
G-Man

9

Nếu PCRE không được hỗ trợ, bạn có thể đạt được kết quả tương tự với hai lệnh grep. Ví dụ để lấy từ sau khi foobar làm điều này:

<test.txt grep -o 'foobar  *[^ ]*' | grep -o '[^ ]*$'

Điều này có thể được mở rộng thành một từ tùy ý sau foobar như thế này (với EREs để dễ đọc):

i=1
<test.txt egrep -o 'foobar +([^ ]+ +){'$i'}[^ ]+' | grep -o '[^ ]*$'

Đầu ra:

1

Lưu ý chỉ số ilà không dựa trên.


6

pcregrepcó một -otùy chọn thông minh hơn cho phép bạn chọn nhóm chụp nào bạn muốn đầu ra. Vì vậy, bằng cách sử dụng tệp ví dụ của bạn,

$ pcregrep -o1 "foobar (\w+)" test.txt
bash
happy

4

Việc sử dụng grepkhông tương thích đa nền tảng, vì -P/ --perl-regexpchỉ khả dụng trên GNUgrep , không phải BSDgrep .

Đây là giải pháp sử dụng ripgrep:

$ rg -o "foobar (\w+)" -r '$1' <test.txt
bash
happy

Theo man rg:

-r/ --replace REPLACEMENT_TEXTThay thế mọi trận đấu bằng văn bản đã cho.

Các chỉ số nhóm chụp (ví dụ $5:) và tên (ví dụ $foo:) được hỗ trợ trong chuỗi thay thế.

Liên quan: GH-462 .


2

Tôi thấy câu trả lời của @jgshawkey rất hữu ích. grepkhông phải là một công cụ tốt cho việc này, nhưng sed thì, mặc dù ở đây chúng ta có một ví dụ sử dụng grep để lấy một dòng có liên quan.

Cú pháp Regex của sed là idiosyncratic nếu bạn không quen với nó.

Đây là một ví dụ khác: đây là phân tích cú pháp đầu ra của xinput để lấy số nguyên ID

⎜   ↳ SynPS/2 Synaptics TouchPad                id=19   [slave  pointer  (2)]

và tôi muốn 19

export TouchPadID=$(xinput | grep 'TouchPad' | sed  -n "s/^.*id=\([[:digit:]]\+\).*$/\1/p")

Lưu ý cú pháp lớp:

[[:digit:]]

và sự cần thiết phải thoát khỏi những điều sau đây +

Tôi giả sử chỉ có một dòng phù hợp.


Đây chính xác là những gì tôi đã cố gắng làm. Cảm ơn!
James

Phiên bản đơn giản hơn một chút mà không cần thêm grep, giả sử 'TouchPad' nằm ở bên trái của 'id':echo "SynPS/2 Synaptics TouchPad id=19 [slave pointer (2)]" | sed -nE "s/.*TouchPad.+id=([0-9]+).*/\1/p"
Amit N Nikol
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.