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

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

...
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 с несколькими шаблонами:

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/