8 votes

Comment peut-on combiner une série d'instructions grep intégrées en une seule instruction grep ?

Je voudrais savoir s'il existe un moyen de combiner une série de déclarations grep où l'effet est de "et" les expressions plutôt que de "ou" les expressions correspondantes.

Démonstration ci-dessous :

./script  
     À partir d'une déclaration grep, je veux une sortie comme ceci
a b c

     pas comme cela
a
c
a b
a b c
a b c d

Voici un aperçu du script.

 #!/bin/bash
 string="a
 b
 c
 d
 a b
 a b c
 a b c d"

 echo -e "\t À partir d'une déclaration grep, je veux une sortie comme ceci"
 echo "$string" |
 grep a |grep c |grep -v d #Correct output but pipes three grep statements

 echo -e "\n\tPas comme cela"
 echo "$string" |
 grep -e'a' -e'c' -e-v'd' #One grep statement but matching expressions are "or" versus "and"

8voto

Michael Points 34110

Vous ne pouvez pas transformer le filtre grep a | grep c | grep -v d en une simple commande grep. Il n'y a que des moyens compliqués et inefficaces. Le résultat a une performance lente et le sens de l'expression est obscurci.

Combinaison de la trois greps en une seule commande

Si vous voulez simplement exécuter une seule commande, vous pouvez utiliser awk qui fonctionne également avec des expressions régulières et peut les combiner avec des opérateurs logiques. Voici l'équivalent de votre filtre:

awk '/a/ && /c/ && $0 !~ /d/'

Je pense que dans la plupart des cas, il n'y a pas de raison de simplifier un pipe en une seule commande sauf lorsque la combinaison donne une expression grep relativement simple qui pourrait être plus rapide (voir résultats ci-dessous).

Les systèmes de type Unix sont conçus pour utiliser des pipes et pour connecter divers utilitaires ensemble. Bien que la communication par pipe ne soit pas la plus efficace possible, mais dans la plupart des cas, elle est suffisante. Parce qu'aujourd'hui la plupart des nouveaux ordinateurs ont des cœurs de CPU multiples, vous pouvez utiliser naturellement une parallélisation de CPU en utilisant simplement un pipe!

Votre filtre d'origine fonctionne très bien et je pense que dans de nombreux cas, la solution awk serait un peu plus lente même sur un seul cœur.

Comparaison des performances

En utilisant un programme simple, j'ai généré un fichier de test aléatoire avec 200 000 000 lignes, chacune avec 4 caractères comme une combinaison aléatoire des caractères a, b, c et d. Le fichier fait 1 Go. Pendant les tests, il était complètement chargé en mémoire cache afin que les opérations sur le disque n'affectent pas la mesure des performances. Les tests ont été effectués sur un double cœur Intel.

Un seul grep

$ time ( grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*$' testfile >/dev/null )
real    3m2.752s
user    3m2.411s
sys 0m0.252s

Un seul awk

$ time ( awk '/a/ && /c/ && $0 !~ /d/' testfile >/dev/null )
real    0m54.088s
user    0m53.755s
sys 0m0.304s

Les trois greps originaux pipés

$ time ( grep a testfile | grep c | grep -v d >/dev/null )
real    0m28.794s
user    0m52.715s
sys 0m1.072s

Hybride - greps positifs combinés, négatif pipé

$ time ( grep -E 'a.*c|c.*a' testfile | grep -v d >/dev/null )
real    0m15.838s
user    0m24.998s
sys 0m0.676s

Ici, vous voyez que le seul grep est très lent à cause de l'expression complexe. Le pipe original des trois greps est assez rapide en raison d'une bonne parallélisation. Sans parallélisation - sur un seul cœur - le pipe original s'exécute légèrement plus rapidement que awk qui en tant que processus unique n'est pas parallélisé. Awk et grep utilisent probablement le même code d'expressions régulières et la logique des deux solutions est similaire.

Le grand gagnant est l'hybride qui combine deux greps positifs et laisse le négatif dans le pipe. Il semble que l'expression régulière avec | n'ait pas de pénalité de performance.

2voto

Sparhawk Points 6620

Le problème est que -e fonctionne comme un ou, pas comme un et. Vous pouvez le faire en une seule ligne, mais c'est assez compliqué. La partie négation est la plus compliquée.

Pour simplifier les parties a et c (en supposant que l'ordre est inconnu) :

grep -E 'a.*c|c.*a'

ou

grep -e 'a.*c' -e 'c.*a'

Par conséquent, vous pourriez faire

grep -E 'a.*c|c.*a' | grep -v 'd'

Pour une seule déclaration grep, vous devrez vous assurer qu'il n'y a pas de d avant, après ou entre les a et c :

grep -E '^[^d]*a[^d]*c[^d]*$|^[^d]*c[^d]*a[^d]*$'

0voto

Vous pouvez utiliser l'interrupteur -x, qui selon la page de manuel de grep, "sélectionne uniquement les correspondances qui correspondent exactement à toute la ligne".

Dans votre exemple, essayez : grep -x "a b c"

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X