it-swarm-vi.com

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

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 Word ngay sau foobar. Nhưng khi tôi thực hiện grep "foobar \(\w\+\)" test.txt, tôi sẽ nhận được toàn bộ các dòng khớp với toàn bộ regex, thay vì chỉ "Word 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 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?

338
Cory Klein

GNU grep có -P tùy chọn cho biểu thức kiểu Perl và -o tù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 Các 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 của -o.

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

Các \K là dạng ngắn (và dạng hiệu quả hơn) của (?<=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. (?=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 khớp Word 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
373
camh

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 can . Bạn có thể chuyển sang sed, awk hoặc Perl. Dưới đây là một vài ví dụ làm những gì bạn muốn vào đầu vào mẫu của bạn, chúng hoạt động hơi khác nhau trong các trường hợp góc.

Thay thế foobar Word other stuff by Word, chỉ in nếu thay thế xong.

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

Nếu Word đầu tiên là foobar, hãy in Word thứ hai.

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

Xóa foobar nếu đó là Word đầ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'
    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
46
jgshawkey

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
19
Dave

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

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

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 Word sau 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ỉ mục i là không dựa trên.

9
Thor

Sử dụng grep không tương thích đa nền tảng, vì -P/--Perl-regexp chỉ khả dụng trên GNU grep , không phải BSD grep .

Đâ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_TEXT Thay thế mọi trận đấu bằng văn bản đã cho.

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

Liên quan: GH-462 .

7
kenorb

Tôi thấy câu trả lời của @jgshawkey rất hữu ích. grep không phải là một công cụ tốt cho việc này, nhưng sed là, mặc dù ở đây chúng tôi 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: cái này phân tích đầ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 + sau đây

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

2
Tim Richardson