Rekurencyjne grep vs find / -type f -exec grep {} \; Który jest bardziej wydajny / szybszy?

70

Które jest bardziej skuteczne w wyszukiwaniu, które pliki w całym systemie plików zawierają ciąg znaków: rekurencyjny grep lub znajdź za pomocą grep w instrukcji exec? Zakładam, że znajdowanie byłoby bardziej wydajne, ponieważ możesz przynajmniej przeprowadzić filtrowanie, jeśli znasz rozszerzenie pliku lub wyrażenie regularne pasujące do nazwy pliku, ale kiedy wiesz tylko, -type fktóra z nich jest lepsza? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Przykład:

grep -r -i 'the brown dog' /

find / -type f -exec grep -i 'the brown dog' {} \;

Gregg Leventhal
źródło
1
Wydajność matematyki / informatyki / algorytmu nie jest oparta na opiniach.
Gregg Leventhal
Sprawdź ten. Choć nie rekurencyjne, dałoby to zrozumienie, na czym lepiej. unix.stackexchange.com/questions/47983/…
Ramesh
8
@AvinashRaj nie pyta o opinię. Pyta, który jest bardziej wydajny i / lub szybszy , a nie który jest „lepszy”. To pytanie, na które można w pełni odpowiedzieć, ma jedną, konkretną odpowiedź, która zależy od tego, jak te dwa programy wykonują swoją pracę i od tego, co dokładnie dajesz im do przeszukania.
terdon
2
Pamiętaj, że -exec {} +formularz będzie mniej widelców, więc powinien być szybszy niż -exec {} \;. Konieczne może być dodanie -H(lub -h) grepopcji, aby uzyskać dokładnie równoważny wynik.
Mikel
Prawdopodobnie nie chciał -ropcję grepdla drugiego
qwertzguy

Odpowiedzi:

85

Nie jestem pewny:

grep -r -i 'the brown dog' /*

to naprawdę to, co miałeś na myśli. Oznaczałoby to rekurencyjnie grep we wszystkich nie ukrytych plikach i katalogach w /(ale nadal zaglądanie do ukrytych plików i katalogów wewnątrz nich).

Zakładając, że miałeś na myśli:

grep -r -i 'the brown dog' /

Kilka rzeczy do zapamiętania:

  • Nie wszystkie grepimplementacje obsługują -r. A wśród tych, które się tak zachowują, zachowania się różnią: niektóre podążają za dowiązaniami symbolicznymi do katalogów podczas przechodzenia przez drzewo katalogów (co oznacza, że ​​możesz skończyć przeglądać kilka razy w tym samym pliku lub nawet działać w nieskończonych pętlach), niektóre nie. Niektóre zaglądają do plików urządzeń ( /dev/zerona przykład zajmie to trochę czasu ) lub potoków lub plików binarnych ... niektóre nie.
  • Jest skuteczny, ponieważ grepzaczyna przeszukiwać pliki, gdy tylko je odkryje. Ale gdy szuka w pliku, nie szuka już więcej plików do przeszukania (co w większości przypadków jest prawdopodobnie równie dobre)

Twój:

find / -type f -exec grep -i 'the brown dog' {} \;

(usunięto to, -rco nie miało sensu tutaj) jest strasznie nieefektywne, ponieważ uruchamiasz jeden grepna plik. ;powinien być używany tylko dla poleceń, które akceptują tylko jeden argument. Ponadto tutaj, ponieważ grepwygląda tylko w jednym pliku, nie wydrukuje nazwy pliku, więc nie będziesz wiedział, gdzie są dopasowania.

Nie przeglądasz plików urządzeń, potoków, dowiązań symbolicznych ..., nie podążasz za dowiązaniami symbolicznymi, ale nadal potencjalnie zaglądasz do takich rzeczy jak /proc/mem.

find / -type f -exec grep -i 'the brown dog' {} +

byłoby znacznie lepiej, ponieważ grepuruchomiono by jak najmniej poleceń. Otrzymasz nazwę pliku, chyba że ostatnie uruchomienie zawiera tylko jeden plik. W tym celu lepiej użyć:

find / -type f -exec grep -i 'the brown dog' /dev/null {} +

lub z GNU grep:

find / -type f -exec grep -Hi 'the brown dog' {} +

Pamiętaj, że grepnie zostanie uruchomiony, dopóki findnie znajdzie wystarczającej liczby plików do przeżuwania, więc wystąpi pewne początkowe opóźnienie. I findnie będzie kontynuował wyszukiwania kolejnych plików, dopóki poprzednie grepnie powróci. Przydzielanie i przekazywanie dużej listy plików ma pewien (prawdopodobnie nieistotny) wpływ, więc w sumie prawdopodobnie będzie mniej wydajne niż to, grep -rże nie podąża za dowiązaniem symbolicznym ani nie przegląda urządzeń.

Za pomocą narzędzi GNU:

find / -type f -print0 | xargs -r0 grep -Hi 'the brown dog'

Jak wyżej, grepuruchomionych findzostanie jak najmniej instancji, ale będzie ona nadal szukała więcej plików, podczas gdy pierwsze grepwywołanie zajrzy do pierwszej partii. To może, ale nie musi być zaletą. Na przykład dane przechowywane na obrotowych dyskach twardych findi grepuzyskiwanie dostępu do danych przechowywanych w różnych miejscach na dysku spowolnią przepustowość dysku, powodując ciągłe ruchy głowicy dysku. W konfiguracji RAID (gdzie findi grepmoże uzyskiwać dostęp do różnych dysków) lub na dyskach SSD może to mieć pozytywny wpływ.

W konfiguracji RAID, bieganie kilka jednoczesnych grep wywołania może również poprawić rzeczy. Nadal z narzędziami GNU na macierzy RAID1 z 3 dyskami,

find / -type f -print0 | xargs -r0 -P2 grep -Hi 'the brown dog'

może znacznie zwiększyć wydajność. Zauważ jednak, że drugi grepzostanie uruchomiony dopiero po znalezieniu wystarczającej liczby plików do wypełnienia pierwszego greppolecenia. Możesz dodać -nopcję xargs, aby stało się to wcześniej (i przekazać mniej plików na jedno grepwywołanie).

Zauważ też, że jeśli przekierowujesz xargsdane wyjściowe na cokolwiek innego niż urządzenie końcowe, grepss zacznie buforować swoje dane wyjściowe, co oznacza, że ​​dane wyjściowe tych grepprawdopodobnie zostaną nieprawidłowo przeplecione. Będziesz musiał użyć na nich stdbuf -oL(jeśli jest dostępny, jak na GNU lub FreeBSD), aby obejść ten problem (nadal możesz mieć problemy z bardzo długimi liniami (zazwyczaj> 4KiB)) lub poproś każde z nich o zapisanie ich wyników w osobnym pliku i połączenie ich. wszystko w końcu.

Tutaj ciąg, którego szukasz, jest naprawiony (nie jest wyrażeniem regularnym), więc użycie tej -Fopcji może mieć znaczenie (mało prawdopodobne, ponieważ grepimplementacje wiedzą, jak to zoptymalizować).

Inną rzeczą, która może mieć dużą różnicę, jest ustawienie języka na C, jeśli jesteś w ustawieniu wielobajtowym:

find / -type f -print0 | LC_ALL=C xargs -r0 -P2 grep -Hi 'the brown dog'

Aby uniknąć zaglądania do wnętrza /proc, /sys... użyj -xdevi określ systemy plików, w których chcesz wyszukiwać:

LC_ALL=C find / /home -xdev -type f -exec grep -i 'the brown dog' /dev/null {} +

Lub przycinaj ścieżki, które chcesz jawnie wykluczyć:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +
Stéphane Chazelas
źródło
Nie sądzę, aby ktoś mógł wskazać mi zasób - lub wyjaśnić - co oznaczają {} i +. Nic nie widzę na stronach podręcznika dla exec, grep lub znajduję na polu Solaris, którego używam. Czy tylko powłoka łączy nazwy plików i przekazuje je grep?
3
@Poldie, to jasno wyjaśniono w opisie -execorzeczenia na stronie podręcznika Solaris
Stéphane Chazelas
O tak. Nie uciekałem {char podczas wyszukiwania na stronie podręcznika. Twój link jest lepszy; Uważam, że strony podręcznika są trudne do przeczytania.
1
RAID1 z 3 dyskami? Jak dziwnie ...
zadzwoń
1
@tink, tak RAID1 znajduje się na 2 lub więcej dyskach. 3 dyski w porównaniu do 2 dysków zwiększają redundancję i wydajność odczytu, a wydajność zapisu jest mniej więcej taka sama. W przypadku 3 dysków w przeciwieństwie do 2 oznacza to, że możesz także poprawić błędy, ponieważ gdy trochę przewróci się jedna z kopii, możesz stwierdzić, która jest poprawna, sprawdzając wszystkie 3 kopie, mając 2 dyski, nie możesz naprawdę powiedz.
Stéphane Chazelas,
13

Jeśli *w grepwywołaniu nie jest dla ciebie ważne to pierwszy powinien być bardziej efektywny, jak tylko jedna instancja grepjest uruchomiona, i widły nie są wolne. W większości przypadków będzie to szybsze, nawet *w skrajnych przypadkach sortowanie może to odwrócić.

Mogą istnieć inne find- grepstruktury, które działają lepiej, zwłaszcza w przypadku wielu małych plików. Odczytywanie dużych ilości wpisów plików i i-węzłów jednocześnie może poprawić wydajność obracających się mediów.

Ale spójrzmy na statystyki syscall:

odnaleźć

> strace -cf find . -type f -exec grep -i -r 'the brown dog' {} \;
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 97.86    0.883000        3619       244           wait4
  0.53    0.004809           1      9318      4658 open
  0.46    0.004165           1      6875           mmap
  0.28    0.002555           3       977       732 execve
  0.19    0.001677           2       980       735 stat
  0.15    0.001366           1      1966           mprotect
  0.09    0.000837           0      1820           read
  0.09    0.000784           0      5647           close
  0.07    0.000604           0      5215           fstat
  0.06    0.000537           1       493           munmap
  0.05    0.000465           2       244           clone
  0.04    0.000356           1       245       245 access
  0.03    0.000287           2       134           newfstatat
  0.03    0.000235           1       312           openat
  0.02    0.000193           0       743           brk
  0.01    0.000082           0       245           arch_prctl
  0.01    0.000050           0       134           getdents
  0.00    0.000045           0       245           futex
  0.00    0.000041           0       491           rt_sigaction
  0.00    0.000041           0       246           getrlimit
  0.00    0.000040           0       489       244 ioctl
  0.00    0.000038           0       591           fcntl
  0.00    0.000028           0       204       188 lseek
  0.00    0.000024           0       489           set_robust_list
  0.00    0.000013           0       245           rt_sigprocmask
  0.00    0.000012           0       245           set_tid_address
  0.00    0.000000           0         1           uname
  0.00    0.000000           0       245           fchdir
  0.00    0.000000           0         2         1 statfs
------ ----------- ----------- --------- --------- ----------------
100.00    0.902284                 39085      6803 total

tylko grep

> strace -cf grep -r -i 'the brown dog' .
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 40.00    0.000304           2       134           getdents
 31.71    0.000241           0       533           read
 18.82    0.000143           0       319         6 openat
  4.08    0.000031           4         8           mprotect
  3.29    0.000025           0       199       193 lseek
  2.11    0.000016           0       401           close
  0.00    0.000000           0        38        19 open
  0.00    0.000000           0         6         3 stat
  0.00    0.000000           0       333           fstat
  0.00    0.000000           0        32           mmap
  0.00    0.000000           0         4           munmap
  0.00    0.000000           0         6           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0       245       244 ioctl
  0.00    0.000000           0         1         1 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0       471           fcntl
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           futex
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0       132           newfstatat
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.000760                  2871       466 total
Hauke ​​Laging
źródło
1
W skali przeszukiwania całego systemu plików rozwidlenia są znikome. I / O jest tym, co chcesz zmniejszyć.
Gilles
Chociaż jest to błąd z PO, porównanie jest niepoprawne, powinieneś usunąć -rflagę greppodczas używania find. Możesz zobaczyć, że wielokrotnie przeszukiwał te same pliki, porównując ich liczbę open.
qwertzguy
1
@qwertzguy, nie, -rpowinno być nieszkodliwe, ponieważ -type fgwarancje, że żaden argument nie jest katalogiem. Wiele z nich open()jest bardziej prawdopodobne w stosunku do innych plików otwieranych grepprzy każdym wywołaniu (biblioteki, dane lokalizacji ...) (dzięki za edycję mojej odpowiedzi btw)
Stéphane Chazelas
5

Jeśli korzystasz z dysku SSD i czas poszukiwania jest znikomy, możesz użyć GNU równolegle:

find /path -type f | parallel --gnu --workdir "$PWD" -j 8 '
    grep -i -r 'the brown dog' {} 
'

Spowoduje to wykonanie do 8 procesów grep jednocześnie, w zależności od tego, co findznaleziono.

Spowoduje to uszkodzenie dysku twardego, ale dysk SSD powinien sobie z tym poradzić.

Naftuli Kay
źródło
-1

Jeszcze jedna rzecz do rozważenia w tej sprawie jest następująca.

Czy którykolwiek z katalogów, przez które grep będzie musiał przechodzić rekurencyjnie, będzie zawierał więcej plików niż ustawienie nofile w twoim systemie ? (np. liczba otwartych uchwytów plików, domyślnie jest to 1024 w większości dystrybucji Linuksa)

Jeśli tak, to zdecydowanie jest droga do znalezienia, ponieważ niektóre wersje grep będą bombardować zbyt długim błędem listy argumentów, gdy trafi do katalogu z większą liczbą plików niż ustawienie maksymalnej liczby otwartych uchwytów plików.

Tylko moje 2 ¢.

B.Kaatz
źródło
1
Dlaczego miałby się grepbombardować? Przynajmniej z GNU grep, jeśli podasz ścieżkę z trailingiem /i użyjesz -Rjej, po prostu przejdzie przez katalogi. Powłoka nie będzie się rozwijać coś chyba dasz shell-globs. Tak więc w podanym przykładzie ( /*) tylko treść /materii, a nie podfolderów, które zostaną po prostu wyliczone grep, nie jest przekazywana jako argument z powłoki.
0xC0000022L
Cóż, biorąc pod uwagę, że OP pytał o wyszukiwanie rekurencyjne (np. „Grep -r -i 'brązowy pies” / * ”), widziałem, że grep GNU (przynajmniej wersja 2.9) bombarduje:„ - bash: / bin / grep: lista argumentów za długa ”przy użyciu dokładnego wyszukiwania OP użytego w katalogu, w którym znajduje się ponad 140 000 podkatalogów.
B.Kaatz 12.04.16