Як використовувати 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

та багато іншого, але не змогли отримати успіх.

Відповіді:


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

Баш-рішення:

#!/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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.