Проверьте, существуют ли все несколько строк или регулярных выражений в файле

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

...
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 время округляется до ближайшей половины секунды)

(Вызов 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

Кредиты:

Заметки:

  • Он будет сканировать части файла, предшествующие совпадающим строкам, несколько раз - хотя файл откроется только один раз.
  • Весь файл может оказаться загруженным в память, особенно если строка не совпадает, ОС должна решить, что
  • Поддержка регулярных выражений, вероятно, может быть добавлена ​​с помощью библиотеки регулярных выражений 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/
Другие вопросы по тегам