Рекурсивный grep против find / -type f -exec grep {} \; Что является более эффективным / быстрее?

70

Что является более эффективным для определения того, какие файлы во всей файловой системе содержат строку: рекурсивный grep или поиск с помощью grep в выражении exec? Я предполагаю, что поиск будет более эффективным, потому что вы можете по крайней мере выполнить некоторую фильтрацию, если знаете расширение файла или регулярное выражение, соответствующее имени файла, но когда вы знаете только, -type fчто лучше? GNU grep 2.6.3; find (GNU findutils) 4.4.2

Пример:

grep -r -i 'the brown dog' /

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

Грегг Левенталь
источник
1
Математика / информатика / эффективность алгоритмов не основаны на мнениях.
Грегг Левенталь
Проверьте это. Хотя это и не рекурсивно, это дало бы понимание того, что лучше. unix.stackexchange.com/questions/47983/…
Рамеш
8
@AvinashRaj он не спрашивает мнение. Он спрашивает, что является более эффективным и / или более быстрым , а не какое «лучше». Это совершенно отвечающий вопрос, на который есть один конкретный ответ, который зависит от того, как эти две программы выполняют свою работу, и от того, что именно вы даете им для поиска.
Тердон
2
Обратите внимание, что -exec {} +форма будет делать меньше вилок, поэтому должна быть быстрее, чем -exec {} \;. Возможно, вам придется добавить -H(или -h) к grepпараметрам, чтобы получить точно эквивалентный результат.
Микель
Вы, вероятно, не хотели -rопцию grepдля второго
qwertzguy

Ответы:

85

Я не уверен:

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

действительно то, что вы имели в виду. Это будет означать рекурсивный grep во всех не скрытых файлах и директориях /(но все же заглянуть внутрь скрытых файлов и каталогов внутри них).

Предполагая, что вы имели в виду:

grep -r -i 'the brown dog' /

Несколько вещей, на которые стоит обратить внимание:

  • Не все grepреализации поддерживают -r. И среди тех, кто это делает, поведение отличается: некоторые переходят по символическим ссылкам на каталоги при обходе дерева каталогов (что означает, что вы можете в конечном итоге просмотреть один и тот же файл несколько раз или даже выполнить бесконечные циклы), некоторые - нет. Некоторые будут смотреть внутри файлов устройств (и это займет довольно много времени, /dev/zeroнапример) или каналов или двоичных файлов ..., некоторые не будут.
  • Это эффективно, так как grepначинает искать внутри файлов, как только обнаруживает их. Но пока он просматривает файл, он больше не ищет больше файлов для поиска (что, вероятно, также хорошо в большинстве случаев)

Твой:

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

(убрал то, -rчто здесь не имело смысла) ужасно неэффективно, потому что вы запускаете по одному grepна файл. ;следует использовать только для команд, которые принимают только один аргумент. Более того, здесь, поскольку grepвыглядит только в одном файле, он не будет печатать имя файла, поэтому вы не будете знать, где находятся совпадения.

Вы не заглядывая внутрь файлы устройства, трубы, симлинки ..., вы не следующие ссылок, но вы по- прежнему потенциально смотрите внутри вещи , как /proc/mem.

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

было бы намного лучше, потому что grepбыло бы выполнено как можно меньше команд. Вы получите имя файла, если только у последнего запуска не будет только одного файла. Для этого лучше использовать:

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

или с GNU grep:

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

Обратите внимание, что grepон не будет запущен, пока findне найдет достаточно файлов для его пережевывания, поэтому будет некоторая начальная задержка. И findне будет продолжать поиск других файлов, пока grepне вернется предыдущий . Распределение и передача большого списка файлов оказывает некоторое (вероятно, незначительное) влияние, поэтому в целом он, вероятно, будет менее эффективным, чем grep -rтот, который не следует по символической ссылке или не заглядывает внутрь устройств.

С инструментами GNU:

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

Как и выше, grepбудет запущено как можно меньше экземпляров, но findбудет продолжаться поиск большего количества файлов, пока первый grepвызов просматривает первый пакет. Это может или не может быть преимуществом, хотя. Например, данные, хранящиеся на вращающихся жестких дисках, findи grepдоступ к данным, хранящимся в разных местах на диске, замедляют пропускную способность диска, вызывая постоянное перемещение головки диска. В настройке RAID (где findи grepмогут иметься доступ к разным дискам) или на SSD это может иметь положительное значение.

В настройке RAID выполнение нескольких одновременных grep вызовов также может улучшить ситуацию. Все еще с инструментами GNU на хранилище RAID1 с 3 дисками,

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

может значительно увеличить производительность. Однако обратите внимание, что вторая grepбудет запущена только после того, как будет найдено достаточно файлов для заполнения первой grepкоманды. Вы можете добавить -nопцию xargsдля того, чтобы это произошло раньше (и передавать меньше файлов за grepвызов).

Также обратите внимание, что если вы перенаправляете xargsвывод на что-либо, кроме оконечного устройства, то grepss начнут буферизовать свои выходные данные, что означает, что выходные данные этих greps будут, вероятно, неправильно чередоваться. Вам придется использовать stdbuf -oL(там, где это доступно, например, в GNU или FreeBSD) их, чтобы обойти это (у вас все еще могут быть проблемы с очень длинными строками (обычно> 4 КБ)) или каждый из них записывает свои выходные данные в отдельный файл и объединяет их все в итоге.

Здесь искомая строка является фиксированной (не является регулярным выражением), поэтому использование -Fопции может иметь значение (маловероятно, поскольку grepреализации уже знают, как ее оптимизировать).

Еще одна вещь, которая может иметь большое значение, это исправить языковой стандарт на C, если вы находитесь в многобайтовом языковом стандарте:

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

Чтобы не заглядывать внутрь /proc, /sys... используйте -xdevи укажите файловые системы, в которых вы хотите искать:

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

Или удалите пути, которые вы хотите явно исключить:

LC_ALL=C find / \( -path /dev -o -path /proc -o -path /sys \) -prune -o \
  -type f -exec grep -i 'the brown dog' /dev/null {} +
Стефан Шазелас
источник
Я не думаю, что кто-то может указать мне на ресурс - или объяснить - что означают {} и +. Там нет ничего, что я могу увидеть на страницах руководства для exec, grep или найти в окне Solaris, которое я использую. Разве оболочка объединяет имена файлов и передает их в grep?
3
@Poldie, это ясно объяснено в описании -execпредиката на странице
Стефан Шазелас
О да. Я не сбежал от своего персонажа, пока искал в справочной странице. Ваша ссылка лучше; Я нахожу страницы справки ужасными для чтения.
1
RAID1 с 3 дисками? Как странно ...
Тинк
1
@tink, да RAID1 находится на 2 или более дисках. С 3 дисками по сравнению с 2 дисками вы увеличиваете избыточность и производительность чтения, тогда как производительность записи примерно одинакова. С 3 дисками, а не с 2, это означает, что вы также можете исправлять ошибки, так как, когда немного переворачивается на одной из копий, вы можете определить, какая из них верна, проверив все 3 копии, а с двумя дисками вы не можете действительно скажи.
Стефан Шазелас
13

Если *в grepвызове не важно для вас , то первый должен быть более эффективным , так как только один экземпляр grepзапускается, и вилки не свободны. В большинстве случаев это будет быстрее, даже если, *но в крайних случаях сортировка может изменить это.

Там может быть и другими find- grepструктуры , которые работают лучше , особенно с большим количеством мелких файлов. Чтение большого количества записей в файлах и инодов одновременно может повысить производительность вращающихся носителей.

Но давайте посмотрим на статистику системных вызовов:

находить

> 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

> 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
Хауке Лагинг
источник
1
В масштабах поиска по всей файловой системе вилки ничтожны. I / O - это то, что вы хотите уменьшить.
Жиль "ТАК - перестань быть злым"
Хотя это ошибка из OP, сравнение некорректно, вы должны удалить -rфлаг grepпри использовании find. Вы можете видеть, что он снова и снова просматривал одни и те же файлы, сравнивая количество openпроизошедших событий.
qwertzguy
1
@qwertzguy, нет, они -rдолжны быть безвредными, так как -type fгарантии, что ни один из аргументов не является каталогом. Множественные open()значения более вероятны вплоть до других файлов, открываемых grepпри каждом вызове (библиотеки, данные о локализации ...) (спасибо за редактирование моего ответа)
Стефан Шазелас
5

Если вы используете SSD и время поиска незначительное, вы можете использовать GNU параллельно:

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

Это выполнит до 8 процессов grep одновременно, в зависимости от findнайденного.

Это сломает жесткий диск, но SSD должен справиться с этим довольно хорошо.

Нафтули Кей
источник
-1

Еще одна вещь, на которую следует обратить внимание, заключается в следующем.

Будут ли какие-либо из каталогов, которые grep должен будет рекурсивно проходить, содержать больше файлов, чем параметр nofile вашей системы ? (например, количество дескрипторов открытых файлов, по умолчанию 1024 на большинстве дистрибутивов Linux)

Если это так, то определенно стоит искать, так как некоторые версии grep будут выдавать слишком длинную ошибку из списка аргументов при попадании в каталог с большим количеством файлов, чем задано параметром максимального количества открытых файлов.

Просто мои 2 ¢.

B.Kaatz
источник
1
Зачем grepбомбить? По крайней мере, с GNU grep, если вы дадите путь с трейлингом /и -Rбудете его использовать, вы будете просто перебирать каталоги. Оболочка не собирается расширять ничего , если не дать оболочки-шарики. Таким образом, в данном примере ( /*) только содержимое /материи, а не подпапок, которые будут просто перечислены grep, не передается в качестве аргумента из оболочки.
0xC0000022L
Что ж, учитывая, что ОП спрашивал о рекурсивном поиске (например, "grep -r -i 'коричневая собака' / *"), я видел, как GNU grep (по крайней мере, версия 2.9) бомбил с помощью: "- bash: / bin / grep: Список аргументов слишком длинный, "используя точный поиск, используемый OP в каталоге, в котором было более 140 000 подкаталогов.
Б. Каац