При циклическом просмотре файлов существует два способа:
используйте
for
-loop:for f in *; do echo "$f" done
использовать
find
:find * -prune | while read f; do echo "$f" done
Предполагая, что эти два цикла найдут один и тот же список файлов, каковы различия между этими двумя параметрами в производительности и обработке?
bash
shell-script
performance
rubo77
источник
источник
find
не открывает файлы, которые он находит. Единственное, что я вижу, кусает вас здесь из-за большого количества файлов - это ARG_MAX .read f
будут искажать имена файлов при их чтении (например, имена с начальными пробелами). Также,find * -prune
кажется, очень запутанный способ сказать простоls -1
да?find .
, нетfind *
.ls -l
- плохая идея. Но разборls -1
(это1
не анl
) не хуже, чем разборfind * -prune
. Оба не работают на файлах с символами новой строки в именах.Ответы:
1.
Первый:
происходит сбой для вызываемых файлов
-n
,-e
и вариантов, таких как-nene
и с некоторыми развертываниями bash, с именами файлов, содержащими обратную косую черту.Секунда:
не может даже больше случаев (файлы называются
!
,-H
,-name
,(
, имена файлов , которые начинаются или конец с пробелами или содержат символы новой строки ...)Это оболочка, которая расширяется
*
,find
ничего не делает, кроме печати файлов, которые она получает в качестве аргументов. Вы могли бы также использоватьprintf '%s\n'
вместо этогоprintf
встроенную функцию, которая также избежала бы слишком большой потенциальной ошибки аргументов .2.
Расширение
*
отсортировано, вы можете сделать его немного быстрее, если вам не нужна сортировка. Вzsh
:или просто:
bash
насколько я могу судить, не имеет аналогов, поэтому вам придется прибегнуть к помощиfind
.3.
(выше с использованием
-print0
нестандартного расширения GNU / BSD ).Это все еще включает в себя создание команды find и использование медленного
while read
цикла, поэтому, вероятно, он будет медленнее, чем использованиеfor
цикла, если список файлов не будет огромным.4.
Кроме того, в отличие от расширения с подстановочными символами оболочки, системный вызов
find
будет выполняться дляlstat
каждого файла, поэтому маловероятно, что несортировка компенсирует это.С GNU / BSD
find
этого можно избежать, используя их-maxdepth
расширение, которое вызовет оптимизацию, сохраняяlstat
:Потому что
find
начинает выводить имена файлов, как только находит их (за исключением буферизации вывода stdio), где это может быть быстрее, если то, что вы делаете в цикле, занимает много времени, а список имен файлов больше, чем буфер stdio (4 / 8 кБ). В этом случае обработка внутри цикла начнется до того, какfind
будет завершен поиск всех файлов. В системах GNU и FreeBSD вы можете использовать,stdbuf
чтобы это произошло раньше (отключение буферизации stdio).5.
POSIX / стандартный / переносимый способ запуска команд для каждого файла с
find
использованием-exec
предиката:В случае,
echo
однако, это менее эффективно, чем выполнение цикла в оболочке, поскольку оболочка будет иметь встроенную версию, вecho
то время какfind
потребуется порождать новый процесс и выполнять/bin/echo
в нем для каждого файла.Если вам нужно выполнить несколько команд, вы можете сделать:
Но будьте осторожны,
cmd2
это выполняется только в случаеcmd1
успеха.6.
Канонический способ запуска сложных команд для каждого файла - вызвать оболочку с помощью
-exec ... {} +
:На этот раз мы вернулись к эффективности,
echo
поскольку мы используемsh
встроенную-exec +
версию, а версия порождаетsh
как можно меньше.7.
В моих тестах с каталогом, содержащим 200 000 файлов с короткими именами в ext4,
zsh
один (параграф 2.) является самым быстрым, за ним следует первый простойfor i in *
цикл (хотя, как обычно,bash
он намного медленнее, чем другие оболочки).источник
!
команда find?!
для отрицания.! -name . -prune more...
будет делать-prune
(иmore...
так как-prune
всегда возвращает true) для каждого файла, но.
. Так что он будет работатьmore...
со всеми файлами в.
, но исключит.
и не сойдет в подкаталоги.
. Так что это стандартный эквивалент GNU-mindepth 1 -maxdepth 1
.Я попробовал это в каталоге с 2259 записями и использовал
time
команду.Вывод
time for f in *; do echo "$f"; done
(минус файлы!):Вывод
time find * -prune | while read f; do echo "$f"; done
(минус файлы!):Я запускал каждую команду несколько раз, чтобы исключить ошибки в кеше. Это говорит о том, что хранить его в
bash
(потому что я в ...) быстрее, чем использоватьfind
и передать вывод (вbash
)Просто для полноты я отбросил канал
find
, поскольку в вашем примере он полностью избыточен. Выход всегоfind * -prune
:Также
time echo *
(вывод не разделен переводом строки, увы):На данный момент, я подозреваю, причина кроется в
echo *
том, что он не выводит так много новых строк, поэтому вывод не прокручивается так сильно. Давайте проверим ...выходы:
в то время как
time find * -prune > /dev/null
дает:и
time for f in *; do echo "$f"; done > /dev/null
дает:и наконец
time echo * > /dev/null
:Некоторые из вариаций могут быть объяснены случайными факторами, но это кажется очевидным:
for f in *; do ...
это медленнее, чемfind * -prune
само по себе, но для конструкций выше, включая трубы, быстрее.Кроме того, оба подхода, по-видимому, отлично справляются с именами с пробелами.
РЕДАКТИРОВАТЬ:
Время для
find . -maxdepth 1 > /dev/null
противfind * -prune > /dev/null
:time find . -maxdepth 1 > /dev/null
:find * -prune > /dev/null
:Итак, дополнительный вывод:
find * -prune
медленнее, чемfind . -maxdepth 1
- в первом случае оболочка обрабатывает глоб, а затем строит (большую) командную строку дляfind
. NB:find . -prune
возвращается просто.
.Больше тестов
time find . -maxdepth 1 -exec echo {} \; >/dev/null
:Вывод:
источник
find * -prune | while read f; do echo "$f"; done
имеет избыточный канал - все, что делает канал, этоfind
выводит именно то, что выводит сам по себе. Без канала это было бы просто.find * -prune
Канал только избыточен, потому что вещь на другой стороне канала просто копирует stdin в stdout (по большей части). Это дорогое бездействие. Если вы хотите что-то сделать с выводом find, кроме как просто выплюнуть его обратно, это другое дело.*
. Как BitsOfNix сказал: я все еще настоятельно рекомендуем не использовать*
и.
дляfind
вместо.find . -prune
что быстрее, потому чтоfind
будет читать дословно записи каталога, в то время как оболочка будет делать то же самое, потенциально сопоставляя с глобусом (может оптимизировать для*
), а затем создавая большую командную строку дляfind
.find . -prune
печатает только.
в моей системе. Это почти не работает вообще. Это совсем не то,find * -prune
что показывает все имена в текущем каталоге. Голыеread f
будут искажать имена файлов с начальными пробелами.Я бы определенно пошел с поиском, хотя я бы изменил вашу находку на это
Производительность мудрая,
find
намного быстрее, в зависимости от ваших потребностей, конечно. То, что у вас сейчасfor
есть, будет отображать только файлы / каталоги в текущем каталоге, но не содержимое каталогов. Если вы используете find, он также покажет содержимое подкаталогов.Я говорю находкой лучше , так как с вашим должны быть расширены первым , и я боюсь , что если у вас есть каталог с огромным количеством файлов , которые он может дать ошибку список аргументов слишком долго . То же самое касается
for
*
find *
Например, в одной из систем, которые я сейчас использую, есть пара каталогов с более чем 2 миллионами файлов (<100k каждая):
источник
-prune
чтобы сделать два примера более похожими. и я предпочитаю трубу с помощью while, чтобы было проще применять больше команд в циклеэто бесполезное использование
find
- «То, что вы говорите, эффективно» для каждого файла в каталоге (*
), не находите никаких файлов. Кроме того, это небезопасно по нескольким причинам:-r
опцииread
. Это не проблема сfor
циклом.for
циклом.Обработка любого имени файла с
find
это трудно , так что вы должны использоватьfor
опцию цикла всякий раз , когда это возможно только по этой причине. Кроме того, запуск такой внешней программы, какfind
правило, будет выполняться медленнее, чем выполнение внутренней команды циклаfor
.источник
find
's',-print0
ниxargs
'' '-0
не совместимы с POSIX, и вы не можете помещать произвольные командыsh -c ' ... '
(одинарные кавычки не могут быть экранированы внутри одинарных кавычек), так что это не так просто.Но мы лохи за вопросы производительности! Этот запрос на эксперимент делает по крайней мере два предположения, которые делают его ужасно действительным.
A. Предположим, что они находят одинаковые файлы ...
Ну, они будут находить одни и те же файлы , во - первых, потому что они оба Перебор же Glob, а именно
*
. Ноfind * -prune | while read f
страдает от нескольких недостатков, которые делают вполне возможным, он не найдет все файлы, которые вы ожидаете:find
реализаций делают, но тем не менее, вы не должны полагаться на это.find *
может сломаться при удареARG_MAX
.for f in *
не будет, потому чтоARG_MAX
относитсяexec
, а не встроенные.while read f
может порваться с именами файлов, начинающимися и заканчивающимися пробелами, которые будут удалены. Вы можете преодолеть это сwhile read
параметра по умолчаниюREPLY
, но это все равно не поможет вам, когда дело доходит до имен файлов с символами новой строки.B.
echo
. Никто не собирается делать это просто, чтобы повторить имя файла. Если вы хотите этого, просто выполните одно из следующих действий:Канал к
while
циклу здесь создает неявную подоболочку, которая закрывается, когда цикл заканчивается, что для некоторых может быть неинтуитивно.Чтобы ответить на вопрос, вот результаты в моем каталоге, в котором 184 файла и каталоги.
источник
$ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20811 pts/1 R+ 0:00 grep bash $ while true; do while true; do while true; do while true; do while true; do sleep 100; done; done; done; done; done ^Z [1]+ Stopped sleep 100 $ bg [1]+ sleep 100 & $ ps ax | grep bash 20784 pts/1 Ss 0:00 -bash 20924 pts/1 S+ 0:00 grep bash
find *
не будет работать правильно, если*
создает токены, которые выглядят как предикаты, а не пути.Вы не можете использовать обычный
--
аргумент, чтобы исправить это, потому что--
указывает конец параметров, а параметры поиска идут перед путями.Чтобы исправить эту проблему, вы можете использовать
find ./*
вместо этого. Но тогда он не производит точно такие же строки, какfor x in *
.Обратите внимание, что на
find ./* -prune | while read f ..
самом деле не использует функцию сканированияfind
. Это глобальный синтаксис,./*
который фактически пересекает каталог и генерирует имена. Тогдаfind
программа должна будет выполнить хотя быstat
проверку каждого из этих имен. У вас есть накладные расходы, связанные с запуском программы и доступом к этим файлам, а затем вводом-выводом для чтения ее вывода.Трудно представить, как это может быть чем-то менее эффективным, чем
for x in ./* ...
.источник
Начнем
for
с того, что ключевое слово shell, встроенное в Bash,find
является отдельным исполняемым файлом.for
Цикл будет найти только файлы из globstar характера , когда она расширяется, она не будет рекурсией в любые каталоги , которые он находит.Находить с другой стороны также будет предоставлен список, расширенный globstar, но он рекурсивно найдет все файлы и каталоги под этим расширенным списком и направит каждый из них к
while
цикл.Оба этих подхода могут считаться опасными в том смысле, что они не обрабатывают пути или имена файлов, содержащие пробелы.
Это все, что я могу придумать, чтобы прокомментировать эти 2 подхода.
источник
Если все файлы, возвращаемые функцией find, могут быть обработаны с помощью одной команды (очевидно, неприменимо к вашему эхо-примеру выше), вы можете использовать xargs:
источник
В течение многих лет я использовал это: -
искать определенные файлы (например, * .txt), которые содержат шаблон, который может искать grep, и направлять его в большее, чтобы он не скроллировал с экрана. Иногда я использую канал >>, чтобы записать результаты в другой файл, который я смогу просмотреть позже.
Вот пример результата:
источник