Как использовать grep для поиска строки с одним из двух слов, но не с обоими?

11

Я хочу найти строки с 'word1' XOR 'word2' в текстовом файле. Таким образом, он должен вывести строки с word1, word2, но не с обоими этими словами. Я хотел использовать XOR, но я не знаю, как написать это в командной строке Linux.

Я старался:

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

и многое другое, но не смог добиться успеха.

Lukali
источник

Ответы:

6

grep 'word1\|word2' text.txtищет строки, содержащие word1или word2. Это включает в себя строки, которые содержат оба.

grep word1 text.txt | grep word2ищет строки, содержащие word1и word2. Два слова могут перекрываться (например, foobarсодержит fooи ob). Другой способ поиска строк, содержащих оба слова, но только непересекающимся способом, состоит в том, чтобы искать их в любом порядке:grep 'word1.*word2\|word2.*word1' text.txt

grep word1 text.txt | grep -v word2ищет строки, содержащие word1но не word2. -vОпция говорит Grep держать несовпадающие линии и соответсвующие удалить строки, а не наоборот. Это дает вам половину результатов, которые вы хотели. Добавляя симметричный поиск, вы получаете все строки, содержащие ровно одно из слов.

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

Кроме того, вы можете начать со строк, содержащих любое слово, и удалить строки, содержащие оба слова. Учитывая строительные блоки выше, это легко, если слова не пересекаются.

grep 'word1\|word2' text.txt | grep -v 'word1.*word2\|word2.*word1'
Жиль "ТАК - перестань быть злым"
источник
Спасибо, это именно то, что я искал. Другие ответы также очень интересны, поэтому плохо смотрю на них. Спасибо всем за участие.
Лукали
17

С GNU awk:

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

Или переносимо:

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

С grepподдержкой -P(PCRE):

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

С sed:

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

Если вы хотите , чтобы рассмотреть только целые слова (что не является ни , fooни barв foobarили barbar, например), то вы должны решить , как эти слова разделены. Если это какой-либо символ, отличный от букв, цифр и подчеркивания, как во -wмногих вариантах grepреализации, то вы бы изменили их на:

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

Для sedэто становится немного сложнее , если у вас есть sedреализация , как GNU , sed которая поддерживает \</ , \>как границы слов , как GNU awkделает.

Стефан Шазелас
источник
6
Стефан, пожалуйста, напиши книгу о сценариях оболочки!
pfnuesel
Извините, я только запустил командную строку несколько недель назад. Как бы я заставил его искать только слова? Я пробовал -Pw и -wP, но это дало мне неправильный вывод. Я также пытался использовать '' между * word1 / * word2 и вокруг word1 / word2.
Лукали
@Lukali, см. Редактировать.
Стефан Шазелас
2

Решение Bash:

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

Чтобы проверить это:

$ ./script {foo,bar}\ {foo,bar} neither
foo foo
bar bar
NotAnUnixNazi
источник