Як переглядати рядки, що містять будь-яке з двох слів, але не обидва?


25

Я намагаюся використовувати grepдля показу лише рядки, що містять будь-яке з двох слів, якщо в одному рядку відображається лише одне, але не, якщо вони знаходяться в одному рядку.

Поки я намагався, grep pattern1 | grep pattern2 | ...але не отримав очікуваного результату.


(1) Ви говорите про "слова" та "зразки". Що це таке? Звичайні слова, такі як "швидкий", "коричневий" і "лисиця", або регулярні вирази, як [a-z][a-z0-9]\(,7\}\(\.[a-z0-9]\{,3\}\)+? (2) Що робити, якщо одне зі слів / зразків з’являється в рядку декілька разів (а інше не з’являється)? Це еквівалент слово, яке з’являється один раз, або воно вважається численним випадком?
G-Man каже: "Відновіть Моніку"

Відповіді:


59

Інший інструмент, ніж grepце шлях.

Наприклад, використовуючи команду perl:

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

perl -neвиконує команду, задану над кожним рядком stdin, яка в цьому випадку друкує рядок, якщо вона збігається /pattern1/ xor /pattern2/, або іншими словами відповідає одному шаблону, але не іншому (виключно або).

Це працює для шаблону в будь-якому порядку, і має мати кращу продуктивність, ніж кілька викликів grep, а також менше набирати текст.

Або, ще коротше, з awk:

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

або для версій awk, у яких немає xor:

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

4
Приємно - Awk xorдоступний лише в GNU Awk?
steeldriver

9
@steeldriver Я думаю, що це лише GNU, так. Або, принаймні, його немає в старих версіях. Ви можете замінити його на /pattern1/+/pattern2/==1ir xorвідсутній.
Кріс

4
@JimL. Ви можете розмістити межі слів ( \b) у самих шаблонах, тобто \bword\b.
wjandrea

4
@vikingsteve Якщо ви конкретно хочете використовувати греп, тут є багато інших відповідей. Але людям, які просто хочуть виконати роботу, добре знати, що є й інші інструменти, за допомогою яких можна зробити все, що робиться, але все легше та легше.
Кріс

3
@vikingsteve Я напевно припускаю, що попит на рішення grep є своєрідною проблемою XY
Хаген фон Ейтцен

30

За допомогою GNU grepви можете передати обидва слова, grepа потім видалити рядки, що містять обидва шаблони.

$ 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

Спробуйте з egrep

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

3
можна також записати якgrep -e foo -e bar | grep -v -e 'foo.*bar' -e 'bar.*foo'
glenn jackman

8
Також зверніть увагу на сторінку grep man: Direct invocation as either egrep or fgrep is deprecated- віддайте перевагуgrep -E
glenn jackman

Це не в моїй ОС @glennjackman
Grump

1
@Grump дійсно? Що це за ОС? Навіть POSIX згадує, що grep повинен мати -fта -eпараметри, хоча старший egrepі fgrepбуде підтримуватися деякий час.
тердон

1
@terdon, POSIX не визначає шлях утиліт POSIX. Знову ж , є, стандарт grep(який підтримує -F, -E, -e, , -fяк POSIX вимагає) в /usr/xpg4/bin. Комунальні послуги в /binзастарілих.
Стефан Шазелас

12

З grepреалізаціями, які підтримують регулярні вирази, подібні до perl (наприклад, pcregrepGNU або ast-open grep -P), ви можете це зробити за один grepвиклик за допомогою:

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

Тобто знаходьте рядки, які відповідають, pat1але ні pat2, pat2але ні pat1.

(?=...)і (?!...), відповідно, дивляться вперед і негативно дивляться вперед оператори. Тож технічно, вищезазначене шукає початок теми ( ^) за умови, що за ним слідує, .*pat1а не слідує за ним .*pat2, або те саме з pat1і pat2перевернуто.

Це субоптимально для ліній, що містять обидва візерунки, як їх би потім шукали двічі. Ви можете замість цього використовувати більш просунуті оператори perl, такі як:

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

(?(1)yespattern|nopattern)відповідає проти, yespatternякщо група захоплення 1st (порожня ()вгорі) збігається та nopatternінше. Якщо це ()збіг, це означає, що pat1не відповідало, тому ми шукаємо pat2(позитивний погляд вперед), а ми шукаємо не pat2 інакше (негативний погляд вперед).

З sed, ви можете написати це:

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

Ваше перше рішення не вдається grep: the -P option only supports a single pattern, принаймні в кожній системі, до якої я маю доступ. Хоча +1 для вашого другого рішення.
Кріс

1
@Chris, ти маєш рацію. Це здається обмеженням, характерним для GNU grep. pcregrepі у аст-відкритого грепу немає такої проблеми. Я замінив множину -eоператором змінної RE, тому він повинен працювати і з GNU grep.
Стефан Шазелас

Так, це зараз добре працює.
Кріс

3

Булево, ви шукаєте A xor B, який можна записати як

(А, а не В)

або

(B, а не A)

Зважаючи на те, що у вашому запитанні не згадується, що ви переймаєтесь порядком виводу, доки не відображаються відповідні рядки, булеве розширення A xor B досить чорно просте:

$ 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
Це працює, але це скомпонує порядок файлу.
Спархак

@Sparhawk Щоправда, хоча "сутичка" - це суворе слово. ;) вона перераховує спочатку всі відповідність 'a', за порядком, а потім усі відповіді 'b' наступні, по порядку. ОП не висловлювало зацікавленості підтримувати порядок, просто показувало лінії. FAWK, наступним кроком може бути sort | uniq.
Джим Л.

Справедливий дзвінок; Я згоден, моя мова була неточною. Я мав на увазі, що початковий порядок буде змінено.
Sparhawk

1
@Sparhawk ... І я відредагував ваше спостереження для повного розкриття.
Джим Л.

-2

Для наступного прикладу:

# Patterns:
#    apple
#    pear

# Example line
line="a_apple_apple_pear_a"

Це може бути зроблено виключно з grep -E, uniqі wc.

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

Якщо grepкомпілюється з регулярними виразами Perl, то ви можете зіставитись з останньою появою, а не з необхідністю передавати на uniq:

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

Виведіть результат:

# 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

Одноколісний:

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

Якщо ви не хочете жорстко кодувати шаблон, складання його зі змінним набором елементів може бути автоматизовано функцією.

Це також можна зробити на Bash як функція без труб або додаткових процесів, але це буде більше задіяне і, ймовірно, не входить у сферу вашого питання.


(1) Мені було цікаво, коли хтось збирається дати відповідь, використовуючи регулярні вирази Perl. Якщо ви зосередилися на тій частині своєї посади і пояснили, як вона працює, це може бути хорошою відповіддю. (2) Але я боюся, що решта не настільки хороша. У запитанні сказано: "показувати лише рядки, що містять будь-яке з двох слів" (наголос додано). Якщо висновок повинен бути рядком , то, безумовно, слід вказувати , що вхід також повинен бути декількома рядками.   Але ваш підхід працює лише при перегляді лише однієї лінії. … (Продовжував)
G-Man каже «Відновити Моніку»

(Протяг)… Наприклад, якщо вхід містить рядки Big apple\nі pear-shaped\n, то вихід повинен містити обидва ці рядки. Ваше рішення отримає кількість 2; довга версія повідомляла б, що "обидва слова відповідають" (це відповідь на неправильне запитання), а коротка версія нічого не скаже. (3) Пропозиція: використання -oтут - це дуже погана ідея, оскільки вона приховує рядки, що містять відповідність, тому ви не бачите, коли обидва слова з’являються в одному рядку. … (Продовжував)
G-Man каже «Відновити Моніку»

(Продовження)… (4) Підсумок: використання uniq/ sort -uі вигадливий регулярний вираз Perl для відповідності лише останньому випадку в кожному рядку насправді не доповнює корисну відповідь на це запитання. Але навіть якщо вони це зробили, це все одно буде поганою відповіддю, оскільки ви не пояснюєте, як вони сприяють відповіді на питання. (Див. Відповідь Стефана Шазеласа на прикладі хорошого пояснення.)
G-Man каже "

ОП каже, що вони хотіли "показати лише рядки, що містять будь-яке з двох слів", що означає, що кожен рядок повинен оцінюватися самостійно. Я не бачу, чому ти вважаєш, що це не відповідає на питання. Наведіть приклад, який, на вашу думку, не вдасться.
Жро

Ой, що то , що ви мали в виду? “Прочитайте вхідний рядок одночасно і виконайте ці дві-три команди для кожного рядка . ? (1) Болісно незрозуміло, що це ви мали на увазі. (2) Це болісно неефективно. Чотири відповіді перед вашими показали, як обробляти весь файл за кілька команд (одна, дві чи чотири), і ви хочете виконати 3 ×  n команд для n рядків введення? Навіть якщо це спрацьовує, він заробляє голосування вниз за непотрібне дороге виконання. (3) Загрожуючи розщеплення волосся, він все ще не виконує завдання показувати відповідні лінії.
G-Man каже: "Відновіть Моніку"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.