Comment vérifier si un fichier fait partie d’un autre?

Je dois vérifier si un fichier se trouve dans un autre fichier par script bash. Pour un modèle de multiligne donné et un fichier d’entrée.

Valeur de retour:

Je souhaite recevoir le statut (comment dans la commande grep) 0 si des correspondances ont été trouvées, 1 si aucune correspondance n’a été trouvée.

Modèle:

  • multiligne,
  • l’ordre des lignes est important (traité comme un seul bloc de lignes),
  • comprend des caractères tels que des chiffres, des lettres,?, &, *, # etc.,

Explication

Seuls les exemples suivants doivent trouver des correspondances:

pattern file1 file2 file3 file4 222 111 111 222 222 333 222 222 333 333 333 333 444 444 

ce qui suit ne devrait pas:

 pattern file1 file2 file3 file4 file5 file6 file7 222 111 111 333 *222 111 111 222 333 *222 222 222 *333 222 222 333 333* 444 111 333 444 333 333 

Voici mon script:

 #!/bin/bash function writeToFile { if [ -w "$1" ] ; then echo "$2" >> "$1" else echo -e "$2" | sudo tee -a "$1" > /dev/null fi } function writeOnceToFile { pcregrep --color -M "$2" "$1" #echo $? if [ $? -eq 0 ]; then echo This file contains text that was added previously else writeToFile "$1" "$2" fi } file=file.txt #1?1 #2?2 #3?3 #4?4 pattern=`cat pattern.txt` #2?2 #3?3 writeOnceToFile "$file" "$pattern" 

Je peux utiliser la commande grep pour toutes les lignes de motif, mais cela échoue avec cet exemple:

 file.txt #1?1 #2?2 #=== added line #3?3 #4?4 pattern.txt #2?2 #3?3 

ou même si vous changez de ligne: 2 avec 3

 file=file.txt #1?1 #3?3 #2?2 #4?4 

retourner 0 quand il ne devrait pas.

Comment puis-je le réparer? Notez que je préfère utiliser les programmes installés natifs (si cela peut être sans pcregrep). Peut-être sed ou awk peut résoudre ce problème?

J’ai une version de travail utilisant perl.

Je pensais que je travaillais avec GNU awk , mais pas moi. RS = chaîne vide divisée sur des lignes vierges. Voir l’historique des modifications pour la version awk cassée.

Comment rechercher un motif multiligne dans un fichier? montre comment utiliser pcregrep, mais je ne vois pas comment le faire fonctionner lorsque le modèle à rechercher peut contenir des caractères spéciaux de regex. -F mode chaîne fixe ne fonctionne pas utilement avec le mode multiligne: il traite toujours le motif comme un ensemble de lignes à comparer séparément. (Pas comme une chaîne fixe multi-lignes à faire correspondre.) Je vois que vous utilisiez déjà pcregrep dans votre tentative.

BTW, je pense que vous avez un bogue dans votre code dans le cas non-sudo:

 function writeToFile { if [ -w "$1" ] ; then "$2" >> "$1" # probably you mean echo "$2" >> "$1" else echo -e "$2" | sudo tee -a "$1" > /dev/null fi } 

Quoi qu’il en soit, les tentatives d’utilisation d’outils basés sur les lignes ont échoué, il est donc temps de sortir un langage de programmation plus sérieux qui ne force pas la convention de nouvelle ligne sur nous. Il suffit de lire les deux fichiers dans des variables et d’utiliser une recherche sans regex:

 #!/usr/bin/perl -w # multi_line_match.pl pattern_file target_file # exit(0) if a match is found, else exit(1) #use IO::File; use File::Slurp; my $pat = read_file($ARGV[0]); my $target = read_file($ARGV[1]); if ((substr($target, 0, length($pat)) eq $pat) or index($target, "\n".$pat) >= 0) { exit(0); } exit(1); 

Voir Quelle est la meilleure façon de filtrer un fichier dans une chaîne en Perl? pour éviter la dépendance de File::Slurp (qui ne fait pas partie de la dissortingbution standard de perl ou d’un système Ubuntu 15.04 par défaut). Je suis allé pour File :: Slurp en partie pour la lisibilité de ce que le programme fait, pour les non-perl-geeks, par rapport à:

 my $contents = do { local(@ARGV, $/) = $file; <> }; 

Je travaillais à éviter de lire le fichier complet en mémoire, avec une idée de http://www.perlmonks.org/?node_id=98208 . Je pense que les cas sans correspondance devraient toujours lire le fichier en entier immédiatement. En outre, la logique était assez complexe pour gérer une correspondance au début du fichier, et je ne voulais pas passer trop de temps à tester pour s’assurer qu’elle était correcte pour tous les cas. Voici ce que j’avais avant d’abandonner:

 #IO::File->input_record_separator($pat); $/ = $pat; # pat must include a trailing newline if you want it to match one my $fh = IO::File->new($ARGV[2], O_RDONLY) or die 'Could not open file ', $ARGV[2], ": $!"; $tail = substr($fh->getline, -1); #fast forward to the first match #print each occurence in the file #print IO::File->input_record_separator while $fh->getline; #FIXME: something clever here to handle the case where $pat matches at the beginning of the file. do { # fixme: need to check defined($fh->getline) if (($tail eq '\n') or ($tail = substr($fh->getline, -1))) { exit(0); # if there's a 2nd line } } while($tail); exit(1); $fh->close; 

Une autre idée consistait à filtrer les motifs et les fichiers à rechercher via tr '\n' '\r' ou quelque chose, ils seraient donc tous à une seule ligne. ( \r étant un choix sûr qui ne se heurterait à aucun élément déjà présent dans un fichier ou un motif.)

Je voudrais juste utiliser diff pour cette tâche:

 diff pattern <(grep -f file pattern) 

Explication

  • diff file1 file2 indique si deux fichiers diffèrent ou non.

  • En disant grep -f file pattern vous voyez quel contenu du pattern est dans le file .

Donc, ce que vous faites est de vérifier quelles lignes du pattern sont dans le file , puis de les comparer au pattern lui-même. Si elles correspondent, cela signifie que le pattern est un sous-ensemble du file !

Des tests

seq 10 fait partie du seq 20 ! Vérifions-le:

 $ diff <(seq 10) <(grep -f <(seq 20) <(seq 10)) $ 

seq 10 n'est pas exactement à l'intérieur de la seq 2 20 (1 n'est pas dans le second):

 $ diff -q <(seq 10) <(grep -f <(seq 2 20) <(seq 10)) Files /dev/fd/63 and /dev/fd/62 differ 

J’ai revu le problème à nouveau et je pense que awk peut mieux gérer cela:

 awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1 {for (i in a) len++} {for (i=last; i<=len; i++) { if (a[i]==$0) {last=i; next} } status=1} END {print status+0}' file pattern 

L'idée est la suivante: - Lire tout le fichier en mémoire dans un tableau sur a[line_number] = line . - Compter les éléments dans le tableau. - Parcourez le pattern fichier et vérifiez si la ligne en cours se trouve dans le file tout moment entre le curseur et la fin du file . S'il correspond, déplacez le curseur sur la position où il a été trouvé. Si ce n'est pas le cas, définissez le statut sur 1 - c'est-à-dire qu'il existe une ligne dans le pattern qui ne s'est pas produite dans le file après la correspondance précédente. - Imprimez le statut, qui sera 0 sauf s'il a été défini sur 1 tout moment.

Tester

Ils correspondent:

 $ tail fp ==> f <== 222 333 555 ==> p <== 222 333 $ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' fp 0 

Ils ne le font pas:

 $ tail fp ==> f <== 333 222 555 ==> p <== 222 333 $ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' fp 1 

Avec seq :

 $ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' <(seq 2 20) <(seq 10) 1 $ awk 'FNR==NR {a[FNR]=$0; next} FNR==1 && NR>1{for (i in a) len++} {for (i=last; i<=len; i++) {if (a[i]==$0) {last=i; next}} status=1} END {print status+0}' <(seq 20) <(seq 10) 0