Zajímalo by mě, zda existuje způsob, jak zkombinovat řadu příkazů grep, kde výsledkem je „a“ výrazy spíše než „nebo“ odpovídající výrazy.
Demo níže:
./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
Slyšet je pohled na scénář.
#!/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"
Přijatá odpověď:
Nemůžete transformovat filtr grep a | grep c | grep -v d
do jediného jednoduchého grep
. Existují pouze složité a neefektivní způsoby. Výsledek má pomalý výkon a význam výrazu je zastřený.
Jedna kombinace příkazů tří greps
Pokud chcete spustit pouze jeden příkaz, můžete použít awk
který pracuje i s regulárními výrazy a umí je kombinovat s logickými operátory. Zde je ekvivalent vašeho filtru:
awk '/a/ && /c/ && $0 !~ /d/'
Myslím, že ve většině případů není důvod zjednodušovat rouru na jeden příkaz, kromě případů, kdy výsledkem kombinace je skutečně jednoduchý výraz grep, který by mohl být rychlejší (viz výsledky níže).
Unixové systémy jsou navrženy tak, aby využívaly potrubí a spojovaly různé inženýrské sítě dohromady. Potrubní komunikace sice není nejefektivnější možná, ale ve většině případů je dostačující. Protože v dnešní době má většina nových počítačů více jader CPU, můžete „přirozeně“ využívat paralelizaci CPU pouhým použitím potrubí!
Váš původní filtr funguje velmi dobře a myslím si, že v mnoha případech awk
řešení by bylo o něco pomalejší i na jednom jádru.
Porovnání výkonu
Pomocí jednoduchého programu jsem vygeneroval náhodný testovací soubor s 200 000 000 řádky, každý se 4 znaky jako náhodnou kombinací znaků a
, b
, c
a d
. Soubor má 1 GB. Během testů byl zcela načten do mezipaměti, takže žádné operace s diskem neovlivnily měření výkonu. Testy byly spuštěny na dvoujádrovém procesoru Intel.
Jeden 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
Jeden awk
$ time ( awk '/a/ && /c/ && $0 !~ /d/' testfile >/dev/null )
real 0m54.088s
user 0m53.755s
sys 0m0.304s
Původní tři grepy zapojené
$ time ( grep a testfile | grep c | grep -v d >/dev/null )
real 0m28.794s
user 0m52.715s
sys 0m1.072s
Hybridní – kladné grepy kombinované, záporné sdružené
$ time ( grep -E 'a.*c|c.*a' testfile | grep -v d >/dev/null )
real 0m15.838s
user 0m24.998s
sys 0m0.676s
Zde vidíte, že jediný grep
je velmi pomalý kvůli složitému výrazu. Původní potrubí tří grepů je docela rychlé díky dobré paralelizaci. Bez paralelizace – na jediném jádru – běží původní potrubí jen o něco rychleji než awk
který jako jediný proces není paralelizován. Awk a grep pravděpodobně používají stejný kód regulárních výrazů a logika těchto dvou řešení je podobná.
Jednoznačným vítězem je hybridní spojení dvou pozitivních grepů a ponechání negativního v potrubí. Zdá se, že regulární výraz s |
nemá žádnou výkonnostní penalizaci.