3 votes

grep variables et donner un résultat informatif

Je veux voir combien de fois un mot spécifique a été mentionné dans le fichier/les lignes.

Mes exemples fictifs ressemblent à ceci :

cat words
blue
red 
green
yellow 

cat text
TEXTTEXTblueTEXTTEXTblue
TEXTTEXTgreenblueTEXTTEXT
TEXTTEXyeowTTEXTTEXTTEXT

C'est ce que je fais :

for i in $(cat words); do grep "$i" text | wc >> output; done

cat output
  2       2      51
  0       0       0
  1       1      26
  0       0       0

Mais ce que je veux vraiment obtenir est :
1. Mot qui a été utilisé comme variable ;
2. Dans combien de lignes (en plus des occurrences de texte) le mot a été trouvé.

Il est préférable que la sortie ressemble à ceci :

blue    3   2
red     0   0 
green   1   1
yellow  0   0

$1 - variable qui a été grep'ed
$2 - combien de fois la variable a été trouvée dans le texte
$3 - dans combien de lignes la variable a été trouvée

J'espère que quelqu'un pourra m'aider à faire cela avec grep, awk, sed car ils sont assez rapides pour le grand ensemble de données, mais Perl one liner m'aiderait aussi.

Modifier

J'ai essayé ça.

   for i in $(cat words); do grep "$i" text > out_${i}; done && wc out*  

et ça a l'air sympa, mais certains mots ont plus de 300 lettres et je ne peux pas créer de fichier nommé comme le mot.

4voto

Vivek Points 802

Vous pouvez utiliser le grep option -o qui n'impriment que les parties correspondantes d'une ligne correspondante, avec chaque correspondance sur une ligne de sortie séparée .

while IFS= read -r line; do
    wordcount=$(grep -o "$line" text | wc -l)
    linecount=$(grep -c "$line" text)
    echo $line $wordcount $linecount
done < words | column -t

Vous pouvez tout mettre sur une seule ligne pour en faire une ligne unique.

Si column donne l'erreur "column too long", vous pouvez utiliser printf à condition de connaître le nombre maximum de caractères. Utilisez le texte ci-dessous au lieu de echo et retirer le tuyau de la colonne :

printf "%-20s %-2s %-2s\n" "$line" $wordcount $linecount

Remplacez le chiffre 20 par votre longueur maximale de mots et les autres chiffres également si nécessaire.

3voto

amon Points 42005

Voici une solution similaire en Perl ; mais plutôt écrite comme un script complet.

#!/usr/bin/perl

use 5.012;

die "USAGE: $0 wordlist.txt [text-to-search.txt]\n" unless @ARGV;

my $wordsfile = shift @ARGV;
my @wordlist = do {
    open my $words_fh, "<", $wordsfile or die "Can't open $wordsfile: $!";
    map {chomp; length() ? $_ : ()} <$words_fh>;
};

my %words;
while (<>) {
    for my $word (@wordlist) {
        my $cnt = 0;
        $cnt++ for /\Q$word\E/g;
        $words{$word}[0] += $cnt;
        $words{$word}[1] += 1&!! $cnt; # trick to force 1 or 0.
    }
}

# sorts output after frequency. remove `sort {...}` to get unsorted output.
for my $key (sort {$words{$b}->[0] <=> $words{$a}->[0] or $a cmp $b} keys %words) {
    say join "\t", $key, @{ $words{$key} };
}

Exemple de sortie :

blue    3       2
green   1       1
red     0       0
yellow  0       0

Avantage par rapport à bash script : chaque fichier n'est lu qu'une fois.

1voto

Dave Sherohman Points 25122

C'est assez moche en Perl (en partie parce qu'il faut récupérer des données de deux fichiers et qu'un seul peut être envoyé sur stdin, en partie parce qu'il faut compter à la fois le nombre de lignes correspondantes et le nombre total de correspondances), mais voilà :

perl -E 'undef $|; open $w, "<", "words"; @w=<$w>; chomp @w; $r{$_}=[0,{}] for @w; my $re = join "|", @w; while(<>) { $l++; while (/($re)/g) { $r{$1}[0]++; $r{$1}[1]{$l}++; } }; say "$_\t$r{$_}[0]\t" . scalar keys %{$r{$_}[1]} for @w' < text

Cela nécessite perl 5.10 ou plus, mais le changer pour supporter 5.8 et plus tôt est trivial. (Changez le -E à -e le changement say à print et ajouter un \n à la fin de chaque ligne de sortie).

Sortie :

blue    3   2
red     0   0
green   1   1
yellow  0   0

1voto

Kent Points 71470

Un oneliner awk(gawk) pourrait vous épargner le casse-tête grep :

  awk 'NR==FNR{n[$0];l[$0];next;}{for(w in n){ s=$0;t=gsub(w,"#",s); n[w]+=t;l[w]+=t>0?1:0;}}END{for(x in n)print x,n[x],l[x]}' words text

formater un peu le code :

awk 'NR==FNR{n[$0];l[$0];next;}
    {for(w in n){ s=$0;
        t=gsub(w,"#",s); 
        n[w]+=t;l[w]+=t>0?1:0;}
    }END{for(x in n)print x,n[x],l[x]}' words text

test avec votre exemple :

kent$  awk 'NR==FNR{n[$0];l[$0];next;}{for(w in n){ s=$0;t=gsub(w,"#",s); n[w]+=t;l[w]+=t>0?1:0;}}END{for(x in n)print x,n[x],l[x]}' words text
yellow  0 0
red  0 0
green 1 1
blue 3 2

si vous voulez formater votre sortie, vous pouvez simplement envoyer la sortie awk vers column -t

donc ça ressemble à ça :

yellow  0  0
red     0  0
green   1  1
blue    3  2

1voto

Ed Morton Points 25374
awk '
NR==FNR { words[$0]; next }
{
   for (word in words) {
      count = gsub(word,word)
      if (count) {
         counts[word] += count
         lines[word]++
      }
   }
}
END { for (word in words) printf "%s %d %d\n", word, counts[word], lines[word] }
' file

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X