Как запустить grep с несколькими AND-шаблонами?

Вопрос или проблема

Я хотел бы получить совпадение с несколькими шаблонами с неявным AND между шаблонами, т.е. эквивалентно выполнению нескольких greps последовательно:

grep pattern1 | grep pattern2 | ...

Как это можно преобразовать в нечто подобное?

grep pattern1 & pattern2 & pattern3

Я хотел бы использовать один grep, потому что я строю аргументы динамически, поэтому все должно уместиться в одной строке. Использование фильтра является системной функцией, а не grep, поэтому это не аргумент для него.


Не путайте этот вопрос с:

grep "pattern1\|pattern2\|..."

Это OR совпадение с несколькими шаблонами. Я ищу совпадение с AND шаблоном.

Чтобы найти строки, соответствующие каждому шаблону из списка, agrep (оригинальный, теперь поставляется с glimpse, не путать с другим из библиотеки TRE regexp library) позволяет сделать это с помощью следующего синтаксиса:

agrep 'pattern1;pattern2'

С помощью GNU grep, когда он построен с поддержкой PCRE, вы можете использовать несколько утверждений предварительного просмотра:

grep -P '^(?=.*pattern1)(?=.*pattern2)'

С ast grep:

grep -X '.*pattern1.*&.*pattern2.*'

(добавляя .*, так как <x>&<y> соответствует строкам, которые соответствуют как <x>, так и <y> в точности, a&b никогда не совпадет, так как нет такой строки, которая может быть одновременно и a, и b).

Если шаблоны не пересекаются, вы также можете:

grep -e 'pattern1.*pattern2' -e 'pattern2.*pattern1'

Наиболее портативный способ вероятно с awk, как уже упоминалось:

awk '/pattern1/ && /pattern2/'

Или с sed:

sed -e '/pattern1/!d' -e '/pattern2/!d'

Или perl:

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

Обратите внимание, что все они будут иметь разные синтаксисы регулярных выражений.

Варианты с awk/sed/perl не отражают, соответствует ли какая-либо строка шаблонам в их статусе выхода. Для этого необходимо:

awk '/pattern1/ && /pattern2/ {print; found = 1}
     END {exit !found}'
perl -ne 'if (/pattern1/ && /pattern2/) {print; $found = 1}
          END {exit !$found}'

Или перенаправьте команду в grep '^'.

Для потенциально сжатых файлов gzip, вы можете использовать zgrep, который обычно является оболочечным скриптом-оболочкой для grep, и использовать одно из вышеупомянутых решений grep (не с ast-open, так как эта реализация grep не может использоваться zgrep) или вы можете использовать модуль PerlIO::gzip из perl, который может прозрачно распаковать файлы на входе:

perl -MPerlIO::gzip -Mopen='IN,gzip(autopop)' -ne '
  print "$ARGV:$_" if /pattern1/ && /pattern2/' -- *.gz

(который, если файлы достаточно малы, даже будет более эффективным, чем zgrep, так как распаковка производится внутри без необходимости запускать gunzip для каждого файла).

Вы не указали версию grep, это важно. Некоторые движки регулярных выражений позволяют множественное совпадение, сгруппированное через AND с использованием ‘&’, но это нестандартная и непереносимая функция. Однако, по крайней мере, GNU grep не поддерживает это.

Вместо этого вы можете просто заменить grep на sed, awk, perl и т.д. (перечислены в порядке увеличения веса). С awk команда будет выглядеть так:

awk '/regexp1/ && /regexp2/ && /regexp3/ { print; }'

и она может быть сконструирована таким образом, чтобы быть указанной в командной строке простым способом.

git grep

Вот синтаксис с использованием git grep, объединяющий несколько шаблонов с помощью Булевых выражений:

git grep --no-index -e pattern1 --and -e pattern2 --and -e pattern3

Вышеуказанная команда будет выводить строки, которые соответствуют всем шаблонам одновременно.

--no-index Ищет файлы в текущем каталоге, который не управляется Git.

Посмотрите man git-grep для помощи.

Смотрите также:

Для операции OR смотрите:

grep pattern1 | grep pattern2 | ...

Я хотел бы использовать один grep, потому что я строю аргументы динамически, так что все должно уместиться в одну строку.

Фактически, можно построить конвейер динамически (не прибегая к eval):

# Исполняет: grep "$1" | grep "$2" | grep "$3" | ...
function chained-grep {
    local pattern="$1"
    if [[ -z "$pattern" ]]; then
        cat
        return
    fi    

    shift
    grep -- "$pattern" | chained-grep "$@"
}

cat something | chained-grep all patterns must match order but matter dont

Вероятно, это не очень эффективное решение.

Если patterns содержит один шаблон на строку, вы можете сделать что-то вроде этого:

awk 'NR==FNR{a[$0];next}{for(i in a)if($0!~i)next}1' patterns -

Или это совпадает с подстроками, а не регулярными выражениями:

awk 'NR==FNR{a[$0];next}{for(i in a)if(!index($0,i))next}1' patterns -

Чтобы напечатать все вместо отсутствия строк ввода в случае, если patterns пустой, замените NR==FNR на FILENAME==ARGV[1], или на ARGIND==1 в gawk.

Эти функции печатают строки STDIN, которые содержат каждую строку, заданную в качестве аргумента, как подстроку. ga означает grep all, а gai игнорирует регистр.

ga(){ awk 'FILENAME==ARGV[1]{a[$0];next}{for(i in a)if(!index($0,i))next}1' <(printf %s\n "$@") -; }
gai(){ awk 'FILENAME==ARGV[1]{a[tolower($0)];next}{for(i in a)if(!index(tolower($0),i))next}1' <(printf %s\n "$@") -; }

Вот мое мнение, и это работает для слов в нескольких строках:

Используйте find . -type f, за которым следует столько
-exec grep -q 'first_word' {} \;
и последнее ключевое слово с
-exec grep -l 'nth_word' {} \;

-q беззвучный / тихий
-l показывать файлы с совпадениями

Следующее возвращает список имен файлов с словами ‘rabbit’ и ‘hole’ в них:
find . -type f -exec grep -q 'rabbit' {} \; -exec grep -l 'hole' {} \;

чтобы искать в нескольких файлах наличие двух шаблонов где угодно в файле, используйте

awk -v RS="" '/patern1/&&/patern2/{print FILENAME}' file1 ... filen

ripgrep

Вот пример использования rg:

rg -N '(?P<p1>.*pattern1.*)(?P<p2>.*pattern2.*)(?P<p3>.*pattern3.*)' file.txt

Это один из самых быстрых инструментов для grep, так как построен на основе регулярного выражения Rust, которое использует конечные автоматы, SIMD и агрессивные оптимизации литералов, чтобы сделать поиск очень быстрым.

См. также связанный запрос на добавление функции на GH-875.

просто напрямую УМНОЖЬТЕ шаблоны, если хотите, чтобы они все были истинными, таким образом устраняя любые и все условные ветвления

awk '/regexp1/ * /regexp2/ * /regexp3/ … '

скажем, если вам нужно, чтобы regex 4 было ЛОЖНО, в то время как regex 5/6 оба были ИСТИННЫМИ, то вы можете объединить их все в одно сравнение :

awk '/regexp4/ < /regexp5/ * /regexp6/'

или скажем, если вы хотите совпадение либо с regex 7, либо с regex 8, но не оба одновременно, тогда сделайте любой из этих вариантов

логично "!=" НЕ РАВНО

awk '/regexp7/ != /regexp8/'

арифметическое "-" МИНУС, так как [ А XOR Б ] на уровне одного бита 
то же самое, что проверка на ненулевой результат вычитания

awk '/regexp7/ - /regexp8/'

Реальный пример комбинации этой комбинации – проверка, содержит ли данный месяц 31 день или нет:

jot 12 | awk '(_ = +$1) % 2 != (7 < _)'
                                        или                   
         awk '((_ = +$1) + (7 < _)) % 2'

     1
     3
     5
     7
     8
    10
    12

Обратная проверка на неполный месяц будет :

jot 12 | awk  '(_ = +$1) % 2 == (7 < _)'
         awk  '(_ = +$1) % 2 -  (_ < 8)'                               
         awk '((_ = +$1)     +  (_ < 8)) % 2'

     2
     4
     6
     9
    11

вот еще самый странный – если вы хотите, чтобы regex 9 был ИСТИННЫМ, а regex 10 – ЛОЖНЫМ, и хотите сделать это без условного ветвления :

awk '/regexp9/ ^ /regexp10/'

Верно – regex 9 В СТЕПЕНИ regex 10. Это работает потому что

    1 1 1^1 ->  1
    1 0 1^0 ->  1
    0 1 0^1 -> [0]
    0 0 0^0 ->  1

Таким образом, единственный случай, когда это алгебраическое выражение возвращает ЛОЖЬ, будет тогда, когда regex 9 ЛОЖНЫЙ, а regex 10 ИСТИННЫЙ. Его близнец через логические операторы сравнения будет :

awk '/regexp9/ >= /regexp10/'

Все эти выражения могут не казаться идиоматическими, но они все соответствуют стандарту POSIX awk, и полностью портативны.

Хотя это может быть не элегантно или быстро, но легко запомнить. Для двух шаблонов:

grep B $(grep -l A *)

Вы можете передать список файлов (-l), которые соответствуют вашему первому шаблону, следующему grep, например:

➜ grep A *
a:A
ab:A

➜ grep B *
ab:B
b:B

➜ grep B $(grep -l A *)
ab:B

Вы также можете вложить их, но придется добавить -l:

grep -l C $(grep -l B $(grep -l A *))

Для поиска всех слов (или шаблонов), вы можете запустить grep в цикле for. Главным преимуществом здесь является поиск из списка регулярных выражений.

Реальный пример:

# Файл 'search_all_regex_and_error_if_missing.sh'

find_list="\
^a+$ \
^b+$ \
^h+$ \
^d+$ \
"

for item in $find_list; do
   if grep -E "$item" file_to_search_within.txt
   then
       echo "$item найден в файле."
   else
       echo "Ошибка: $item не найден в файле. Выход!"
       exit 1
   fi
done

Теперь запустим его на этом файле:

hhhhhhhhhh
aaaaaaa
bbbbbbbbb
ababbabaabbaaa
ccccccc
dsfsdf
bbbb
cccdd
aa
caa
$ ./search_all_regex_and_error_if_missing.sh
aaaaaaa aa
^a+$ найден в файле.
bbbbbbbbb bbbb
^b+$ найден в файле.
hhhhhhhhhh
^h+$ найден в файле.
Ошибка: ^d+$ не найден в файле. Выход!

Ответ или решение

Использование grep с несколькими паттернами, соединенными логическим оператором AND, представляет собой достаточно распространенную задачу в области информационных технологий. Это может быть востребовано в различных случаях, когда необходимо проверить, что строка удовлетворяет нескольким условиям одновременно. На первый взгляд задача может показаться сложной, поскольку штатная функция grep не предоставляет прямой поддержки для такой операции. Однако существует множество подходов, которые могут помочь в ее решении, в зависимости от доступных инструментов и требований к кроссплатформенности. В следующих разделах мы рассмотрим теорию, примеры и практическое применение для решения этой задачи.

Теория

Основная задача состоит в поиске строк, которые содержат все заданные паттерны одновременно. Стандартный grep поддерживает использование операторов OR для поиска строк, содержащих один из указанных паттернов, но не предоставляет встроенной функциональности для соединения паттернов логическим оператором AND.

Однако, доступные реализации grep имеют разные возможности. Например, GNU grep, с поддержкой PCRE (Perl Compatible Regular Expressions), позволяет использовать утверждения о предварительном просмотре (lookahead assertions), чтобы добиться эффекта логического AND.

Пример

Рассмотрим, как можно использовать различные инструменты для достижения цели:

  1. GNU grep с PCRE:

    grep -P '^(?=.*pattern1)(?=.*pattern2)' file.txt

    Здесь мы используем утверждения о предварительном просмотре, которые проверяют наличие каждого из паттернов в строке.

  2. awk:

    awk '/pattern1/ && /pattern2/' file.txt

    awk обеспечивает широкий функционал для работы с текстами, где можно задать сразу несколько условий для проверки строк.

  3. sed:

    sed -e '/pattern1/!d' -e '/pattern2/!d' file.txt

    sed позволяет последовательно удалять строки, которые не содержат указанные паттерны, добиваясь того же результата AND.

  4. perl:

    perl -ne 'print if /pattern1/ && /pattern2/' file.txt

    Используя perl, можно легко составлять сложные логические конструкции для поиска по нескольким условиям одновременно.

  5. git grep:

    git grep --no-index -e pattern1 --and -e pattern2 file.txt

    Если используется репозиторий Git и требуется искать в файлах, не отслеживаемых системой контроля версий, git grep предоставляет удобный способ с поддержкой булевых выражений.

Применение

Выбор подхода зависит от конкретной ситуации, включая следующие аспекты:

  • Требования к производительности. При обработке больших файлов или множества файлов инструменты, оптимизированные для быстрого поиска, например, ripgrep с движком регулярных выражений на базе Rust, могут быть предпочтительными:

    rg -N '(?P<pat1>.*pattern1.*)(?P<pat2>.*pattern2.*)' file.txt
  • Совместимость платформ. Если необходимо обеспечивать кроссплатформенность, следует выбирать инструменты и подходы, широко поддерживаемые на разных операционных системах.

  • Удобство написания и поддержки. Например, использование awk может быть предпочтительным из-за его читабельности и мощных функциональных возможностей поиска и обработки текстов.

Заключение

Использование grep или альтернативных инструментов для поиска строк, удовлетворяющих нескольким условиям, требует глубокого понимания доступных функций и ограничений. Правильный подход позволит эффективно решать задачи поиска и работы с текстами, улучшая производительность систем и повышая эффективность рабочих процессов. В зависимости от версии grep и доступных инструментов, решений может быть множество, и выбор следует делать, опираясь на специфические требования текущей задачи.

Оцените материал
Добавить комментарий

Капча загружается...