Je veux vérifier si toutes de mes chaînes existent dans un fichier texte. Ils peuvent exister sur la même ligne ou sur des lignes différentes. Et les correspondances partielles devraient être OK. Comme ça:
...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on
Dans l'exemple ci-dessus, nous pourrions avoir des expressions rationnelles à la place de chaînes.
Par exemple, le code suivant vérifie si tout de mes chaînes existe dans le fichier:
if grep -EFq "string1|string2|string3" file; then
# there is at least one match
fi
Comment vérifier si tous d'entre eux existent? Puisque nous nous intéressons seulement à la présence de presence de toutes les correspondances, nous devrions arrêter de lire le fichier dès que toutes les chaînes sont mises en correspondance.
Est-il possible de le faire sans avoir à invoquer grep
plusieurs fois (ce qui ne sera pas mis à l'échelle lorsque le fichier d'entrée est volumineux ou si nous avons un grand nombre de chaînes à faire correspondre) ou à l'aide d'un outil comme awk
ou python
?
De plus, existe-t-il une solution pour les chaînes qui peut facilement être étendue pour les regex?
Awk est l'outil que les gars qui ont inventé grep, Shell, etc. ont inventé pour effectuer des tâches de manipulation de texte générales comme celle-ci, vous ne savez donc pas pourquoi vous voulez essayer de l'éviter.
Au cas où vous recherchiez la brièveté, voici le GNU awk one-liner qui vous permet de faire exactement ce que vous avez demandé:
awk 'NR==FNR{a[$0];next} {for(s in a) if(!index($0,s)) exit 1}' strings RS='^$' file
Et voici un tas d'autres informations et options:
En supposant que vous cherchiez vraiment des ficelles, ce serait:
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
ce qui précède arrêtera de lire le fichier dès que toutes les chaînes auront été mises en correspondance.
Si vous recherchiez des expressions rationnelles au lieu de chaînes, alors avec GNU awk pour une RS multi-caractères et une rétention de 0 $ dans la section END, vous pourriez faire:
awk -v RS='^$' 'END{exit !(/regexp1/ && /regexp2/ && /regexp3/)}' file
En fait, même si c’était des chaînes, vous pouviez faire
awk -v RS='^$' 'END{exit !(index($0,"string1") && index($0,"string2") && index($0,"string3"))}' file
Le problème principal avec les 2 solutions GNU awk ci-dessus est que, comme la solution GNU grep -P de @ anubhava, tout le fichier doit être lu en mémoire en même temps alors que le premier script awk ci-dessus, cela fonctionnera dans n'importe quel awk dans n'importe quel Shell sur n'importe quelle unité UNIX et ne stockera qu'une ligne d'entrée à la fois.
Je vois que vous avez ajouté un commentaire sous votre question pour indiquer que vous pourriez avoir plusieurs milliers de "modèles". En supposant que vous vouliez dire "chaînes", au lieu de les transmettre comme arguments au script, vous pouvez les lire à partir d'un fichier, par exemple. avec GNU awk pour RS multi-caractères et un fichier avec une chaîne de recherche par ligne:
awk '
NR==FNR { strings[$0]; next }
{
for (string in strings)
if ( !index($0,string) )
exit 1
}
' file_of_strings RS='^$' file_to_be_searched
et pour regexps ce serait:
awk '
NR==FNR { regexps[$0]; next }
{
for (regexp in regexps)
if ( $0 !~ regexp )
exit 1
}
' file_of_regexps RS='^$' file_to_be_searched
Si vous n'avez pas GNU awk et que votre fichier d'entrée ne contient pas de caractères NUL, vous pouvez obtenir le même effet que ci-dessus en utilisant RS='\0'
au lieu de RS='^$'
ou en ajoutant à la variable une ligne à la fois au moment de sa lecture et puis en traitant cette variable dans la section END.
Si votre file_to_be_searched est trop volumineux pour tenir dans la mémoire, alors ce serait ceci pour les chaînes:
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
et l'équivalent pour les expressions rationnelles:
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
Voici la syntaxe utilisant git grep
avec plusieurs modèles:
git grep --all-match --no-index -l -e string1 -e string2 -e string3 file
Vous pouvez également combiner des motifs avec des expressions Boolean telles que --and
, --or
et --not
.
Consultez man git-grep
pour obtenir de l'aide.
--all-match
Lorsque vous donnez plusieurs expressions de modèle, cet indicateur est spécifié pour limiter la correspondance aux fichiers dont les lignes correspondent à toutes.
--no-index
Recherche dans le répertoire actuel les fichiers qui ne sont pas gérés par Git.
-l
/--files-with-matches
/--name-only
Affiche uniquement les noms des fichiers.
-e
Le paramètre suivant est le motif. La valeur par défaut consiste à utiliser une expression rationnelle de base.
Autres paramètres à prendre en compte:
--threads
Nombre de threads de travail grep à utiliser.
-q
/--quiet
/--silent
Ne pas afficher les lignes correspondantes; quitter avec le statut 0 quand il y a une correspondance.
Pour changer le type de motif, vous pouvez également utiliser -G
/--basic-regexp
(par défaut), -F
/--fixed-strings
, -E
/--extended-regexp
, -P
/--Perl-regexp
, -f file
et autres.
Ce script gnu-awk
peut fonctionner:
cat fileSearch.awk
re == "" {
exit
}
{
split($0, null, "\\<(" re "\\>)", b)
for (i=1; i<=length(b); i++)
gsub("\\<" b[i] "([|]|$)", "", re)
}
END {
exit (re != "")
}
Ensuite, utilisez-le comme:
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
Sinon, vous pouvez utiliser cette solution gnu grep
avec l'option PCRE
:
grep -qzP '(?s)(?=.*\bstring1\b)(?=.*\bstring2\b)(?=.*\bstring3\b)' file
-z
, nous faisons grep
lire le fichier complet en une seule chaîne.(?s)
ou DOTALL
mod pour faire correspondre .*
sur toutes les lignes.Selon 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.
Tout d'abord, vous voudrez probablement utiliser awk
. Puisque vous avez éliminé cette option dans la déclaration, oui, il est possible de le faire et cela fournit un moyen de le faire. C'est probablement BEAUCOUP plus lent que d'utiliser awk
, mais si vous voulez le faire quand même ...
Ceci est basé sur les hypothèses suivantes: G
grep
plusieurs fois est inacceptablegrep
moins d'une fois est acceptablebash
au lieu d’outils externes est acceptablebash
version est> = 3 pour la version à expression régulièreCela pourrait répondre à toutes vos exigences: (la version regex manque quelques commentaires, regardez plutôt la version chaîne)
#!/bin/bash
multimatch() {
filename="$1" # Filename is first parameter
shift # move it out of the way that "$@" is useful
strings=( "$@" ) # search strings into an array
declare -a matches # Array to keep track which strings already match
# Initiate array tracking what we have matches for
for ((i=0;i<${#strings[@]};i++)); do
matches[$i]=0
done
while IFS= read -r line; do # Read file linewise
foundmatch=0 # Flag to indicate whether this line matched anything
for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes
if [ "${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet
string="${strings[$i]}" # fetch the string
if [[ $line = *$string* ]]; then # check if it matches
matches[$i]=1 # mark that we have found this
foundmatch=1 # set the flag, we need to check whether we have something left
fi
fi
done
# If we found something, we need to check whether we
# can stop looking
if [ "$foundmatch" -eq 1 ]; then
somethingleft=0 # Flag to see if we still have unmatched strings
for ((i=0;i<${#matches[@]};i++)); do
if [ "${matches[$i]}" -eq 0 ]; then
somethingleft=1 # Something is still outstanding
break # no need check whether more strings are outstanding
fi
done
# If we didn't find anything unmatched, we have everything
if [ "$somethingleft" -eq 0 ]; then return 0; fi
fi
done < "$filename"
# If we get here, we didn't have everything in the file
return 1
}
multimatch_regex() {
filename="$1" # Filename is first parameter
shift # move it out of the way that "$@" is useful
regexes=( "$@" ) # Regexes into an array
declare -a matches # Array to keep track which regexes already match
# Initiate array tracking what we have matches for
for ((i=0;i<${#regexes[@]};i++)); do
matches[$i]=0
done
while IFS= read -r line; do # Read file linewise
foundmatch=0 # Flag to indicate whether this line matched anything
for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes
if [ "${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet
regex="${regexes[$i]}" # Get regex from array
if [[ $line =~ $regex ]]; then # We use the bash regex operator here
matches[$i]=1 # mark that we have found this
foundmatch=1 # set the flag, we need to check whether we have something left
fi
fi
done
# If we found something, we need to check whether we
# can stop looking
if [ "$foundmatch" -eq 1 ]; then
somethingleft=0 # Flag to see if we still have unmatched strings
for ((i=0;i<${#matches[@]};i++)); do
if [ "${matches[$i]}" -eq 0 ]; then
somethingleft=1 # Something is still outstanding
break # no need check whether more strings are outstanding
fi
done
# If we didn't find anything unmatched, we have everything
if [ "$somethingleft" -eq 0 ]; then return 0; fi
fi
done < "$filename"
# If we get here, we didn't have everything in the file
return 1
}
if multimatch "filename" string1 string2 string3; then
echo "file has all strings"
else
echo "file miss one or more strings"
fi
if multimatch_regex "filename" "regex1" "regex2" "regex3"; then
echo "file match all regular expressions"
else
echo "file does not match all regular expressions"
fi
J'ai effectué des analyses comparatives en recherchant .c
, .h
et .sh
dans Arch/arm/de Linux 4.16.2 pour les chaînes "void", "function" et "#define". (Des enveloppes de shell ont été ajoutées/le code ajusté pour que tous puissent être appelés en tant que testname <filename> <searchstring> [...]
et qu’une if
puisse être utilisée pour vérifier le résultat.)
Résultats: (mesuré avec time
, real
temps arrondi à la demi seconde la plus proche)
multimatch
: 49smultimatch_regex
: 55s(Invoquer grep
plusieurs fois, en particulier avec la méthode récursive, a été meilleur que prévu)
Une solution récursive. Parcourez les fichiers un à un. Pour chaque fichier, vérifiez s'il correspond au premier motif et effectuez une rupture anticipée (-m1: lors de la première correspondance), uniquement s'il correspond au premier motif, recherchez le deuxième motif, etc.:
#!/bin/bash
patterns="$@"
fileMatchesAllNames () {
file=$1
if [[ $# -eq 1 ]]
then
echo "$file"
else
shift
pattern=$1
shift
grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" $@
fi
}
for file in *
do
test -f "$file" && fileMatchesAllNames "$file" $patterns
done
./allfilter.sh cat filter Java
test.sh
Recherche dans le répertoire en cours les jetons "cat", "filter" et "Java". Les trouve seulement dans "test.sh".
Donc, grep est souvent invoqué dans le pire des cas (recherche des premiers modèles N-1 dans la dernière ligne de chaque fichier, à l'exception du N-ème modèle).
Toutefois, si possible, la solution devrait être raisonnable, car de nombreux fichiers sont abandonnés de manière anticipée car ils ne correspondaient pas au premier mot clé ou n'étaient pas acceptés de manière anticipée, car ils correspondaient à un mot clé close. jusqu'au sommet.
Exemple: Vous recherchez un fichier source scala qui contient tailrec (assez rarement utilisé), mutable (rarement utilisé, mais si tel est le cas, situé en haut à gauche des déclarations d’importation) main (rarement utilisé, souvent pas tout à fait en haut) et println (souvent utilisé, position imprévisible), vous les commanderiez:
./allfilter.sh mutable tailrec main println
ls *.scala | wc
89 89 2030
Dans 89 fichiers scala, j'ai la distribution des mots-clés:
for keyword in mutable tailrec main println; do grep -m 1 $keyword *.scala | wc -l ; done
16
34
41
71
Les rechercher avec une version légèrement modifiée des scripts, ce qui permet d'utiliser un attribut de fichier au premier argument, prend environ 0.2 s:
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
dans près de 15 000 lignes de code:
cat *.scala | wc
14913 81614 610893
Après avoir lu dans les commentaires de la question, que nous pourrions parler de milliers de motifs, leur donner comme arguments ne semble pas être une idée intelligente; Mieux vaut les lire dans un fichier et passer le nom du fichier en argument - peut-être aussi pour la liste des fichiers à filtrer:
#!/bin/bash
filelist="$1"
patternfile="$2"
patterns="$(< $patternfile)"
fileMatchesAllNames () {
file=$1
if [[ $# -eq 1 ]]
then
echo "$file"
else
shift
pattern=$1
shift
grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" $@
fi
}
echo -e "Filepattern: $filepattern\tPatterns: $patterns"
for file in $(< $filelist)
do
test -f "$file" && fileMatchesAllNames "$file" $patterns
done
Si le nombre et la longueur des patterns/fichiers dépasse les possibilités de passer des arguments, la liste des patterns peut être divisée en plusieurs fichiers de patterns et traitée en boucle (par exemple, à l'aide de 20 fichiers de patterns):
for i in {1..20}
do
./allfilter2.sh file.$i.lst pattern.$i.lst > file.$((i+1)).lst
done
Pour moi, le moyen le plus simple de vérifier si le fichier contient les trois modèles est d'obtenir uniquement des modèles appariés, de ne générer que des parties uniques et de compter des lignes. Ensuite, vous pourrez le vérifier avec un simple Condition de test : test 3 -eq $grep_lines
.
grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l)
Concernant votre deuxième question , je ne pense pas qu’il soit possible d’arrêter de lire le fichier dès que plus d’un motif est trouvé. J'ai lu la page de manuel de grep et aucune option ne pourrait vous aider. Vous ne pouvez arrêter de lire les lignes après une ligne spécifique qu'avec une option grep -m [number]
qui se produit peu importe les modèles correspondants.
Je suis presque sûr qu’une fonction personnalisée est nécessaire à cette fin.
Vous pouvez
utiliser l'option -o
| --only-matching
de grep
(qui oblige à ne produire que les parties correspondantes d'une ligne correspondante, chacune sur une ligne de sortie distincte),
puis éliminez les occurrences en double des chaînes correspondantes avec sort -u
,
et enfin, vérifiez que le nombre de lignes restantes est égal au nombre de chaînes en entrée.
Manifestation:
$ 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
Un inconvénient de cette solution (si les exigences de correspondance partielles ne sont pas satisfaites] est que grep
ne détecte pas les correspondances qui se chevauchent. Par exemple, bien que le texteabcd
corresponde à la fois àabc
etbcd
, grep
n'en trouve qu'un:
$ grep -o -F $'abc\nbcd' <<< abcd
abc
$ grep -o -F $'bcd\nabc' <<< abcd
abc
Notez que cette approche/solution ne fonctionne que pour les chaînes fixes. Il ne peut pas être étendu pour les expressions rationnelles, car une seule expression rationnelle peut correspondre à plusieurs chaînes différentes et nous ne pouvons pas déterminer quelle correspondance correspond à quelle expression rationnelle. Le mieux que vous puissiez faire est de stocker les correspondances dans un fichier temporaire, puis d'exécuter grep
plusieurs fois en utilisant une expression régulière à la fois.
La solution implémentée en tant que script bash:
matchall :
#!/usr/bin/env bash
if [ $# -lt 2 ]
then
echo "Usage: $(basename "$0") input_file string1 [string2 ...]"
exit 1
fi
function find_all_matches()
(
infile="$1"
shift
IFS=$'\n'
newline_separated_list_of_strings="$*"
grep -o -F "$newline_separated_list_of_strings" "$infile"
)
string_count=$(($# - 1))
matched_string_count=$(find_all_matches "$@"|sort -u|wc -l)
if [ "$matched_string_count" -eq "$string_count" ]
then
echo "ALL strings matched"
exit 0
else
echo "Some strings DID NOT match"
exit 1
fi
Manifestation:
$ ./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
Ignorer la phrase "Est-il possible de le faire sans ... ou d'utiliser un outil comme awk
ou python
?" exigence, vous pouvez le faire avec un script Perl:
(Utilisez un Shebang approprié pour votre système ou quelque chose comme /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
Est enregistré en tant que matcher.pl
cela recherchera les chaînes de texte brut:
./matcher filename string1 string2 string3 'complex string'
Cela recherchera des expressions régulières:
./matcher -r filename regex1 'regex2' 'regex4'
(Le nom du fichier peut être donné avec -f
à la place):
./matcher -f filename -r string1 string2 string3 'complex string'
Il est limité aux modèles de correspondance d'une seule ligne (en raison du traitement du fichier ligne par ligne).
La performance, lorsque vous appelez un grand nombre de fichiers à partir d'un script Shell, est plus lente que awk
(mais les modèles de recherche peuvent contenir des espaces, contrairement à ceux transmis séparés de -v
à awk
). Si elle est convertie en une fonction et appelée à partir de code Perl (avec un fichier contenant une liste de fichiers à rechercher), elle devrait être beaucoup plus rapide que la plupart des implémentations awk
. (Lorsqu'il est appelé sur plusieurs fichiers de petite taille, le temps de démarrage de Perl (analyse, etc. du script) domine le minutage)
Cela peut être considérablement accéléré par le codage en dur, que des expressions régulières soient utilisées ou non, au détriment de la flexibilité. (Voir mon points de repère ici pour voir quel effet la suppression de Getopt::Std
a)
Peut-être avec 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"
et vous l'appelez comme ça:
./match_Word.sh infile string1 string2 string3
renvoie 0 si toutes les correspondances sont trouvées, sinon 1
ici vous pouvez chercher 4 cordes
si vous voulez plus, vous pouvez ajouter des lignes comme
/\b'"$x"'/!bA
C'est un problème intéressant, et rien dans la page de manuel de grep ne suggère une solution simple. Il pourrait y avoir une expression rationnelle insensée qui le ferait, mais cela peut être plus clair avec une chaîne simple de greps, même si cela finit par analyser le fichier n fois. Au moins l’option -q l’a mis en attente au premier match à chaque fois, et le && raccourcira l’évaluation si l’une des chaînes n’est pas trouvée.
$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
Pour une vitesse normale, sans limitations d’outil externe ni expressions rationnelles, cette version C (brute) fait un travail décent. (Peut-être uniquement Linux, bien que cela devrait fonctionner sur tous les systèmes de type Unix avec mmap
)
#include <sys/mman.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/* https://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);
}
Compilez-le avec:
gcc matcher.c -o matcher
Exécutez-le avec:
./matcher filename needle1 needle2 needle3
Crédits:
mmap
page de manuelRemarques:
Le script python
suivant devrait faire l'affaire. Elle appelle en quelque sorte l’équivalent de grep
(re.search
) plusieurs fois pour chaque ligne - c’est-à-dire qu’elle recherche chaque motif pour chaque ligne, mais comme vous n’établissez pas un processus à chaque fois, il devrait être beaucoup plus efficace. En outre, il supprime les motifs déjà trouvés et s’arrête lorsque tous les ont été trouvés.
#!/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
Vous pouvez ajouter une vérification distincte pour les chaînes standard (string in line
) si vous traitez avec des chaînes standard (non regex). Elles seront légèrement plus efficaces.
Cela résout-il votre problème?
Bon nombre de ces réponses sont correctes dans la mesure du possible.
Mais si les performances sont un problème - certainement possible si l’entrée est importante et que vous avez plusieurs milliers de modèles - alors vous obtiendrez unlargeen utilisant un outil comme Lex
ou flex
qui génère véritable automate fini déterministe en tant que reconnaissance plutôt que d’appeler un interpréteur de regex une fois par motif.
L'automate fini exécutera quelques instructions machine par caractère saisi quel que soit le nombre de motifs.
Une solution flexible sans fioritures:
%{
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;
}
Un inconvénient est que vous devez construire ceci pour chaque ensemble de motifs donné. Ce n'est pas si mal:
flex matcher.y
gcc -O Lex.yy.c -o matcher
Maintenant, lancez-le:
./matcher < input.txt
En supposant que toutes vos chaînes à vérifier se trouvent dans un fichier strings.txt, et que le fichier à archiver soit input.txt, le liner suivant fera l'affaire:
Mise à jour de la réponse en fonction des commentaires:
$ diff <( sort -u strings.txt ) <( grep -o -f strings.txt input.txt | sort -u )
Explication:
Utilisez l'option -o de grep pour ne faire correspondre que les chaînes qui vous intéressent. Cela donne toutes les chaînes présentes dans le fichier input.txt. Ensuite, utilisez diff pour obtenir les chaînes qui ne sont pas trouvées. Si toutes les chaînes étaient trouvées, le résultat ne serait rien. Ou, vérifiez simplement le code de sortie de diff.
Ce qu'il ne fait pas:
Qu'est-ce qu'il fait:
Juste pour "compléter les solutions", vous pouvez utiliser un outil différent et éviter plusieurs greps et boucles awk/sed ou grandes (et probablement lentes) Shell; Un tel outil est agréable .
agrep
est en fait une sorte de egrep
prenant également en charge l'opération and
entre les modèles, en utilisant ;
comme séparateur de modèle.
Comme egrep
et comme la plupart des outils bien connus, agrep
est un outil qui fonctionne sur les enregistrements/lignes et nous avons donc besoin d'un moyen de traiter le fichier entier comme un seul enregistrement.
De plus, Agrep propose une option -d
pour définir votre délimiteur d’enregistrement personnalisé.
Quelques 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
Aucun outil n'est parfait, et agrep
a aussi quelques limitations; vous ne pouvez pas utiliser de regex/pattern de plus de 32 caractères et certaines options ne sont pas disponibles avec regexps - elles sont toutes expliquées dans page de manuel d'agendp
En python, utiliser le module fileinput permet de spécifier les fichiers sur la ligne de commande ou le texte lu ligne par ligne à partir de stdin. Vous pouvez coder les chaînes dans une liste 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',
)
ou lire les chaînes d'un autre fichier
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()
Une autre variante de Perl - chaque fois que toutes les chaînes données correspondent .. même lorsque le fichier est lu à moitié, le traitement se termine et affiche simplement les résultats
> 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