Làm cách nào để sử dụng grep để tìm kiếm một dòng có một trong hai từ nhưng không phải cả hai?


11

Tôi muốn tìm kiếm các dòng có 'word1' XOR 'word2' trong một tệp văn bản. Vì vậy, nó nên xuất các dòng với word1, word2 nhưng không xuất ra các dòng có cả hai từ này. Tôi muốn sử dụng XOR nhưng tôi không biết cách viết nó trong dòng lệnh linux.

Tôi đã thử:

grep 'word1\|word2' text.txt
grep word1 word2 text.txt
grep word1 text.txt | grep word2
grep 'word1\^word2' text.txt

và nhiều hơn nữa, nhưng không thể có được thành công.

Câu trả lời:


6

grep 'word1\|word2' text.txttìm kiếm các dòng có chứa word1hoặc word2. Điều này bao gồm các dòng có chứa cả hai.

grep word1 text.txt | grep word2tìm kiếm các dòng chứa word1word2. Hai từ có thể trùng nhau (ví dụ foobarchứa fooob). Một cách khác để tìm kiếm các dòng chứa cả hai từ, nhưng chỉ theo cách không chồng chéo, là tìm kiếm chúng theo một trong hai thứ tự:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2tìm kiếm các dòng có chứa word1nhưng không word2. Các -vtùy chọn cho grep để giữ dòng không phù hợp và dòng phù hợp với loại bỏ, thay vì ngược lại. Điều này cung cấp cho bạn một nửa kết quả bạn muốn. Bằng cách thêm tìm kiếm đối xứng, bạn sẽ có được tất cả các dòng chứa chính xác một trong các từ.

grep word1 text.txt | grep -v word2
grep word2 text.txt | grep -v word1

Ngoài ra, bạn có thể bắt đầu từ các dòng có chứa một trong hai từ và loại bỏ các dòng chứa cả hai từ. Với các khối xây dựng ở trên, điều này thật dễ dàng nếu các từ không trùng nhau.

grep 'word1\|word2' text.txt | grep -v 'word1.*word2\|word2.*word1'

Cảm ơn bạn đây là chính xác những gì tôi đang tìm kiếm. Các câu trả lời khác cũng rất thú vị vì vậy hãy nhìn vào chúng. Cảm ơn mọi người đã đóng góp.
Lukali

17

Với GNU awk:

$ printf '%s\n' {foo,bar}{bar,foo} neither | gawk 'xor(/foo/,/bar/)'
foofoo
barbar

Hoặc di chuyển:

awk '((/foo/) + (/bar/)) % 2'

Với sự grephỗ trợ cho -P(PCRE):

grep -P '^((?=.*foo)(?!.*bar)|(?=.*bar)(?!.*foo))'

Với sed:

sed '
  /foo/{
    /bar/d
    b
  }
  /bar/!d'

Nếu bạn muốn xem xét toàn bộ chỉ từ (rằng có không phải là foohay bartrong foobarhoặc barbarchẳng hạn), bạn sẽ cần phải quyết định như thế nào những từ được phân cách. Nếu đó là bởi bất kỳ ký tự nào ngoài các chữ cái, chữ số và dấu gạch dưới như -wtùy chọn của nhiều cách grepthực hiện, thì bạn sẽ thay đổi chúng thành:

gawk 'xor(/\<foo\>/,/\<bar\>/)'
awk '((/(^|[^[:alnum:]_)foo([^[:alnum:]_]|$)/) + \
      (/(^|[^[:alnum:]_)bar([^[:alnum:]_]|$)/)) % 2'
grep -P '^((?=.*\bfoo\b)(?!.*\bbar\b)|(?=.*\bbar\b)(?!.*\bfoo\b))'

sedđiều đó trở nên hơi phức tạp trừ khi bạn có một sedtriển khai như GNU sed hỗ trợ \</ \>như các ranh giới từ như GNU awk.


6
Stephane, xin vui lòng viết một cuốn sách về kịch bản shell!
pfnuesel

Xin lỗi tôi chỉ bắt đầu dòng lệnh một vài tuần trước. Làm thế nào tôi có thể buộc nó chỉ tìm kiếm từ? Tôi đã thử -Pw và -wP nhưng điều này cho tôi đầu ra sai. Tôi cũng đã thử sử dụng '' giữa * word1 / * word2 và xung quanh word1 / word2.
Lukali

@Lukali, xem chỉnh sửa.
Stéphane Chazelas

2

Một giải pháp bash:

#!/bin/bash 
while (( $# )); do
    a=0 ; [[ $1 =~ foo ]] && a=1 
    b=0 ; [[ $1 =~ bar ]] && b=1
    (( a ^ b )) && echo "$1"
    shift
done

Để kiểm tra nó:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
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.