web-dev-qa-db-fra.com

Vérifier si toutes les chaînes ou expressions régulières existent dans un fichier

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?

16
codeforester

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

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-indexRecherche 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.

9
kenorb

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
  • En utilisant -z, nous faisons grep lire le fichier complet en une seule chaîne.
  • Nous utilisons plusieurs assertions d'anticipation pour affirmer que toutes les chaînes sont présentes dans le fichier.
  • Regex doit utiliser (?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.
4
anubhava

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

  • Invoquer AWK est inacceptable
  • Invoquer grep plusieurs fois est inacceptable
  • L'utilisation de tout autre outil externe est inacceptable
  • Invoquer grep moins d'une fois est acceptable
  • Il doit retourner le succès si tout est trouvé, échec sinon
  • Utiliser bash au lieu d’outils externes est acceptable
  • bash version est> = 3 pour la version à expression régulière

Cela 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

Des repères

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)

(Invoquer grep plusieurs fois, en particulier avec la méthode récursive, a été meilleur que prévu)

4
Gert van den Berg

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

Usage:

./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 

Performance:

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

mettre à jour:

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
3
user unknown

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.

2
Anna Fomina

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 texteabcdcorresponde à la fois àabcetbcd, 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
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

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)

1
Gert van den Berg

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
1
ctac_

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

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: 

Remarques:

  • Il analysera plusieurs fois les parties du fichier précédant les chaînes correspondantes - il n'ouvrira le fichier qu'une seule fois.
  • La totalité du fichier peut être chargée en mémoire, en particulier si une chaîne ne correspond pas, le système d'exploitation doit décider que
  • la prise en charge de regex peut probablement être ajoutée à l’aide de Bibliothèque POSIX regex (Les performances seraient probablement légèrement meilleures que grep - elles devraient être basées sur la même bibliothèque et vous auriez moins de temps système en ouvrant le fichier une seule fois pour rechercher plusieurs regex)
  • Les fichiers contenant des valeurs NULL devraient fonctionner, mais ne recherchent pas les chaînes avec eux ...
  • Tous les caractères autres que null doivent être interrogeables (\ r,\n, etc.)
0
Gert van den Berg

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?

0
Monad

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

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:

  • Quittez dès que tous les résultats sont trouvés.
  • Extensible à regx.
  • Jeux qui se chevauchent.

Qu'est-ce qu'il fait:

  • Trouvez tous les matchs.
  • Appel unique à grep.
  • N'utilise pas awk ou python.
0
Gautam

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

0
George Vasiliou

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

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