J'aimerais savoir s'il existe un moyen de combiner une série d'instructions grep où l'effet est de "et" les expressions plutôt que "ou" les expressions correspondantes.
Démo ci-dessous :
./script
From one grep statement, I want output like this
a b c
not like this
a
c
a b
a b c
a b c d
Écoutez un aperçu du script.
#!/bin/bash
string="a
b
c
d
a b
a b c
a b c d"
echo -e "\t From one grep statement I want output like this"
echo "$string" |
grep a |grep c |grep -v d #Correct output but pipes three grep statements
echo -e "\n\tNot like this"
echo "$string" |
grep -e'a' -e'c' -e-v'd' #One grep statement but matching expressions are "or" versus "and"
Réponse acceptée :
Vous ne pouvez pas transformer le filtre grep a | grep c | grep -v d
à un simple grep
. Il n'y a que des moyens compliqués et inefficaces. Le résultat a des performances lentes et la signification de l'expression est obscurcie.
Combinaison de commande unique des trois greps
Si vous voulez juste 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 aucune raison de simplifier un tube en une seule commande, sauf lorsque la combinaison aboutit à une expression grep relativement simple qui pourrait être plus rapide (voir les résultats ci-dessous).
Les systèmes de type Unix sont conçus pour utiliser des tuyaux et pour connecter divers utilitaires ensemble. Bien que la communication par tuyau ne soit pas la plus efficace possible, elle est suffisante dans la plupart des cas. Parce qu'aujourd'hui, la plupart des nouveaux ordinateurs ont plusieurs cœurs de processeur, vous pouvez "naturellement" utiliser la parallélisation du processeur simplement en utilisant un tube !
Votre filtre d'origine fonctionne très bien et je pense que dans de nombreux cas le awk
la solution serait un peu plus lente même sur un seul cœur.
Comparaison des performances
À l'aide d'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 à partir des caractères a
, b
, c
et d
. Le fichier a 1 Go. Pendant les tests, il a été entièrement chargé dans le cache, de sorte qu'aucune opération de disque n'a affecté la mesure des performances. Les tests ont été exécutés sur Intel dual core.
Grep unique
$ 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
Awk unique
$ time ( awk '/a/ && /c/ && $0 !~ /d/' testfile >/dev/null )
real 0m54.088s
user 0m53.755s
sys 0m0.304s
Les trois greps d'origine sont canalisé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, canalisation négative
$ 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 tube original de trois greps est assez rapide grâce à une bonne parallélisation. Sans parallélisation - sur un seul cœur - le tube d'origine s'exécute juste un peu plus vite 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 gagnant clair est l'hybridation combinant deux greps positifs et laissant le négatif dans le tuyau. Il semble que l'expression régulière avec |
n'a aucune pénalité de performance.