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?
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
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-index
Durchsucht 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.
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
-z
machen wir grep
die vollständige Datei in eine einzige Zeichenfolge.(?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.
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
grep
ist nicht akzeptabelgrep
weniger als einmal aufzurufenbash
anstelle von externen Tools ist zulässigbash
-Version ist für die Version mit regulären Ausdrücken> = 3Dies 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
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)
multimatch
: 49smultimatch_regex
: 55s(Das mehrfache Aufrufen von grep
, insbesondere mit der rekursiven Methode, hat besser als erwartet gedauert.)
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
./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
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
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
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.
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 Textabcd
mit beidenabc
undbcd
ü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
Perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ? "Match": "No Match"}' file
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)
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
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
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:
mmap
man-Seite gestohlenAnmerkungen:
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?
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
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:
Was macht es:
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.
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()
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