it-swarm.com.de

Prüfen Sie, ob in einer Datei alle Zeichenfolgen oder Regex enthalten sind

Ich möchte überprüfen, ob alle meiner Zeichenfolgen in einer Textdatei vorhanden sind. Sie können in derselben Zeile oder in verschiedenen Zeilen vorhanden sein. Und teilweise Übereinstimmungen sollten in Ordnung sein. So was:

...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on

Im obigen Beispiel könnten wir anstelle von Zeichenfolgen Regexen verwenden.

Zum Beispiel prüft der folgende code , ob any meiner Zeichenfolgen in der Datei vorhanden ist:

if grep -EFq "string1|string2|string3" file; then
  # there is at least one match
fi

Wie kann man prüfen, ob alle von ihnen existieren? Da wir nur an presence aller Übereinstimmungen interessiert sind, sollten wir die Datei nicht mehr lesen, sobald alle Zeichenfolgen übereinstimmen.

Ist es möglich, ohne grep mehrmals aufzurufen (was nicht skaliert wird, wenn die Eingabedatei groß ist oder eine große Anzahl von Zeichenfolgen vorhanden ist) oder ein Werkzeug wie awk oder python zu verwenden?

Gibt es auch eine Lösung für Zeichenfolgen, die leicht für reguläre Ausdrücke erweitert werden können?

16
codeforester

Awk ist das Werkzeug, das die Leute, die grep, Shell usw. erfunden haben, erfunden haben, um allgemeine Textbearbeitungsaufgaben wie diese auszuführen. Sie sollten also nicht sicher sein, warum Sie dies vermeiden möchten.

Falls Sie nach Kürze suchen, ist hier der GNU awk-Einliner genau das, was Sie gefragt haben:

awk 'NR==FNR{a[$0];next} {for(s in a) if(!index($0,s)) exit 1}' strings RS='^$' file

Und hier noch einige weitere Informationen und Optionen:

Angenommen, Sie suchen wirklich nach Zeichenketten, wäre dies:

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

der obige Befehl hört auf, die Datei zu lesen, sobald alle Zeichenfolgen übereinstimmen.

Wenn Sie Regex-Ausdrücke anstelle von Zeichenfolgen suchen, können Sie mit GNU awk für RS mit mehreren Zeichen und der Beibehaltung von $ 0 im END-Abschnitt Folgendes tun:

awk -v RS='^$' 'END{exit !(/regexp1/ && /regexp2/ && /regexp3/)}' file

Eigentlich, selbst wenn es Strings wären, könnten Sie folgendes tun:

awk -v RS='^$' 'END{exit !(index($0,"string1") && index($0,"string2") && index($0,"string3"))}' file

Das Hauptproblem bei den oben genannten 2 GNU awk-Lösungen ist, dass wie bei @ anubhavas GNU grep -P-Lösung die gesamte Datei auf einmal in den Speicher eingelesen werden muss, mit der ersten awk-Skript oben, es funktioniert in jedem awk in jeder Shell unter einer UNIX-Box und speichert nur eine Zeile Eingabe.

Wie Sie sehen, haben Sie Ihrer Frage einen Kommentar hinzugefügt, um zu sagen, dass Sie mehrere tausend "Muster" haben könnten. Angenommen, Sie meinen "Zeichenketten", anstatt sie als Argumente an das Skript zu übergeben, könnten Sie sie aus einer Datei lesen, z. mit GNU awk für RS mit mehreren Zeichen und einer Datei mit einem Suchstring pro Zeile:

awk '
NR==FNR { strings[$0]; next }
{
    for (string in strings)
        if ( !index($0,string) )
            exit 1
}
' file_of_strings RS='^$' file_to_be_searched

und für Regexs wäre es:

awk '
NR==FNR { regexps[$0]; next }
{
    for (regexp in regexps)
        if ( $0 !~ regexp )
            exit 1
}
' file_of_regexps RS='^$' file_to_be_searched

Wenn Sie GNU awk nicht haben und Ihre Eingabedatei keine NUL-Zeichen enthält, können Sie den gleichen Effekt wie oben erzielen, indem Sie RS='\0' anstelle von RS='^$' verwenden oder indem Sie an die Variable eine Zeile anhängen Lesen und verarbeiten Sie diese Variable im END-Abschnitt.

Wenn Ihre file_to_be_searched zu groß ist, um in den Speicher zu passen, wäre dies für Strings die folgende:

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

und das Äquivalent für Regexs:

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
17
Ed Morton

git grep

Hier ist die Syntax mit git grep mit mehreren Mustern:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

Sie können Muster auch mit Boolean - Ausdrücken wie --and, --or und --not kombinieren.

Überprüfen Sie man git-grep für Hilfe.


--all-match Wenn mehrere Musterausdrücke angegeben werden, wird dieses Flag angegeben, um die Übereinstimmung auf Dateien zu beschränken, deren Zeilen mit allen übereinstimmen.

--no-indexDurchsucht Dateien im aktuellen Verzeichnis, das nicht von Git verwaltet wird.

-l/--files-with-matches/--name-only Zeigt nur die Namen der Dateien an.

-e Der nächste Parameter ist das Muster. Standardmäßig wird der grundlegende Regex verwendet.

Andere zu beachtende Parameter:

--threads Anzahl der zu verwendenden grep-Arbeitsthreads.

-q/--quiet/--silent Keine übereinstimmenden Zeilen ausgeben; Beenden Sie den Status 0, wenn eine Übereinstimmung vorliegt.

Um den Mustertyp zu ändern, können Sie auch -G/--basic-regexp (Standard), -F/--fixed-strings, -E/--extended-regexp, -P/--Perl-regexp, -f file und andere verwenden.

9
kenorb

Dieses gnu-awk-Skript kann funktionieren:

cat fileSearch.awk
re == "" {
   exit
}
{
   split($0, null, "\\<(" re "\\>)", b)
   for (i=1; i<=length(b); i++)
      gsub("\\<" b[i] "([|]|$)", "", re)
}
END {
   exit (re != "")
}

Dann verwenden Sie es als:

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

Alternativ können Sie diese gnu grep-Lösung mit der Option PCRE verwenden:

grep -qzP '(?s)(?=.*\bstring1\b)(?=.*\bstring2\b)(?=.*\bstring3\b)' file
  • Mit -z machen wir grep die vollständige Datei in eine einzige Zeichenfolge.
  • Wir verwenden mehrere Lookahead-Assertions, um zu bestätigen, dass alle Zeichenfolgen in der Datei vorhanden sind.
  • Regex muss (?s) oder DOTALL mod verwenden, damit .* in den Zeilen übereinstimmt.

Gemäß 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
anubhava

Zunächst möchten Sie wahrscheinlich awk verwenden. Da Sie diese Option in der Fragenaussage weggelassen haben, können Sie dies auch tun. Dies ist eine Möglichkeit, dies zu tun. Es ist wahrscheinlich viel langsamer als awk, aber wenn Sie es trotzdem wollen ...

Dies basiert auf folgenden Annahmen: G

  • Das Aufrufen von AWK ist nicht akzeptabel
  • Mehrmaliges Aufrufen von grep ist nicht akzeptabel
  • Die Verwendung anderer externer Tools ist nicht akzeptabel
  • Es ist akzeptabel, grep weniger als einmal aufzurufen
  • Es muss Erfolg zurückgeben, wenn alles gefunden wird, Fehler, wenn nicht
  • Die Verwendung von bash anstelle von externen Tools ist zulässig
  • Die bash-Version ist für die Version mit regulären Ausdrücken> = 3

Dies könnte alle Ihre Anforderungen erfüllen: (Regex-Version lässt einige Kommentare aus, schauen Sie sich stattdessen die String-Version an.)

#!/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

Benchmarks

Ich habe einige Benchmarking-Tests durchgeführt, in denen .c, .h und .sh in Arch/arm/von Linux 4.16.2 nach den Zeichenfolgen "void", "function" und "#define" gesucht wurden. (Shell-Wrapper wurden hinzugefügt/der Code wurde so eingestellt, dass alle als testname <filename> <searchstring> [...] aufgerufen werden können und dass eine if zum Überprüfen des Ergebnisses verwendet werden kann)

Ergebnisse: (gemessen mit time, real Zeit auf halbe Sekunde gerundet)

(Das mehrfache Aufrufen von grep, insbesondere mit der rekursiven Methode, hat besser als erwartet gedauert.)

4

Eine rekursive Lösung. Iterieren Sie die Dateien nacheinander. Prüfen Sie für jede Datei, ob sie mit dem ersten Muster übereinstimmt und bricht früh ab (-m1: bei der ersten Übereinstimmung). Nur wenn sie mit dem ersten Muster übereinstimmt, suchen Sie nach dem zweiten Muster usw.

#!/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

Verwendungszweck:

./allfilter.sh cat filter Java
test.sh

Sucht im aktuellen Verzeichnis nach den Tokens "cat", "filter" und "Java". Fand sie nur in "test.sh".

Daher wird grep im schlimmsten Fall häufig aufgerufen (Auffinden der ersten N-1-Muster in der letzten Zeile jeder Datei, mit Ausnahme des N-ten Musters). 

Bei einer sachkundigen Bestellung (selten passt zuerst, frühes Matching zuerst) sollte die Lösung nach Möglichkeit schnell sein, da viele Dateien vorzeitig abgebrochen werden, weil sie nicht mit dem ersten Schlüsselwort übereinstimmen oder frühzeitig akzeptiert wurden, da sie mit einem Schlüsselwort zu tun hatten Zum Seitenanfang. 

Beispiel: Sie suchen in einer Scala-Quelldatei, die tailrec (etwas selten verwendet), mutierbar (selten verwendet, aber wenn ja nahe an der Spitze der Importanweisungen) main (selten verwendet, oft nicht nahe an der Spitze) und println (häufig) enthält unvorhersehbare Position), würden Sie sie bestellen: 

./allfilter.sh mutable tailrec main println 

Performance:

ls *.scala | wc 
 89      89    2030

In 89 Scala-Dateien habe ich die Verteilung der Schlüsselwörter:

for keyword in mutable tailrec main println; do grep -m 1 $keyword *.scala | wc -l ; done 
16
34
41
71

Das Durchsuchen mit einer leicht modifizierten Version der Skripts, die die Verwendung eines Dateipattern als erstes Argument ermöglicht, dauert etwa 0,2 Sekunden:

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

in fast 15.000 Codelines:

cat *.scala | wc 
  14913   81614  610893

aktualisieren:

Nachdem wir in den Kommentaren zu der Frage gelesen haben, dass wir über Tausende von Mustern sprechen können, scheint es nicht eine kluge Idee zu sein, sie als Argumente zu betrachten. Lesen Sie sie besser aus einer Datei und geben Sie den Dateinamen als Argument an - vielleicht für die Liste der zu filternden Dateien:

#!/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

Wenn die Anzahl und Länge von Mustern/Dateien die Möglichkeiten der Argumentübergabe überschreitet, kann die Liste der Muster in viele Musterdateien aufgeteilt und in einer Schleife verarbeitet werden (z. B. 20 Musterdateien):

for i in {1..20}
do
   ./allfilter2.sh file.$i.lst pattern.$i.lst > file.$((i+1)).lst
done
3
user unknown

Der einfachste Weg für mich zu überprüfen, ob die Datei alle drei Muster enthält, ist, nur übereinstimmende Muster zu erhalten, nur eindeutige Teile auszugeben und Zeilen zu zählen. .__ Dann können Sie es mit einer einfachen Test-Bedingung überprüfen : test 3 -eq $grep_lines.

 grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l)

Zu Ihrer zweiten Frage glaube ich nicht, dass es möglich ist, die Datei zu lesen, sobald mehr als ein Muster gefunden wird. Ich habe die Manpage für grep gelesen und es gibt keine Optionen, die Ihnen dabei helfen könnten. Sie können das Lesen von Zeilen nur mit einer Option grep -m [number] beenden, die unabhängig von übereinstimmenden Mustern auftritt.

Ziemlich sicher, dass für diesen Zweck eine benutzerdefinierte Funktion benötigt wird.

2
Anna Fomina

Sie können

  • die -o | --only-matching-Option von grep verwenden (die dazu zwingt, nur die übereinstimmenden Teile einer übereinstimmenden Zeile auszugeben, wobei jeder dieser Teile in einer separaten Ausgabezeile steht)

  • dann doppelte Vorkommen von übereinstimmenden Zeichenfolgen mit sort -u beseitigen,

  • und überprüfen Sie schließlich, ob die Anzahl der verbleibenden Zeilen der Anzahl der eingegebenen Zeichenfolgen entspricht.

Demonstration:

$ 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

Ein Manko bei dieser Lösung (das Nichteinhalten der partiellen Übereinstimmungen sollte OK sein), besteht darin, dass grep keine überlappenden Übereinstimmungen erkennt. Obwohl der Textabcdmit beidenabcundbcdübereinstimmt, findet grep nur eine davon:

$ grep -o -F $'abc\nbcd' <<< abcd
abc

$ grep -o -F $'bcd\nabc' <<< abcd
abc

Beachten Sie, dass dieser Ansatz/Lösung nur für feste Zeichenfolgen funktioniert. Es kann nicht für reguläre Ausdrücke erweitert werden, da ein einzelner regulärer Ausdruck mit mehreren verschiedenen Zeichenfolgen übereinstimmen kann und wir nicht verfolgen können, welcher Übereinstimmungscode dem regulären Ausdruck entspricht. Am besten speichern Sie die Übereinstimmungen in einer temporären Datei und führen dann grep mehrmals mit jeweils einem regulären Ausdruck aus.


Die Lösung wurde als Bash-Skript implementiert:

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

Demonstration:

$ ./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
2
Leon
Perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ? "Match": "No Match"}' file
1
binish

Ignorieren des "Ist es möglich, dies ohne ... oder mit einem Werkzeug wie awk oder python zu tun?" Voraussetzung ist das Perl-Skript:

(Verwenden Sie einen geeigneten Shebang für Ihr System oder etwas wie /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

Wird als matcher.pl gespeichert, sucht dies nach reinen Textzeichenfolgen:

./matcher filename string1 string2 string3 'complex string'

Dadurch wird nach regulären Ausdrücken gesucht:

./matcher -r filename regex1 'regex2' 'regex4'

(Der Dateiname kann stattdessen mit -f angegeben werden):

./matcher -f filename -r string1 string2 string3 'complex string'

Es ist auf einzeilige Übereinstimmungsmuster beschränkt (aufgrund des zeilenweisen Umgangs mit der Datei).

Wenn Sie viele Dateien aus einem Shell-Skript aufrufen, ist die Leistung langsamer als awk (Suchmuster können jedoch Leerzeichen enthalten, im Gegensatz zu denen, die in -v durch awk durch Leerzeichen getrennt sind). Wenn eine Funktion in eine Funktion konvertiert und von Perl-Code (mit einer Datei, die eine Liste der zu durchsuchenden Dateien enthält) aufgerufen wird, sollte sie viel schneller als die meisten awk-Implementierungen sein. (Wenn mehrere kleine Dateien aufgerufen werden, dominiert die Perl-Startzeit (Parsen usw. des Skripts) das Timing.)

Es kann durch Hardcoding erheblich beschleunigt werden, ob reguläre Ausdrücke verwendet werden oder nicht, auf Kosten der Flexibilität. (Siehe meine Benchmarks hier , um zu sehen, welchen Effekt das Entfernen von Getopt::Std hat)

1

Vielleicht mit 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"

und du nennst es so:

./match_Word.sh infile string1 string2 string3

0 zurückgeben, wenn alle Übereinstimmungen gefunden wurden, sonst 1

hier können Sie nach 4 Saiten suchen

wenn Sie mehr wollen, können Sie Zeilen wie hinzufügen

/\b'"$x"'/!bA
1
ctac_

Es ist ein interessantes Problem, und auf der grep-Manpage gibt es nichts offensichtliches, um eine einfache Antwort vorzuschlagen. Es könnte einen wahnsinnigen Regex geben, der dies tun würde, aber mit einer einfachen Kette von Greps könnte dies klarer sein, auch wenn die Datei n-mal gescannt wird. Zumindest für die Option -q wird jedes Mal der erste Treffer erzielt, und die &&-Verknüpfung kürzt die Auswertung, wenn eine der Zeichenfolgen nicht gefunden wird.

$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
1
Ian McGowan

Bei normaler Geschwindigkeit, ohne Einschränkungen für externe Werkzeuge und ohne reguläre Ausdrücke, leistet diese (grobe) C-Version gute Arbeit. (Möglicherweise nur Linux, obwohl es auf allen Unix-ähnlichen Systemen mit mmap funktionieren sollte.)

#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://stackoverflow.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);
}

Kompilieren Sie es mit:

gcc matcher.c -o matcher

Führen Sie es aus mit:

./matcher filename needle1 needle2 needle3

Credits: 

Anmerkungen:

  • Es wird mehrmals durch die Teile der Datei gescannt, die den übereinstimmenden Zeichenfolgen vorangehen. Die Datei wird jedoch nur einmal geöffnet.
  • Die gesamte Datei wird möglicherweise in den Speicher geladen, insbesondere wenn eine Zeichenfolge nicht übereinstimmt, muss das Betriebssystem dies entscheiden
  • regex-Unterstützung kann wahrscheinlich mit der POSIX-regex-Bibliothek hinzugefügt werden (Die Leistung wäre wahrscheinlich etwas besser als bei grep - sie sollte auf derselben Bibliothek basieren und würde den Aufwand reduzieren, wenn Sie die Datei nur einmal für die Suche öffnen mehrere Regexe)
  • Dateien, die Nullen enthalten, sollten funktionieren, Suchzeichenfolgen sollten jedoch nicht durchsucht werden.
  • Alle Zeichen außer Null sollten durchsuchbar sein (\ r,\n usw.)
0

Das folgende python-Skript sollte den Trick ausführen. Es ruft das Äquivalent von grep (re.search) mehrmals für jede Zeile auf - d. H. Es durchsucht jedes Muster für jede Zeile. Da Sie jedoch nicht jedes Mal einen Prozess ausfällen, sollte dies wesentlich effizienter sein. Es entfernt auch die bereits gefundenen Muster und stoppt, wenn alle gefunden wurden.

#!/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

Sie können eine separate Prüfung auf einfache Zeichenfolgen (string in line) durchführen, wenn Sie mit einfachen (nicht regulären) Zeichenfolgen arbeiten. Dies ist etwas effizienter.

Löst das dein Problem?

0
Monad

Viele dieser Antworten sind soweit gut. 

Wenn Leistung jedoch ein Problem ist - sicherlich möglich, wenn die Eingabe groß ist und Sie viele Tausende von Mustern haben -, erhalten Sie einelargespeedup, wenn Sie ein Tool wie Lex oder flex verwenden, das eine Ein wahrer deterministischer endlicher Automat als Erkenner, anstatt einen Regex-Interpreter einmal pro Muster aufzurufen.

Der endliche Automat führt einige Maschinenanweisungen pro Eingabezeichen aus, unabhängig von der Anzahl der Muster.

Eine No-Frills-Flex-Lösung:

%{
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;
}

Ein Nachteil ist, dass Sie dies für jeden gegebenen Satz von Mustern erstellen müssen. Das ist gar nicht so schlecht:

flex matcher.y
gcc -O Lex.yy.c -o matcher

Nun führe es aus:

./matcher < input.txt
0
Gene

Vorausgesetzt, alle Ihre zu überprüfenden Zeichenfolgen befinden sich in einer Datei strings.txt, und die Datei, die Sie einchecken möchten, ist input.txt. Der folgende Liner tut dies: 

Antwort aufgrund von Kommentaren aktualisiert: 

$ diff <( sort -u strings.txt )  <( grep -o -f strings.txt input.txt | sort -u )

Erklärung: 

Verwenden Sie die Option -o von grep, um nur die Zeichenfolgen zu finden, an denen Sie interessiert sind. Dadurch werden alle Zeichenfolgen angezeigt, die in der Datei input.txt vorhanden sind. Verwenden Sie dann diff, um die nicht gefundenen Zeichenfolgen zu ermitteln. Wenn alle Zeichenfolgen gefunden wurden, wäre das Ergebnis nichts. Oder überprüfen Sie einfach den Exit-Code von diff.

Was es nicht tut:

  • Beenden Sie, sobald alle Übereinstimmungen gefunden wurden.
  • Erweiterbar bis Regx.
  • Überlappende Übereinstimmungen.

Was macht es:

  • Finde alle Übereinstimmungen.
  • Einzelruf bei grep.
  • Verwendet kein awk oder Python.
0
Gautam

Der Vollständigkeit halber können Sie ein anderes Werkzeug verwenden und mehrere Greps und awk/sed oder große (und möglicherweise langsame) Shell-Schleifen vermeiden. Ein solches Werkzeug ist agreep .

agrep ist eigentlich eine Art egrep, die auch and-Operationen zwischen Mustern unterstützt, wobei ; als Mustertrennzeichen verwendet wird.

Wie egrep und wie die meisten bekannten Werkzeuge ist agrep ein Werkzeug, das mit Datensätzen/Zeilen arbeitet. Daher müssen wir immer noch die gesamte Datei als einen einzigen Datensatz behandeln.
Darüber hinaus bietet agreep eine -d-Option zum Festlegen Ihres benutzerdefinierten Datensatzbegrenzers.

Einige Tests:

$ 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

Kein Werkzeug ist perfekt und agrep hat auch einige Einschränkungen. Sie können ein Regex/Muster nicht länger als 32 Zeichen verwenden. Einige Optionen sind nicht verfügbar, wenn Sie mit Regex verwendet werden. Alle diese Optionen werden in der agreep Manpage erläutert.

0
George Vasiliou

In Python mit dem Modul fileinput können die Dateien in der Befehlszeile angegeben werden oder der Text wird zeilenweise aus stdin gelesen. Sie könnten die Zeichenfolgen hart in eine Python-Liste kodieren.

# Strings to match, must be valid regular expression patterns
# or be escaped when compiled into regex below.
strings = (
    r'string1',
    r'string2',
    r'string3',
)

oder lesen Sie die Zeichenfolgen aus einer anderen Datei

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()
0
Mike Robins

Eine weitere Perl-Variante - immer, wenn alle angegebenen Zeichenfolgen übereinstimmen. Selbst wenn die Datei zur Hälfte gelesen wird, ist die Verarbeitung abgeschlossen und die Ergebnisse werden nur gedruckt

> 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
0
stack0114106