Grep rekursif vs. find / -type f -exec grep {} \; Mana yang lebih efisien / lebih cepat?

70

Manakah yang lebih efisien untuk menemukan file mana di seluruh sistem file yang berisi string: grep rekursif atau temukan dengan grep dalam pernyataan exec? Saya menganggap menemukan akan lebih efisien karena Anda setidaknya dapat melakukan beberapa penyaringan jika Anda tahu ekstensi file atau regex yang cocok dengan nama file, tetapi ketika Anda hanya tahu -type fmana yang lebih baik? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Contoh:

grep -r -i 'the brown dog' /

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

Gregg Leventhal
sumber
1
Efisiensi matematika / ilmu komputer / algoritma berdasarkan opini.
Gregg Leventhal
Periksa ini. Meskipun tidak rekursif, itu akan memberikan pemahaman yang mana yang lebih baik. unix.stackexchange.com/questions/47983/…
Ramesh
8
@AvinashRaj dia tidak meminta pendapat. Dia bertanya mana yang lebih efisien dan / atau lebih cepat , bukan mana yang "lebih baik". Ini adalah pertanyaan yang dapat dijawab dengan sempurna yang memiliki satu jawaban spesifik yang tergantung pada bagaimana kedua program melakukan pekerjaan mereka dan pada apa yang Anda berikan kepada mereka untuk dicari.
terdon
2
Perhatikan bahwa -exec {} +formulir akan melakukan lebih sedikit fork, jadi harus lebih cepat daripada -exec {} \;. Anda mungkin perlu menambahkan -H(atau -h) ke grepopsi untuk mendapatkan hasil yang sama persis.
Mikel
Anda mungkin tidak ingin -ropsi grepuntuk yang kedua
qwertzguy

Jawaban:

85

Saya tidak yakin:

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

benar-benar yang Anda maksudkan. Itu berarti grep secara rekursif di semua file dan dirs non-tersembunyi /(tapi masih mencari di dalam file dan dirs tersembunyi di dalamnya).

Anggap Anda maksud:

grep -r -i 'the brown dog' /

Beberapa hal yang perlu diperhatikan:

  • Tidak semua grepimplementasi mendukung -r. Dan di antara mereka yang melakukannya, perilaku berbeda: beberapa mengikuti symlink ke direktori ketika melintasi pohon direktori (yang berarti Anda mungkin berakhir mencari beberapa kali dalam file yang sama atau bahkan berjalan dalam loop tak terbatas), beberapa tidak akan. Beberapa akan mencari di dalam file perangkat (dan itu akan membutuhkan beberapa waktu /dev/zeromisalnya) atau pipa atau file biner ..., beberapa tidak.
  • Ini efisien karena grepmulai mencari ke dalam file begitu menemukan mereka. Tetapi sementara itu terlihat dalam sebuah file, itu tidak lagi mencari lebih banyak file untuk dicari (yang mungkin sama baiknya dalam kebanyakan kasus)

Anda:

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

(menghapus -ryang tidak masuk akal di sini) sangat tidak efisien karena Anda menjalankan satu grepper file. ;seharusnya hanya digunakan untuk perintah yang hanya menerima satu argumen. Terlebih lagi di sini, karena grephanya terlihat dalam satu file, itu tidak akan mencetak nama file, sehingga Anda tidak akan tahu di mana pertandingannya.

Anda tidak mencari di dalam file perangkat, pipa, symlink ..., Anda tidak mengikuti symlink, tetapi Anda masih berpotensi mencari hal-hal seperti di dalamnya /proc/mem.

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

akan jauh lebih baik karena grepperintah sesedikit mungkin akan dijalankan. Anda akan mendapatkan nama file kecuali proses terakhir hanya memiliki satu file. Untuk itu lebih baik menggunakan:

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

atau dengan GNU grep:

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

Perhatikan bahwa greptidak akan dimulai sampai findmenemukan cukup file untuk dikunyah, sehingga akan ada beberapa penundaan awal. Dan findtidak akan melanjutkan mencari lebih banyak file sampai sebelumnya greptelah kembali. Mengalokasikan dan melewati daftar file besar memiliki beberapa dampak (mungkin dapat diabaikan), jadi semuanya mungkin akan kurang efisien daripada grep -ryang tidak mengikuti symlink atau melihat ke dalam perangkat.

Dengan alat GNU:

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

Seperti di atas, beberapa grepcontoh mungkin akan dijalankan, tetapi findakan terus mencari lebih banyak file sementara grepdoa pertama mencari di dalam batch pertama. Itu mungkin atau mungkin tidak menguntungkan. Misalnya, dengan data yang disimpan pada hard drive rotasi, finddan grepmengakses data yang disimpan di lokasi yang berbeda pada disk akan memperlambat throughput disk dengan menyebabkan kepala disk bergerak terus-menerus. Dalam pengaturan RAID (di mana finddan grepdapat mengakses disk yang berbeda) atau pada SSD, yang mungkin membuat perbedaan positif.

Dalam pengaturan RAID, menjalankan beberapa pemanggilan serentak grep juga dapat meningkatkan banyak hal. Masih dengan alat GNU pada penyimpanan RAID1 dengan 3 disk,

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

dapat meningkatkan kinerja secara signifikan. Namun perlu dicatat bahwa yang kedua grephanya akan dimulai setelah file yang cukup telah ditemukan untuk mengisi grepperintah pertama . Anda dapat menambahkan -nopsi agar hal xargsitu terjadi lebih cepat (dan memberikan lebih sedikit file per greppermintaan).

Juga perhatikan bahwa jika Anda mengarahkan xargsoutput ke apa pun selain perangkat terminal, maka grepss akan mulai buffering output mereka yang berarti bahwa output dari mereka grepmungkin akan disisipkan secara tidak benar. Anda harus menggunakan stdbuf -oL(jika tersedia seperti pada GNU atau FreeBSD) pada mereka untuk mengatasinya (Anda mungkin masih memiliki masalah dengan garis yang sangat panjang (biasanya> 4KiB)) atau minta masing-masing menuliskan output mereka dalam file terpisah dan menggabungkannya semua pada akhirnya.

Di sini, string yang Anda cari sudah diperbaiki (bukan regexp) jadi menggunakan -Fopsi mungkin akan membuat perbedaan (tidak mungkin karena grepimplementasi tahu cara mengoptimalkannya).

Hal lain yang dapat membuat perbedaan besar adalah memperbaiki lokal ke C jika Anda berada di lokal multi-byte:

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

Untuk menghindari mencari ke dalam /proc, /sys..., gunakan -xdevdan tentukan sistem file yang ingin Anda cari:

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

Atau pangkas jalur yang ingin Anda kecualikan secara eksplisit:

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
sumber
Saya tidak mengira seseorang dapat mengarahkan saya ke sumber daya - atau menjelaskan - apa {} dan + artinya. Tidak ada yang bisa saya lihat di halaman manual untuk exec, grep atau temukan pada kotak Solaris yang saya gunakan. Apakah hanya shell yang menyatukan nama file dan meneruskannya ke grep?
3
@Poldie, itu dengan jelas dijelaskan pada deskripsi -execpredikat di halaman manual Solaris
Stéphane Chazelas
Ah iya. Saya tidak melarikan diri {char saya saat mencari di halaman manual. Tautan Anda lebih baik; Saya menemukan halaman buku yang buruk untuk dibaca.
1
RAID1 w / 3 disk? Sungguh aneh ...
tink
1
@tink, ya RAID1 ada di 2 disk atau lebih. Dengan 3 disk dibandingkan dengan 2 disk, Anda meningkatkan redundansi dan membaca kinerja saat menulis kinerja kira-kira sama. Dengan 3 disk sebagai lawan 2, itu berarti Anda juga dapat memperbaiki kesalahan, seperti ketika sedikit membalik pada salah satu salinan, Anda dapat mengetahui mana yang benar dengan memeriksa semua 3 salinan sementara dengan 2 disk, Anda tidak bisa sungguh tahu.
Stéphane Chazelas
13

Jika *dalam greppanggilan tidak penting bagi Anda maka yang pertama harus lebih efisien karena hanya satu contoh dari grepdimulai, dan garpu tidak bebas. Dalam kebanyakan kasus akan lebih cepat bahkan dengan *tetapi dalam kasus tepi penyortiran bisa membalikkan itu.

Mungkin ada find- grepstruktur lain yang bekerja lebih baik terutama dengan banyak file kecil. Membaca entri dan inode file dalam jumlah besar sekaligus dapat memberikan peningkatan kinerja pada media yang berputar.

Tapi mari kita lihat statistik syscall:

Temukan

> 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

grep saja

> 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
sumber
1
Pada skala pencarian seluruh sistem file, garpu diabaikan. I / O adalah apa yang ingin Anda kurangi.
Gilles 'SANGAT berhenti menjadi jahat'
Meskipun ini adalah kesalahan dari OP, perbandingannya tidak benar, Anda harus menghapus -rbendera grepsaat menggunakan find. Anda dapat melihatnya berulang kali mencari file yang sama dengan membandingkan jumlah openyang terjadi.
qwertzguy
1
@ qwertzguy, tidak, -rseharusnya tidak berbahaya karena -type fjaminan tidak ada argumen yang direktori. Beberapa open()s lebih cenderung ke file lain yang dibuka oleh greppada setiap doa (perpustakaan, data lokalisasi ...) (terima kasih atas hasil edit pada jawaban saya btw)
Stéphane Chazelas
5

Jika Anda menggunakan SSD dan mencari waktu dapat diabaikan, Anda dapat menggunakan GNU parallel:

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

Ini akan menjalankan hingga 8 proses grep secara bersamaan berdasarkan apa yang findditemukan.

Ini akan meremukkan drive hard disk, tetapi SSD harus mengatasinya dengan cukup baik.

Naftuli Kay
sumber
-1

Satu hal lagi yang perlu dipertimbangkan dalam hal ini adalah sebagai berikut.

Apakah ada direktori yang grep harus secara rekursif melewati berisi lebih banyak file daripada pengaturan nofile sistem Anda ? (mis. jumlah pegangan file terbuka, standarnya adalah 1024 pada kebanyakan distro linux)

Jika demikian, maka mencari pasti cara untuk pergi karena versi grep tertentu akan membom dengan daftar Argument error terlalu lama ketika hits direktori dengan lebih banyak file daripada pengaturan menangani file terbuka maksimum.

Hanya 2 my saya.

B.Kaatz
sumber
1
Mengapa harus grepmengebom? Setidaknya dengan GNU grep jika Anda memberikan path dengan trailing /dan menggunakannya -Rhanya akan beralih melalui direktori. The shell tidak akan memperluas apa pun kecuali Anda memberikan shell-gumpalan. Jadi dalam contoh yang diberikan ( /*) hanya isi /materi, bukan dari subfolder yang hanya akan disebutkan oleh grep, tidak disahkan sebagai argumen dari shell.
0xC0000022L
Nah, mengingat OP bertanya tentang mencari secara rekursif (misalnya "grep -r -i 'the brown dog' / *"), saya telah melihat grep GNU (setidaknya Versi 2.9) meledak dengan: "- bash: / bin / grep: Daftar argumen terlalu panjang "menggunakan pencarian persis OP yang digunakan pada direktori yang memiliki lebih dari 140.000 sub-direktori di dalamnya.
B.Kaatz