Làm cách nào để grep cho các dòng chứa một trong hai từ, nhưng không phải cả hai?


25

Tôi đang cố gắng sử dụng grepđể chỉ hiển thị các dòng có chứa một trong hai từ, nếu chỉ một trong số chúng xuất hiện trong dòng, nhưng không hiển thị nếu chúng nằm trong cùng một dòng.

Cho đến nay tôi đã thử grep pattern1 | grep pattern2 | ...nhưng không nhận được kết quả như mong đợi.


(1) Bạn nói về những từ ngữ của người Viking. Đó là cái gì Những từ thông thường như nhanh chóng, và nâu, và nâu, hay diễn đạt thông thường như thế [a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+nào? (2) Điều gì xảy ra nếu một trong những từ / mẫu xuất hiện nhiều lần trong một dòng (và một từ khác không xuất hiện)? Là từ đó tương đương với từ xuất hiện một lần, hoặc nó được tính là nhiều lần xuất hiện?
G-Man nói 'Phục hồi Monica'

Câu trả lời:


59

Một công cụ khác hơn greplà con đường để đi.

Ví dụ, sử dụng perl, lệnh sẽ là:

perl -ne 'print if /pattern1/ xor /pattern2/'

perl -nechạy lệnh được cung cấp trên mỗi dòng stdin, trong trường hợp này sẽ in dòng nếu nó khớp /pattern1/ xor /pattern2/hoặc nói cách khác khớp với một mẫu nhưng không phải mẫu kia (loại trừ hoặc).

Điều này hoạt động cho mẫu theo thứ tự, và sẽ có hiệu suất tốt hơn so với nhiều lần gọi grepvà cũng ít gõ hơn.

Hoặc, thậm chí ngắn hơn, với awk:

awk 'xor(/pattern1/,/pattern2/)'

hoặc cho các phiên bản awk không có xor:

awk '/pattern1/+/pattern2/==1`

4
Đẹp - chỉ có Awk xortrong GNU Awk?
Steeldo

9
@steel ấn Tôi nghĩ đó chỉ là GNU, vâng. Hoặc ít nhất là nó thiếu trên các phiên bản cũ hơn. Bạn có thể thay thế nó bằng /pattern1/+/pattern2/==1ir xorbị thiếu.
Chris

4
@JimL. Bạn có thể đặt ranh giới từ ( \b) trong chính các mẫu, nghĩa là \bword\b.
wjandrea

4
@vikingsteve Nếu bạn đặc biệt muốn sử dụng grep, có rất nhiều câu trả lời khác ở đây. Nhưng đối với những người chỉ muốn hoàn thành công việc, thật tốt khi biết có những công cụ khác có thể làm mọi thứ mà grep làm, nhưng ngày càng dễ dàng hơn.
Chris

3
@vikingsteve Tôi cho rằng mạnh mẽ rằng nhu cầu về giải pháp grep là một loại vấn đề XY
Hagen von Eitzen

30

Với GNU grep, bạn có thể chuyển cả hai từ grepvà sau đó xóa các dòng chứa cả hai mẫu.

$ cat testfile.txt
abc
def
abc def
abc 123 def
1234
5678
1234 def abc
def abc

$ grep -w -e 'abc' -e 'def' testfile.txt | grep -v -e 'abc.*def' -e 'def.*abc'
abc
def

16

Thử với egrep

egrep  'pattern1|pattern2' file | grep -v -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

3
cũng có thể được viết làgrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
glenn jackman

8
Ngoài ra, lưu ý từ trang grep man: Direct invocation as either egrep or fgrep is deprecated- prefergrep -E
glenn jackman

Điều đó không có trong HĐH của tôi @glennjackman
Grump

1
@Grump thật sao? Hệ điều hành đó là gì? Ngay cả POSIX cũng đề cập rằng grep nên có -f-ecác tùy chọn mặc dù cũ hơn egrepfgrepsẽ tiếp tục được hỗ trợ trong một thời gian.
terdon

1
@terdon, POSIX không chỉ định đường dẫn của các tiện ích POSIX. Một lần nữa, ở đó, các tiêu chuẩn grep(hỗ trợ mà -F, -E, -e, -fnhư POSIX yêu cầu khác) là ở /usr/xpg4/bin. Các tiện ích trong /binlà những người cổ xưa.
Stéphane Chazelas

12

Với các greptriển khai hỗ trợ các biểu thức chính quy như perl (như pcregrephoặc GNU hoặc ast-open grep -P), bạn có thể thực hiện điều đó trong một lệnh grepgọi với:

grep -P '^(?=.*pat1)(?!.*pat2)|^(?=.*pat2)(?!.*pat1)'

Đó là tìm các dòng phù hợp pat1nhưng không pat2, hoặc pat2không pat1.

(?=...)(?!...)tương ứng nhìn về phía trước và tiêu cực nhìn phía trước các nhà khai thác. Vì vậy, về mặt kỹ thuật, phần trên tìm kiếm phần đầu của chủ đề ( ^) miễn là nó được theo sau .*pat1và không theo sau .*pat2, hoặc tương tự với pat1pat2đảo ngược.

Đó là tối ưu cho các dòng có chứa cả hai mẫu khi chúng sẽ được tìm kiếm hai lần. Thay vào đó, bạn có thể sử dụng các toán tử perl nâng cao hơn như:

grep -P '^(?=.*pat1|())(?(1)(?=.*pat2)|(?!.*pat2))'

(?(1)yespattern|nopattern)phù hợp với yespatternnếu nhóm bắt 1st (trống ()ở trên) khớp, và nopatternnếu không. Nếu điều đó ()phù hợp, điều đó có nghĩa là pat1không phù hợp, vì vậy chúng tôi tìm kiếm pat2(nhìn tích cực về phía trước) và chúng tôi tìm kiếm không pat2 khác (nhìn tiêu cực về phía trước).

Với sed, bạn có thể viết nó:

sed -ne '/pat1/{/pat2/!p;d;}' -e '/pat2/p'

Giải pháp đầu tiên của bạn thất bại với grep: the -P option only supports a single pattern, ít nhất là trên mọi hệ thống tôi có quyền truy cập. +1 cho giải pháp thứ hai của bạn, mặc dù.
Chris

1
@Chris, bạn nói đúng. Đó dường như là một hạn chế cụ thể đối với GNU grep. pcregrepvà grep ast-open không có vấn đề đó. Tôi đã thay thế bội số -ebằng toán tử RE xen kẽ, vì vậy nó cũng sẽ hoạt động với GNU grep.
Stéphane Chazelas

Vâng, nó hoạt động tốt bây giờ.
Chris

3

Theo thuật ngữ Boolean, bạn đang tìm kiếm A xor B, có thể được viết là

(A chứ không phải B)

hoặc là

(B chứ không phải A)

Cho rằng câu hỏi của bạn không đề cập đến việc bạn quan tâm đến thứ tự đầu ra miễn là các dòng khớp được hiển thị, việc mở rộng Boolean của A xor B khá đơn giản trong grep:

$ cat << EOF > foo
> a b
> a
> b
> c a
> c b
> b a
> b c
> EOF
$ grep -w 'a' foo | grep -vw 'b'; grep -w 'b' foo | grep -vw 'a';
a
c a
b
c b
b c

1
Điều này hoạt động, nhưng nó sẽ xáo trộn thứ tự của tập tin.
Sparhawk

@Sparhawk Đúng, mặc dù "tranh giành" là một từ khó nghe. ;) nó liệt kê tất cả các trận đấu 'a' trước, theo thứ tự, sau đó tất cả các trận đấu 'b' tiếp theo, theo thứ tự. OP không thể hiện sự quan tâm trong việc duy trì trật tự, chỉ hiển thị các dòng. FAWK, bước tiếp theo có thể là sort | uniq.
Jim L.

Cuộc gọi công bằng; Tôi đồng ý ngôn ngữ của tôi là không chính xác. Tôi có nghĩa là ngụ ý rằng thứ tự ban đầu sẽ được thay đổi.
Sparhawk

1
@Sparhawk ... Và tôi đã chỉnh sửa trong quan sát của bạn để tiết lộ đầy đủ.
Jim L.

-2

Ví dụ sau:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

Điều này có thể được thực hiện hoàn toàn với grep -E, uniq, và wc.

# Grep for regex pattern, sort as unique, and count the number of lines
result=$(grep -oE 'apple|pear' <<< $line | sort -u | wc -l)

Nếu grepđược biên dịch với các biểu thức chính quy Perl thì bạn có thể khớp với lần xuất hiện cuối cùng thay vì cần chuyển sang uniq:

# Grep for regex pattern and count the number of lines
result=$(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l)

Kết quả đầu ra:

# Only one of the words exists if the result is < 2
((result > 0)) &&
   if (($result < 2)); then
      echo Only one word matched
   else
      echo Both words matched
   fi

Một lớp lót:

(($(grep -oP '(apple(?!.*apple)|pear(?!.*pear))' <<< $line | wc -l) == 1)) && echo Only one word matched

Nếu bạn không muốn mã hóa mô hình, việc lắp ráp nó với một tập hợp các phần tử có thể được tự động hóa bằng một hàm.

Điều này cũng có thể được thực hiện tự nhiên trong Bash như là một hàm không có đường ống hoặc các quy trình bổ sung nhưng sẽ liên quan nhiều hơn và có thể nằm ngoài phạm vi câu hỏi của bạn.


(1) Tôi đã tự hỏi khi ai đó sẽ đưa ra câu trả lời bằng cách sử dụng biểu thức chính quy Perl. Nếu bạn tập trung vào phần đó của bài đăng của bạn và giải thích cách nó hoạt động, đây có thể là một câu trả lời tốt. (2) Nhưng tôi sợ phần còn lại không tốt lắm. Câu hỏi nói rằng chỉ hiển thị các dòng có chứa một trong hai từ mà (nhấn mạnh thêm). Nếu đầu ra được coi là dòng , thì lý do là đầu vào cũng phải là nhiều dòng.   Nhưng cách tiếp cận của bạn chỉ hoạt động khi chỉ nhìn vào một dòng duy nhất. Tiết (Cont'd)
G-Man nói 'Tái lập Monica'

(Tiếp theo) Ví dụ, nếu đầu vào chứa các dòng Big apple\npear-shaped\n, thì đầu ra sẽ chứa cả hai dòng đó. Giải pháp của bạn sẽ nhận được số lượng là 2; phiên bản dài sẽ báo cáo Cả hai từ khớp với (đây là câu trả lời cho câu hỏi sai) và phiên bản ngắn sẽ không nói gì cả. (3) Một gợi ý: sử dụng -oở đây là một ý tưởng thực sự tồi tệ, bởi vì nó ẩn các dòng có chứa các kết quả khớp, vì vậy bạn không thể thấy khi cả hai từ xuất hiện trên cùng một dòng. Tiết (Cont'd)
G-Man nói 'Tái lập Monica'

(Tiếp theo) (4) Dòng dưới cùng: việc bạn sử dụng uniq/ sort -uvà biểu thức chính quy Perl ưa thích để chỉ khớp với lần xuất hiện cuối cùng trên mỗi dòng không thực sự bổ sung cho câu trả lời hữu ích cho câu hỏi này. Nhưng, ngay cả khi họ đã làm, nó vẫn sẽ là một câu trả lời tồi bởi vì bạn không giải thích cách họ đóng góp để trả lời câu hỏi. (Xem câu trả lời của Stéphane Chazelas để biết ví dụ về một lời giải thích hay.)
G-Man nói 'Tái lập lại Monica'

OP nói rằng họ muốn "chỉ hiển thị các dòng có chứa một trong hai từ" có nghĩa là mỗi dòng phải được đánh giá riêng. Tôi không thấy lý do tại sao bạn cảm thấy rằng điều này không trả lời câu hỏi. Vui lòng cung cấp một ví dụ đầu vào mà bạn cảm thấy sẽ thất bại.
Zhro

Oh, đó là những gì bạn có ý nghĩa? Có thể đọc đầu vào một dòng tại một thời điểm và thực hiện hai hoặc ba lệnh này cho mỗi dòng . Mùi? (1) Thật đau đớn khi không rõ đó là ý bạn. (2) Thật không hiệu quả. Bốn câu trả lời trước của bạn cho thấy cách xử lý toàn bộ tệp trong một vài lệnh (một, hai hoặc bốn) và bạn muốn chạy các lệnh 3 ×  n cho n dòng đầu vào? Ngay cả khi nó hoạt động, nó kiếm được một phiếu bầu cho việc thực hiện tốn kém không cần thiết. (3) Có nguy cơ bị chẻ sợi tóc, nó vẫn không thực hiện công việc hiển thị các dòng thích hợp.
G-Man nói 'Tái lập Monica'
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.