Проверьте, существует ли в файле все несколько строк или регулярных выражений
Я хочу проверить, существуют ли все мои строки в текстовом файле. Они могут существовать в одной строке или на разных линиях. И частичные совпадения должны быть в порядке. Как это:
...
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
?
Кроме того, существует ли решение для строк, которое можно легко расширить для регулярных выражений?
Ответы
Ответ 1
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 для множественного char 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 заключается в том, что, как и решение @anubhava GNU grep -P, весь файл должен считываться в память за один раз, тогда как с первым awk-скриптом выше он будет работать в любом awk в любой оболочке в любом ящике UNIX и хранит только одну строку ввода за раз.
Я вижу, вы добавили комментарий под своим вопросом, чтобы сказать, что у вас может быть несколько тысяч "шаблонов". Предполагая, что вы имеете в виду "строки", вместо того, чтобы передавать их в качестве аргументов сценария, вы можете прочитать их из файла, например, с GNU awk для multi-char 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
Ответ 2
Вот синтаксис с использованием 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
-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
и другие.
Ответ 3
Этот скрипт 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
читаем полный файл в одну строку. - Мы используем несколько утверждений lookahead, чтобы утверждать, что все строки присутствуют в файле.
-
DOTALL
должно использовать (?s)
или DOTALL
mod, чтобы сделать .*
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.
Ответ 4
Во-первых, вы, вероятно, хотите использовать awk
. Поскольку вы устранили этот параметр в вопросе, да, это можно сделать, и это дает возможность сделать это. Вероятно, это намного медленнее, чем использование awk
, но если вы все равно хотите это сделать...
Это основано на следующих предположениях: G
- Вызов AWK неприемлем
- Вызов
grep
несколько раз неприемлем - Использование любых других внешних инструментов неприемлемо
- Вызов
grep
менее одного раза допустим - Он должен вернуть успех, если все будет найдено, сбой, если не
-
bash
использование bash
вместо внешних инструментов - версия
bash
> = 3 для версии регулярного выражения
Это может удовлетворить все ваши требования: (в версии regex пропустите несколько комментариев, посмотрите на строковую версию)
#!/bin/bash
multimatch() {
filename="$1" # Filename is first parameter
shift # move it out of the way that "[email protected]" is useful
strings=( "[email protected]" ) # 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 "[email protected]" is useful
regexes=( "[email protected]" ) # 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
времени округляется до ближайшей половины секунды)
(Вызов grep
несколько раз, особенно с помощью рекурсивного метода, сделал лучше, чем я ожидал)
Ответ 5
Рекурсивное решение. Перебирайте файлы по одному. Для каждого файла проверьте, соответствует ли он первому шаблону и рано ли рано (-m1: при первом совпадении), только если он соответствует первому шаблону, ищите второй шаблон и так далее:
#!/bin/bash
patterns="[email protected]"
fileMatchesAllNames () {
file=$1
if [[ $# -eq 1 ]]
then
echo "$file"
else
shift
pattern=$1
shift
grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" [email protected]
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
Обновить:
Прочитав в комментариях к вопросу, что мы можем говорить о thounsands шаблонов, передавая их в качестве аргументов, кажется, не является умной идеей; лучше прочитайте их из файла и передайте имя файла в качестве аргумента - возможно, для списка фильтров тоже:
#!/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" [email protected]
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
Ответ 6
Ты можешь
-
использовать -o
| --only-matching
option 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 "[email protected]"|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
Ответ 7
Самый простой способ проверить, имеет ли файл все три шаблона, - это получить только согласованные шаблоны, выводить только уникальные части и линии подсчета. Затем вы сможете проверить его с помощью простого условия test 3 -eq $grep_lines
: test 3 -eq $grep_lines
.
grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l)
Что касается вашего второго вопроса, я не думаю, что можно прекратить чтение файла, как только будет найдено несколько шаблонов. Я прочитал man-страницу для grep, и нет никаких вариантов, которые могли бы помочь вам в этом. Вы можете только прекратить чтение строк после определенного с помощью опции grep -m [number]
которая происходит независимо от совпадающих шаблонов.
Довольно уверен, что для этой цели нужна пользовательская функция.
Ответ 8
Это интересная проблема, и на странице grep man нет ничего очевидного, чтобы предложить легкий ответ. Может существовать безумное регулярное выражение, которое будет делать это, но может быть более ясным с простой цепочкой 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
Ответ 9
Возможно, с gnu sed
cat 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
return 0, если найдено совпадение else 1
здесь вы можете найти 4 строки
если вы хотите больше, вы можете добавить такие строки, как
/\b'"$x"'/!bA
Ответ 10
Игнорирование "Возможно ли это сделать без... или использовать инструмент, например, 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.pl
это будет искать строки простого текста:
./matcher filename string1 string2 string3 'complex string'
Это приведет к поиску регулярных выражений:
./matcher -r filename regex1 'regex2' 'regex4'
(Имя файла можно -f
с помощью -f
):
./matcher -f filename -r string1 string2 string3 'complex string'
Он ограничен шаблонами соответствия одной строки (из-за работы с файловой линией).
Производительность при вызове большого количества файлов из сценария оболочки медленнее, чем awk
(но шаблоны поиска могут содержать пробелы, в отличие от переданных пробелов в -v
до awk
). Если преобразовать в функцию и вызвать из Perl-кода (с файлом, содержащим список файлов для поиска), он должен быть намного быстрее, чем большинство реализаций awk
. (Когда вызывается несколько небольших файлов, время запуска perl (разбор и т.д. Скрипта) доминирует во времени)
Это может быть значительно ускорено путем жесткого кодирования независимо от того, используются ли регулярные выражения или нет, за счет гибкости. (См. Мои контрольные показатели здесь, чтобы узнать, какой эффект удаляет Getopt::Std
)
Ответ 11
perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ? "Match": "No Match"}' file
Ответ 12
Только для "полноты решений" вы можете использовать другой инструмент и избегать нескольких grep и awk/sed или больших (и, вероятно, медленных) циклов оболочки; Такой инструмент аггреп.
agrep
на самом деле является своего рода egrep
поддерживающим также and
работу между шаблонами, используя ;
как разделитель шаблонов.
Подобно egrep
и, как и большинство известных инструментов, agrep
- это инструмент, который работает с записями/строками, и поэтому нам все же нужен способ обработки всего файла как отдельной записи.
Кроме того, 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 символов, а некоторые опции недоступны при использовании с regexps-, все это объясняется в man-странице agrep
Ответ 13
Предположим, что все ваши строки проверяются в файле strings.txt, а файл, который вы хотите проверить, - input.txt, следующий:
Обновлен ответ, основанный на комментариях:
$ diff <( sort -u strings.txt ) <( grep -o -f strings.txt input.txt | sort -u )
Объяснение:
Используйте параметр grep -o, чтобы соответствовать только тем строкам, которые вас интересуют. Это дает все строки, которые присутствуют в файле input.txt. Затем используйте diff, чтобы получить строки, которые не найдены. Если бы все строки были найдены, результат был бы ничем. Или просто проверьте код выхода diff.
Что это не делает:
- Выйдите, как только будут найдены все совпадения.
- Расширяется до regx.
- Перекрывающиеся совпадения.
Что он делает:
- Найти все совпадения.
- Одиночный вызов grep.
- Не использует awk или python.
Ответ 14
В 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()
Ответ 15
Многие из этих ответов хороши, насколько они идут.
Но если производительность является проблемой - конечно, возможно, если вход большой и у вас много тысяч шаблонов - тогда вы получите большое ускорение с помощью инструмента, такого как 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
Ответ 16
Для простой скорости, без ограничений внешнего инструмента и без регулярных выражений, эта (грубая) версия 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>
/* /questions/271888/strstr-for-a-string-that-is-not-null-terminated/1370935#1370935 */
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
страницы mmap
Заметки:
- Он будет сканировать через части файла, предшествующие совпадающим строкам, несколько раз - он только откроет файл один раз.
- Весь файл может быть загружен в память, особенно если строка не соответствует, ОС должна решить, что
- поддержка регулярных выражений, вероятно, может быть добавлена с помощью библиотеки регулярных выражений POSIX (производительность, вероятно, будет немного лучше, чем grep - она должна быть основана на одной и той же библиотеке, и вы получите уменьшенные накладные расходы только после открытия файла один раз для поиска нескольких регулярных выражений)
- Файлы, содержащие нули, должны работать, искать строки с ними, хотя...
- Все символы, отличные от нуля, должны быть доступны для поиска (\ r,\n и т.д.).
Ответ 17
Следующий скрипт python
должен сделать трюк. Это несколько раз вызывает эквивалент grep
(re.search
) несколько раз для каждой строки, т. 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
), если вы имеете дело с простыми (не-регулярными) строками - будет немного более эффективной.
Решает ли ваша проблема?
Ответ 18
Я не уверен, что у меня не возник вопрос, потому что ответ python выглядит невероятно простым, в то время как есть много длинных и подробных ответов (?).
all(i in open(file).read() for я in list_of_strings)
Это просто не волнует регулярное выражение или строку или что-либо на самом деле, просто проверяет, не все ли сырые строки (которые также могут быть регулярными выражениями, так как это также букет букв), которые вы указываете в файле, или нет.
Ответ 19
Еще один вариант 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
Ответ 20
Я не видел простой счетчик ответов, так что вот встречное решение с использованием 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"
Ответ 21
$ cat allstringsfile | tr '\n' ' ' | awk -f awkpattern1
Где allstringsfile - ваш текстовый файл, как в исходном вопросе. awkpattern1 содержит шаблоны строк с условием &&:
$ cat awkpattern1
/string1/ && /string2/ && /string3/