Проверьте, существуют ли все несколько строк или регулярных выражений в файле
Я хочу проверить, существуют ли все мои строки в текстовом файле. Они могут существовать на одной линии или на разных линиях. И частичные совпадения должны быть в порядке. Как это:
...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on
В приведенном выше примере мы могли бы иметь регулярные выражения вместо строк.
Например, следующий код проверяет, существует ли какая-либо из моих строк в файле:
if grep -EFq "string1|string2|string3" file; then
# there is at least one match
fi
Как проверить, все ли они существуют? Поскольку нас просто интересует наличие всех совпадений, мы должны прекратить чтение файла, как только все строки будут сопоставлены.
Можно ли сделать это без необходимости вызывать grep
несколько раз (что не будет масштабироваться, если входной файл большой или если у нас есть большое количество строк для сопоставления) или использовать инструмент, подобный awk
или же python
?
Кроме того, есть ли решение для строк, которые могут быть легко расширены для регулярных выражений?
22 ответа
Awk - это инструмент, который ребята, которые изобрели grep, shell и т. Д., Изобрели для выполнения общих заданий по обработке текста, как это, поэтому не уверены, почему вы хотите попытаться избежать этого.
В случае, если вы ищете краткость, вот строка GNU awk one-liner, чтобы сделать именно то, что вы просили:
awk 'NR==FNR{a[$0];next} {for(s in a) if(!index($0,s)) exit 1}' strings RS='^$' file
А вот еще куча другой информации и опций:
Предполагая, что вы действительно ищете строки, это будет:
awk -v strings='string1 string2 string3' '
BEGIN {
numStrings = split(strings,tmp)
for (i in tmp) strs[tmp[i]]
}
numStrings == 0 { exit }
{
for (str in strs) {
if ( index($0,str) ) {
delete strs[str]
numStrings--
}
}
}
END { exit (numStrings ? 1 : 0) }
' file
вышеупомянутое остановит чтение файла, как только все строки будут сопоставлены.
Если вы ищете регулярные выражения вместо строк, то с GNU awk для RS с несколькими символами и сохранением $0 в разделе END вы можете сделать:
awk -v RS='^$' 'END{exit !(/regexp1/ && /regexp2/ && /regexp3/)}' file
На самом деле, даже если бы это были строки, вы могли бы сделать:
awk -v RS='^$' 'END{exit !(index($0,"string1") && index($0,"string2") && index($0,"string3"))}' file
Основная проблема с вышеуказанными 2 решениями GNU awk заключается в том, что, как и в решении GNU grep -P @ anubhava, весь файл должен считываться в память одновременно, тогда как с первым приведенным выше сценарием awk он будет работать в любом awk в любая оболочка в любом окне UNIX и хранит только одну строку ввода за раз.
Я вижу, вы добавили комментарий под своим вопросом, чтобы сказать, что вы можете иметь несколько тысяч "шаблонов". Предполагая, что вы имеете в виду "строки", вместо передачи их в качестве аргументов скрипту вы можете прочитать их из файла, например, с помощью GNU awk для RS с несколькими символами и файла с одной строкой поиска на строку:
awk '
NR==FNR { strings[$0]; next }
{
for (string in strings)
if ( !index($0,string) )
exit 1
}
' file_of_strings RS='^$' file_to_be_searched
и для регулярных выражений это будет:
awk '
NR==FNR { regexps[$0]; next }
{
for (regexp in regexps)
if ( $0 !~ regexp )
exit 1
}
' file_of_regexps RS='^$' file_to_be_searched
Если у вас нет GNU awk и ваш входной файл не содержит символов NUL, вы можете получить тот же эффект, что и выше, используя RS='\0'
вместо RS='^$'
или добавляя к переменной по одной строке за раз, когда она читается, а затем обрабатывая эту переменную в разделе END.
Если ваш file_to_be_searched слишком велик, чтобы поместиться в памяти, то это будет для строк:
awk '
NR==FNR { strings[$0]; numStrings=NR; next }
numStrings == 0 { exit }
{
for (string in strings) {
if ( index($0,string) ) {
delete strings[string]
numStrings--
}
}
}
END { exit (numStrings ? 1 : 0) }
' file_of_strings file_to_be_searched
и эквивалент для регулярных выражений:
awk '
NR==FNR { regexps[$0]; numRegexps=NR; next }
numRegexps == 0 { exit }
{
for (regexp in regexps) {
if ( $0 ~ regexp ) {
delete regexps[regexp]
numRegexps--
}
}
}
END { exit (numRegexps ? 1 : 0) }
' file_of_regexps file_to_be_searched
git grep
Вот синтаксис, использующий git grep
с несколькими шаблонами:
git grep --all-match --no-index -l -e string1 -e string2 -e string3 file
Вы также можете комбинировать шаблоны с логическими выражениями, такими как --and
, --or
а также --not
,
Проверьте man git-grep
за помощью.
--all-match
При предоставлении нескольких выражений шаблонов этот флаг указывается для ограничения соответствия файлам, строки которых соответствуют всем из них.
--no-index
Поиск файлов в текущем каталоге, который не управляется Git.
-l
/--files-with-matches
/--name-only
Показывать только имена файлов.
-e
Следующим параметром является шаблон. По умолчанию используется базовое регулярное выражение.
Другие параметры для рассмотрения:
--threads
Количество рабочих потоков grep для использования.
-q
/--quiet
/--silent
Не выводить совпавшие строки; выйти со статусом 0, когда есть совпадение.
Чтобы изменить тип шаблона, вы также можете использовать -G
/ --basic-regexp
(дефолт), -F
/ --fixed-strings
, -E
/ --extended-regexp
, -P
/ --perl-regexp
, -f file
, и другие.
Это gnu-awk
скрипт может работать:
cat fileSearch.awk
re == "" {
exit
}
{
split($0, null, "\\<(" re "\\>)", b)
for (i=1; i<=length(b); i++)
gsub("\\<" b[i] "([|]|$)", "", re)
}
END {
exit (re != "")
}
Тогда используйте это как:
if awk -v re='string1|string2|string3' -f fileSearch.awk file; then
echo "all strings were found"
else
echo "all strings were not found"
fi
Кроме того, вы можете использовать это gnu grep
решение с PCRE
опция:
grep -qzP '(?s)(?=.*\bstring1\b)(?=.*\bstring2\b)(?=.*\bstring3\b)' file
- С помощью
-z
мы делаемgrep
прочитать весь файл в одну строку. - Мы используем несколько косвенных утверждений, чтобы утверждать, что все строки присутствуют в файле.
- Regex должен использовать
(?s)
или жеDOTALL
мод, чтобы сделать.*
совпадать по всем линиям.
Согласно man grep
:
-z, --null-data
Treat input and output data as sequences of lines, each terminated by a
zero byte (the ASCII NUL character) instead of a newline.
Во-первых, вы, вероятно, хотите использовать awk
, Так как вы исключили эту опцию в формулировке вопроса, да, это возможно, и это дает возможность сделать это. Это, вероятно, НАМНОГО медленнее, чем при использовании awk
, но если вы все равно хотите это сделать...
Это основано на следующих предположениях:G
- Вызов AWK недопустим
- Вызов
grep
несколько раз недопустимо - Использование любых других внешних инструментов недопустимо
- Вызов
grep
менее одного раза приемлемо - Он должен вернуть успех, если все найдено, сбой, когда нет
- С помощью
bash
вместо внешних инструментов приемлемо bash
версия>= 3 для версии регулярного выражения
Это может удовлетворить все ваши требования: (версия регулярного выражения пропускает некоторые комментарии, вместо этого посмотрите строковую версию)
#!/bin/bash
multimatch() {
filename="$1" # Filename is first parameter
shift # move it out of the way that "$@" is useful
strings=( "$@" ) # search strings into an array
declare -a matches # Array to keep track which strings already match
# Initiate array tracking what we have matches for
for ((i=0;i<${#strings[@]};i++)); do
matches[$i]=0
done
while IFS= read -r line; do # Read file linewise
foundmatch=0 # Flag to indicate whether this line matched anything
for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes
if [ "${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet
string="${strings[$i]}" # fetch the string
if [[ $line = *$string* ]]; then # check if it matches
matches[$i]=1 # mark that we have found this
foundmatch=1 # set the flag, we need to check whether we have something left
fi
fi
done
# If we found something, we need to check whether we
# can stop looking
if [ "$foundmatch" -eq 1 ]; then
somethingleft=0 # Flag to see if we still have unmatched strings
for ((i=0;i<${#matches[@]};i++)); do
if [ "${matches[$i]}" -eq 0 ]; then
somethingleft=1 # Something is still outstanding
break # no need check whether more strings are outstanding
fi
done
# If we didn't find anything unmatched, we have everything
if [ "$somethingleft" -eq 0 ]; then return 0; fi
fi
done < "$filename"
# If we get here, we didn't have everything in the file
return 1
}
multimatch_regex() {
filename="$1" # Filename is first parameter
shift # move it out of the way that "$@" is useful
regexes=( "$@" ) # Regexes into an array
declare -a matches # Array to keep track which regexes already match
# Initiate array tracking what we have matches for
for ((i=0;i<${#regexes[@]};i++)); do
matches[$i]=0
done
while IFS= read -r line; do # Read file linewise
foundmatch=0 # Flag to indicate whether this line matched anything
for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes
if [ "${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet
regex="${regexes[$i]}" # Get regex from array
if [[ $line =~ $regex ]]; then # We use the bash regex operator here
matches[$i]=1 # mark that we have found this
foundmatch=1 # set the flag, we need to check whether we have something left
fi
fi
done
# If we found something, we need to check whether we
# can stop looking
if [ "$foundmatch" -eq 1 ]; then
somethingleft=0 # Flag to see if we still have unmatched strings
for ((i=0;i<${#matches[@]};i++)); do
if [ "${matches[$i]}" -eq 0 ]; then
somethingleft=1 # Something is still outstanding
break # no need check whether more strings are outstanding
fi
done
# If we didn't find anything unmatched, we have everything
if [ "$somethingleft" -eq 0 ]; then return 0; fi
fi
done < "$filename"
# If we get here, we didn't have everything in the file
return 1
}
if multimatch "filename" string1 string2 string3; then
echo "file has all strings"
else
echo "file miss one or more strings"
fi
if multimatch_regex "filename" "regex1" "regex2" "regex3"; then
echo "file match all regular expressions"
else
echo "file does not match all regular expressions"
fi
Ориентиры
Я сделал некоторые тесты поиска .c
,.h
а также .sh
в arch/arm/ из Linux 4.16.2 для строк "void", "function" и "#define". (Оболочки оболочки были добавлены / код настроен, что все можно назвать как testname <filename> <searchstring> [...]
и что if
можно использовать для проверки результата)
Результаты: (измерено с time
, real
время округляется до ближайшей половины секунды)
multimatch
: 49сmultimatch_regex
: 55с- Matchall: 10,5 с
- fileMatchesAllNames: 4s
- awk (первая версия): 4s
- agrep: 4.5с
- Perl re (-r): 10,5 с
- Perl non-re: 9,5 с
- Perl не оптимизирован для re: 5 с (удалена поддержка Getopt:: Std и regex для более быстрого запуска)
- Оптимизация Perl: 7 с (удалена поддержка Getopt:: Std и не-regex для более быстрого запуска)
- Git Grep: 3,5 с
- C версия (без регулярных выражений): 1,5 с
(Вызов grep
несколько раз, особенно с помощью рекурсивного метода, получилось лучше, чем я ожидал)
Самый простой способ проверить, содержит ли файл все три шаблона, - это получить только совпадающие шаблоны, вывести только уникальные детали и подсчитать количество строк. Тогда вы сможете проверить это с помощью простого условия теста: test 3 -eq $grep_lines
,
grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l)
Что касается вашего второго вопроса, я не думаю, что можно прекратить чтение файла, как только будет найдено более одного шаблона. Я прочитал справочную страницу для grep, и нет никаких опций, которые могли бы вам в этом помочь. Вы можете остановить чтение строк только после определенной с опцией grep -m [number]
что происходит независимо от соответствия шаблонов.
Уверен, что для этой цели нужна пользовательская функция.
Рекурсивное решение. Перебирайте файлы по одному. Для каждого файла проверьте, совпадает ли он с первым шаблоном, и разбейте рано (-m1: при первом совпадении), только если он соответствует первому шаблону, найдите второй шаблон и так далее:
#!/bin/bash
patterns="$@"
fileMatchesAllNames () {
file=$1
if [[ $# -eq 1 ]]
then
echo "$file"
else
shift
pattern=$1
shift
grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" $@
fi
}
for file in *
do
test -f "$file" && fileMatchesAllNames "$file" $patterns
done
Использование:
./allfilter.sh cat filter java
test.sh
Ищет в текущем каталоге токены "cat", "filter" и "java". Нашел их только в "test.sh".
Таким образом, grep часто вызывается в худшем случае (поиск первых N-1 шаблонов в последней строке каждого файла, за исключением N-го шаблона).
Но с осознанным упорядочением (сначала совпадает сначала, сначала раннее совпадение), если это возможно, решение должно быть достаточно быстрым, поскольку многие файлы отбрасываются рано, потому что они не соответствуют первому ключевому слову, или принимаются рано, так как они соответствуют ключевому слову close наверх
Пример: Вы ищете исходный файл scala, который содержит tailrec (довольно редко используется), изменяемый (редко используется, но если это так, близко к началу в операторах импорта) main (редко используется, часто не близко к началу) и println (часто используется, непредсказуемая позиция), вы бы заказали их:
./allfilter.sh mutable tailrec main println
Спектакль:
ls *.scala | wc
89 89 2030
В 89 файлах scala у меня есть распределение ключевых слов:
for keyword in mutable tailrec main println; do grep -m 1 $keyword *.scala | wc -l ; done
16
34
41
71
Поиск их с помощью слегка измененной версии скриптов, которая позволяет использовать файл шаблона в качестве первого аргумента, занимает около 0,2 с:
time ./allfilter.sh "*.scala" mutable tailrec main println
Filepattern: *.scala Patterns: mutable tailrec main println
aoc21-2017-12-22_00:16:21.scala
aoc25.scala
CondenseString.scala
Partition.scala
StringCondense.scala
real 0m0.216s
user 0m0.024s
sys 0m0.028s
в 15 000 строк кода:
cat *.scala | wc
14913 81614 610893
Обновить:
После прочтения в комментариях к вопросу, что мы можем говорить о тысячах шаблонов, передача их в качестве аргументов не кажется умной идеей; лучше прочитать их из файла и передать имя файла в качестве аргумента - возможно, для фильтрации списка файлов тоже:
#!/bin/bash
filelist="$1"
patternfile="$2"
patterns="$(< $patternfile)"
fileMatchesAllNames () {
file=$1
if [[ $# -eq 1 ]]
then
echo "$file"
else
shift
pattern=$1
shift
grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" $@
fi
}
echo -e "Filepattern: $filepattern\tPatterns: $patterns"
for file in $(< $filelist)
do
test -f "$file" && fileMatchesAllNames "$file" $patterns
done
Если количество и длина шаблонов / файлов превышает возможности передачи аргументов, список шаблонов можно разбить на множество шаблонных файлов и обработать их в цикле (например, из 20 файлов шаблонов):
for i in {1..20}
do
./allfilter2.sh file.$i.lst pattern.$i.lst > file.$((i+1)).lst
done
Вы можете
использовать
-o
|--only-matching
вариантgrep
(который заставляет выводить только совпадающие части совпадающей строки, причем каждая такая часть находится на отдельной выходной строке),затем устранить повторяющиеся совпадения совпадающих строк с
sort -u
,и, наконец, проверьте, что количество оставшихся строк равно количеству входных строк.
Демонстрация:
$ cat input
...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on
$ grep -o -F $'string1\nstring2\nstring3' input|sort -u|wc -l
3
$ grep -o -F $'string1\nstring3' input|sort -u|wc -l
2
$ grep -o -F $'string1\nstring2\nfoo' input|sort -u|wc -l
2
Одним из недостатков этого решения (несоблюдение частичных соответствий должно быть нормальным требованием) является то, что grep
не обнаруживает совпадения совпадений. Например, хотя текст abcd
соответствует обоим abc
а также bcd
, grep
находит только один из них:
$ grep -o -F $'abc\nbcd' <<< abcd
abc
$ grep -o -F $'bcd\nabc' <<< abcd
abc
Обратите внимание, что этот подход / решение работает только для фиксированных строк. Его нельзя расширить для регулярных выражений, поскольку одно регулярное выражение может соответствовать нескольким различным строкам, и мы не можем отследить, какое совпадение соответствует какому регулярному выражению. Лучшее, что вы можете сделать, это сохранить совпадения во временном файле, а затем запустить grep
несколько раз, используя одно регулярное выражение одновременно.
Решение реализовано в виде bash-скрипта:
Matchall:
#!/usr/bin/env bash
if [ $# -lt 2 ]
then
echo "Usage: $(basename "$0") input_file string1 [string2 ...]"
exit 1
fi
function find_all_matches()
(
infile="$1"
shift
IFS=$'\n'
newline_separated_list_of_strings="$*"
grep -o -F "$newline_separated_list_of_strings" "$infile"
)
string_count=$(($# - 1))
matched_string_count=$(find_all_matches "$@"|sort -u|wc -l)
if [ "$matched_string_count" -eq "$string_count" ]
then
echo "ALL strings matched"
exit 0
else
echo "Some strings DID NOT match"
exit 1
fi
Демонстрация:
$ ./matchall
Usage: matchall input_file string1 [string2 ...]
$ ./matchall input string1 string2 string3
ALL strings matched
$ ./matchall input string1 string2
ALL strings matched
$ ./matchall input string1 string2 foo
Some strings DID NOT match
Это интересная проблема, и на странице руководства grep нет ничего очевидного, чтобы предложить легкий ответ. Там может быть безумное регулярное выражение, которое сделает это, но может быть более понятным с простой цепочкой greps, даже если это заканчивается сканирование файла n раз. По крайней мере, опция -q заставляет его отображаться в первом совпадении каждый раз, а && будет ускорять оценку, если одна из строк не найдена.
$grep -Fq string1 t && grep -Fq string2 t && grep -Fq string3 t
$echo $?
0
$grep -Fq string1 t && grep -Fq blah t && grep -Fq string3 t
$echo $?
1
Возможно с гну сед
кошка match_word.sh
sed -z '
/\b'"$2"'/!bA
/\b'"$3"'/!bA
/\b'"$4"'/!bA
/\b'"$5"'/!bA
s/.*/0\n/
q
:A
s/.*/1\n/
' "$1"
и ты называешь это так:
./match_word.sh infile string1 string2 string3
вернуть 0, если все совпадения найдены еще 1
здесь можно посмотреть 4 строки
если вы хотите больше, вы можете добавить такие строки, как
/\b'"$x"'/!bA
Игнорирование "Можно ли сделать это без... или использовать такой инструмент, как awk
или же python
?", вы можете сделать это с помощью скрипта Perl:
(Используйте подходящий shebang для вашей системы или что-то вроде /bin/env perl
)
#!/usr/bin/perl
use Getopt::Std; # option parsing
my %opts;
my $filename;
my @patterns;
getopts('rf:',\%opts); # Allowing -f <filename> and -r to enable regex processing
if ($opts{'f'}) { # if -f is given
$filename = $opts{'f'};
@patterns = @ARGV[0 .. $#ARGV]; # Use everything else as patterns
} else { # Otherwise
$filename = $ARGV[0]; # First parameter is filename
@patterns = @ARGV[1 .. $#ARGV]; # Rest is patterns
}
my $use_re= $opts{'r'}; # Flag on whether patterns are regex or not
open(INF,'<',$filename) or die("Can't open input file '$filename'");
while (my $line = <INF>) {
my @removal_list = (); # List of stuff that matched that we don't want to check again
for (my $i=0;$i <= $#patterns;$i++) {
my $pattern = $patterns[$i];
if (($use_re&& $line =~ /$pattern/) || # regex match
(!$use_re&& index($line,$pattern) >= 0)) { # or string search
push(@removal_list,$i); # Mark to be removed
}
}
# Now remove everything we found this time
# We need to work backwards to keep us from messing
# with the list while we're busy
for (my $i=$#removal_list;$i >= 0;$i--) {
splice(@patterns,$removal_list[$i],1);
}
if (scalar(@patterns) == 0) { # If we don't need to match anything anymore
close(INF) or warn("Error closing '$filename'");
exit(0); # We found everything
}
}
# End of file
close(INF) or die("Error closing '$filename'");
exit(1); # If we reach this, we haven't matched everything
Сохраняется как matcher.pl
это будет искать строки простого текста:
./matcher filename string1 string2 string3 'complex string'
Это будет искать регулярные выражения:
./matcher -r filename regex1 'regex2' 'regex4'
(Имя файла может быть дано с -f
вместо):
./matcher -f filename -r string1 string2 string3 'complex string'
Он ограничен шаблонами сопоставления одной строки (из-за работы с файлом).
Производительность при вызове большого количества файлов из сценария оболочки ниже, чем awk
(Но шаблоны поиска могут содержать пробелы, в отличие от тех, которые пропущены через пробел в -v
в awk
). Если преобразовать в функцию и вызвать из кода Perl (с файлом, содержащим список файлов для поиска), он должен быть намного быстрее, чем большинство awk
Реализации. (При вызове нескольких небольших файлов время запуска perl (синтаксический анализ и т. Д. Сценария) доминирует во времени)
Это может быть значительно ускорено путем жесткого кодирования, используются ли регулярные выражения или нет, за счет гибкости. (Смотрите мои тесты здесь, чтобы увидеть, какой эффект удаления Getopt::Std
есть)
perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ? "Match": "No Match"}' file
Просто для "полноты решений" вы можете использовать другой инструмент и избегать множественных greps и awk/sed или больших (и, вероятно, медленных) циклов оболочки; Такой инструмент является agrep.
agrep
на самом деле своего рода egrep
поддерживая также and
работа между шаблонами, используя ;
в качестве разделителя шаблонов.
подобно egrep
и, как большинство известных инструментов, agrep
это инструмент, который работает с записями / строками, и поэтому нам все еще нужен способ обрабатывать весь файл как одну запись.
Кроме того, Агреп обеспечивает -d
возможность установить свой собственный разделитель записей.
Некоторые тесты:
$ cat file6
str4
str1
str2
str3
str1 str2
str1 str2 str3
str3 str1 str2
str2 str3
$ agrep -d '$$\n' 'str3;str2;str1;str4' file6;echo $?
str4
str1
str2
str3
str1 str2
str1 str2 str3
str3 str1 str2
str2 str3
0
$ agrep -d '$$\n' 'str3;str2;str1;str4;str5' file6;echo $?
1
$ agrep -p 'str3;str2;str1' file6 #-p prints lines containing all three patterns in any position
str1 str2 str3
str3 str1 str2
Ни один инструмент не идеален, и agrep
имеет также некоторые ограничения; Вы не можете использовать регулярное выражение / шаблон длиннее 32 символов, и некоторые опции недоступны при использовании регулярных выражений - все это объяснено на странице руководства agrep.
Предполагая, что все ваши строки для проверки находятся в файле strings.txt, а файл, который вы хотите проверить, - input.txt, подойдет следующая строка:
Обновлен ответ на основе комментариев:
$ diff <( sort -u strings.txt ) <( grep -o -f strings.txt input.txt | sort -u )
Пояснение:
Используйте опцию -o grep, чтобы соответствовать только интересующим вас строкам. Это дает все строки, которые присутствуют в файле input.txt. Затем используйте diff, чтобы получить строки, которые не найдены. Если бы все строки были найдены, результат был бы ничем. Или просто проверьте код выхода diff.
Что он не делает:
- Выходите, как только все совпадения найдены.
- Расширяемый в regx.
- Перекрывающиеся спички.
Что он делает:
- Найти все совпадения.
- Разовый вызов grep.
- Не использует awk или python.
В python использование модуля fileinput позволяет указывать файлы в командной строке или читать текст построчно из stdin. Вы можете жестко закодировать строки в список Python.
# Strings to match, must be valid regular expression patterns
# or be escaped when compiled into regex below.
strings = (
r'string1',
r'string2',
r'string3',
)
или читать строки из другого файла
import re
from fileinput import input, filename, nextfile, isfirstline
for line in input():
if isfirstline():
regexs = map(re.compile, strings) # new file, reload all strings
# keep only strings that have not been seen in this file
regexs = [rx for rx in regexs if not rx.match(line)]
if not regexs: # found all strings
print filename()
nextfile()
Я не уверен, что не получил вопрос, потому что ответ на python выглядит невероятно простым, в то время как есть много длинных и подробных (?) Ответов.
all(i in open(file).read() for i in list_of_strings)
Это просто не заботится о регулярных выражениях, или строках, или о чем-то еще, просто проверяет, находятся ли все необработанные строки (которые также могут быть регулярными выражениями, так как они также являются кучей букв), в файле или нет.
Еще один вариант Perl - когда все заданные строки совпадают... даже когда файл читается наполовину, обработка завершается и просто печатает результаты
> perl -lne ' /\b(string1|string2|string3)\b/ and $m{$1}++; eof if keys %m == 3; END { print keys %m == 3 ? "Match": "No Match"}' all_match.txt
Match
> perl -lne ' /\b(string1|string2|stringx)\b/ and $m{$1}++; eof if keys %m == 3; END { print keys %m == 3 ? "Match": "No Match"}' all_match.txt
No Match
Для простой скорости, без ограничений внешнего инструмента и без регулярных выражений, эта (сырая) версия C делает достойную работу. (Возможно, только Linux, хотя он должен работать на всех Unix-подобных системах с mmap
)
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/* https://stackru.com/a/8584708/1837991 */
inline char *sstrstr(char *haystack, char *needle, size_t length)
{
size_t needle_length = strlen(needle);
size_t i;
for (i = 0; i < length; i++) {
if (i + needle_length > length) {
return NULL;
}
if (strncmp(&haystack[i], needle, needle_length) == 0) {
return &haystack[i];
}
}
return NULL;
}
int matcher(char * filename, char ** strings, unsigned int str_count)
{
int fd;
struct stat sb;
char *addr;
unsigned int i = 0; /* Used to keep us from running of the end of strings into SIGSEGV */
fd = open(filename, O_RDONLY);
if (fd == -1) {
fprintf(stderr,"Error '%s' with open on '%s'\n",strerror(errno),filename);
return 2;
}
if (fstat(fd, &sb) == -1) { /* To obtain file size */
fprintf(stderr,"Error '%s' with fstat on '%s'\n",strerror(errno),filename);
close(fd);
return 2;
}
if (sb.st_size <= 0) { /* zero byte file */
close(fd);
return 1; /* 0 byte files don't match anything */
}
/* mmap the file. */
addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr == MAP_FAILED) {
fprintf(stderr,"Error '%s' with mmap on '%s'\n",strerror(errno),filename);
close(fd);
return 2;
}
while (i++ < str_count) {
char * found = sstrstr(addr,strings[0],sb.st_size);
if (found == NULL) { /* If we haven't found this string, we can't find all of them */
munmap(addr, sb.st_size);
close(fd);
return 1; /* so give the user an error */
}
strings++;
}
munmap(addr, sb.st_size);
close(fd);
return 0; /* if we get here, we found everything */
}
int main(int argc, char *argv[])
{
char *filename;
char **strings;
unsigned int str_count;
if (argc < 3) { /* Lets count parameters at least... */
fprintf(stderr,"%i is not enough parameters!\n",argc);
return 2;
}
filename = argv[1]; /* First parameter is filename */
strings = argv + 2; /* Search strings start from 3rd parameter */
str_count = argc - 2; /* strings are two ($0 and filename) less than argc */
return matcher(filename,strings,str_count);
}
Скомпилируйте это с:
gcc matcher.c -o matcher
Запустите это с:
./matcher filename needle1 needle2 needle3
Кредиты:
- использует sstrstr
- Обработка файлов в основном украдена из
mmap
справочная страница
Заметки:
- Он будет сканировать части файла, предшествующие совпадающим строкам, несколько раз - хотя файл откроется только один раз.
- Весь файл может оказаться загруженным в память, особенно если строка не совпадает, ОС должна решить, что
- Поддержка регулярных выражений, вероятно, может быть добавлена с помощью библиотеки регулярных выражений POSIX (производительность, вероятно, будет немного выше, чем у grep - она должна быть основана на той же библиотеке, и вы получите меньше накладных расходов, если откроете файл только один раз для поиска нескольких регулярных выражений)
- Файлы, содержащие нули, должны работать, поиск строк с ними не так...
- Все символы, кроме нуля, должны быть доступны для поиска (\r, \n и т. Д.)
Следующие python
сценарий должен сделать свое дело. Это своего рода называется эквивалентом grep
(re.search
) несколько раз для каждой строки - т. е. если он ищет каждый шаблон для каждой строки, но, поскольку вы не выполняете процесс каждый раз, он должен быть намного более эффективным. Кроме того, он удаляет шаблоны, которые уже были найдены, и останавливается, когда все они были найдены.
#!/usr/bin/env python
import re
# the file to search
filename = '/path/to/your/file.txt'
# list of patterns -- can be read from a file or command line
# depending on the count
patterns = [r'py.*$', r'\s+open\s+', r'^import\s+']
patterns = map(re.compile, patterns)
with open(filename) as f:
for line in f:
# search for pattern matches
results = map(lambda x: x.search(line), patterns)
# remove the patterns that did match
results = zip(results, patterns)
results = filter(lambda x: x[0] == None, results)
patterns = map(lambda x: x[1], results)
# stop if no more patterns are left
if len(patterns) == 0:
break
# print the patterns which were not found
for p in patterns:
print p.pattern
Вы можете добавить отдельную проверку для простых строк (string in line
), если вы имеете дело с простыми (не регулярными) строками - будет немного эффективнее.
Это решает вашу проблему?
Многие из этих ответов хороши, насколько они идут.
Но если производительность является проблемой - конечно, возможной, если ввод велик, и у вас есть много тысяч образцов - тогда вы получите значительное ускорение, используя такой инструмент, как lex
или же flex
который генерирует истинный детерминированный конечный автомат в качестве распознавателя, а не вызывает интерпретатор регулярных выражений один раз для каждого шаблона.
Конечный автомат выполнит несколько машинных инструкций для каждого входного символа независимо от количества шаблонов.
Гибкое решение без излишеств:
%{
void match(int);
%}
%option noyywrap
%%
"abc" match(0);
"ABC" match(1);
[0-9]+ match(2);
/* Continue adding regex and exact string patterns... */
[ \t\n] /* Do nothing with whitespace. */
. /* Do nothing with unknown characters. */
%%
// Total number of patterns.
#define N_PATTERNS 3
int n_matches = 0;
int counts[10000];
void match(int n) {
if (counts[n]++ == 0 && ++n_matches == N_PATTERNS) {
printf("All matched!\n");
exit(0);
}
}
int main(void) {
yyin = stdin;
yylex();
printf("Only matched %d patterns.\n", n_matches);
return 1;
}
Недостатком является то, что вам придется создавать это для каждого данного набора шаблонов. Это не так уж плохо
flex matcher.y
gcc -O lex.yy.c -o matcher
Теперь запустите это:
./matcher < input.txt
Сначала удалите разделитель строк, а затем несколько раз используйте обычный grep в качестве количества шаблонов, как показано ниже.
Пример: пусть содержимое файла будет таким, как показано ниже.
PAT1
PAT2
PAT3
something
somethingelse
cat file | tr -d "\n" | grep "PAT1" | grep "PAT2" | grep -c "PAT3"
Я не видел простой счетчик среди ответов, так что вот решение, ориентированное на счетчик с использованием awk
это останавливается, как только все совпадения удовлетворены:
/string1/ { a = 1 }
/string2/ { b = 1 }
/string3/ { c = 1 }
{
if (c + a + b == 3) {
print "Found!";
exit;
}
}
Общий сценарий
расширить использование через аргументы оболочки:
#! /bin/sh
awk -v vars="$*" -v argc=$# '
BEGIN { split(vars, args); }
{
for (arg in args) {
if (!temp[arg] && $0 ~ args[arg]) {
inc++;
temp[arg] = 1;
}
}
if (inc == argc) {
print "Found!";
exit;
}
}
END { exit 1; }
' filename
Использование (в котором вы можете передавать регулярные выражения):
./script "str1?" "(wo)?men" str3
или применить строку шаблонов:
./script "str1? (wo)?men str3"
$ cat allstringsfile | tr '\n' ' ' | awk -f awkpattern1
Где allstringsfile - ваш текстовый файл, как в оригинальном вопросе. awkpattern1 содержит строковые шаблоны с условием &&:
$ cat awkpattern1
/string1/ && /string2/ && /string3/