Вопрос или проблема
Я хотел бы получить совпадение с несколькими шаблонами с неявным 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
для помощи.
Смотрите также:
- Как использовать grep для совпадения string1 И string2?
- Проверка, существуют ли все из нескольких строк или регулярных выражений в файле
Для операции 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.
Пример
Рассмотрим, как можно использовать различные инструменты для достижения цели:
-
GNU grep с PCRE:
grep -P '^(?=.*pattern1)(?=.*pattern2)' file.txt
Здесь мы используем утверждения о предварительном просмотре, которые проверяют наличие каждого из паттернов в строке.
-
awk:
awk '/pattern1/ && /pattern2/' file.txt
awk
обеспечивает широкий функционал для работы с текстами, где можно задать сразу несколько условий для проверки строк. -
sed:
sed -e '/pattern1/!d' -e '/pattern2/!d' file.txt
sed
позволяет последовательно удалять строки, которые не содержат указанные паттерны, добиваясь того же результата AND. -
perl:
perl -ne 'print if /pattern1/ && /pattern2/' file.txt
Используя
perl
, можно легко составлять сложные логические конструкции для поиска по нескольким условиям одновременно. -
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
и доступных инструментов, решений может быть множество, и выбор следует делать, опираясь на специфические требования текущей задачи.