x=$(find . -name "*.txt")
echo $x
если я запускаю приведенный выше фрагмент кода в оболочке Bash, то получаю строку, содержащую несколько имен файлов, разделенных пробелом, а не список.
Конечно, я могу разделить их пустыми, чтобы получить список, но я уверен, что есть лучший способ сделать это.
Итак, каков наилучший способ просмотреть результаты find
команды?
x=( $(find . -name "*.txt") ); echo "${x[@]}"
Тогда ты сможешь проходитьfor item in "${x[@]}"; { echo "$item"; }
Ответы:
TL; DR: Если вы просто здесь для наиболее правильного ответа, вы, вероятно, хотите, чтобы мои личные предпочтения
find . -name '*.txt' -exec process {} \;
(см. В нижней части этого поста). Если у вас есть время, прочитайте остальные, чтобы увидеть несколько разных способов и проблем с большинством из них.Полный ответ:
Лучший способ зависит от того, что вы хотите сделать, но вот несколько вариантов. Пока ни один файл или папка в поддереве не имеет пробела в имени, вы можете просто зацикливать файлы:
Маргинально лучше вырежьте временную переменную
x
:Это гораздо лучше Glob , когда вы можете. Безопасный пробел, для файлов в текущем каталоге:
Включив эту
globstar
опцию, вы можете поместить все подходящие файлы в этот каталог и все его подкаталоги:В некоторых случаях, например, если имена файлов уже есть в файле, вам может потребоваться использовать
read
:read
можно безопасно использовать в сочетании сfind
установкой соответствующего разделителя:Для более сложных поисков вы, вероятно, захотите использовать
find
, либо с его-exec
опцией, либо с-print0 | xargs -0
:find
может также перейти в каталог каждого файла перед запуском команды с помощью-execdir
вместо-exec
, и может быть сделан интерактивным (запрос перед запуском команды для каждого файла) с использованием-ok
вместо-exec
(или-okdir
вместо-execdir
).*: Технически оба
find
иxargs
(по умолчанию) будут запускать команду с таким количеством аргументов, сколько они могут уместиться в командной строке, столько раз, сколько требуется, чтобы пройти через все файлы. На практике, если у вас нет очень большого количества файлов, это не будет иметь значения, и если вы превысите длину, но нуждаетесь в них в одной командной строке,вы SOLнайдете другой способ.источник
done < filename
и следующим с трубой STDIN не может быть использована больше (→ не более интерактивного материала внутри цикла), но в тех случаях , когда это необходимо, можно использовать3<
вместо<
и добавить<&3
или-u3
кread
часть, в основном с помощью отдельного дескриптора файла. Кроме того, я считаю, чтоread -d ''
это то же самое,read -d $'\0'
но я не могу найти официальную документацию по этому вопросу прямо сейчас.-exec process {} \;
и я думаю, что это совсем другой вопрос - что это значит и как мне манипулировать этим? Где хороший Q / A или док. в теме?man find
). В этом случае-exec
приказываетfind
выполнить следующую команду, оканчивающуюся;
(или+
), в которой{}
будет заменено имя файла, который он обрабатывает (или, если+
используется, все файлы, которые перешли в это состояние).-d ''
лучше чем-d $'\0'
. Последнее не только длиннее, но и предполагает, что вы можете передавать аргументы, содержащие нулевые байты, но не можете. Первый нулевой байт отмечает конец строки. В Баше$'a\0bc'
такого же , какa
и$'\0'
то же,$'\0abc'
или просто пустая строка''
.help read
заявляет, что « первый символ разделителя используется для завершения ввода », поэтому использование''
в качестве разделителя является чем-то вроде хака. Первый символ в пустой строке - это нулевой байт, который всегда отмечает конец строки (даже если вы не записали это явно).Что бы вы ни делали, не используйте
for
цикл :Три причины:
find
должен завершиться.for
цикл возвращает 40 КБ текста. Эти последние 8 КБ будут сброшены с вашегоfor
цикла, и вы никогда об этом не узнаете.Всегда используйте
while read
конструкцию:Цикл будет выполняться во время
find
выполнения команды. Кроме того, эта команда будет работать, даже если имя файла возвращается с пробелом в нем. И вы не переполните свой буфер командной строки.В
-print0
качестве разделителя файлов будет использоваться NULL вместо новой строки, а-d $'\0'
при чтении будет использоваться NULL в качестве разделителя.источник
-exec
Вместо этого используйте find .-exec
является самым безопасным, поскольку он вообще не использует оболочку. Однако NL в именах файлов встречается довольно редко. Пробелы в именах файлов довольно распространены. Суть в том, чтобы не использоватьfor
цикл, рекомендованный многими авторами.-r
опциюread
:-r raw input - disables interpretion of backslash escapes and line-continuation in the read data
Примечание: этот метод и (второй) метод, показанный bmargulies, безопасны для использования с пробелами в именах файлов / папок.
Для того, чтобы также иметь - несколько экзотический - случай новых строк в именах файлов / папок, вам придется прибегнуть к
-exec
предикату,find
например так:{}
Является заполнителем для находки и\;
используются для завершения-exec
предиката.И для полноты позвольте мне добавить еще один вариант - вы должны любить * nix способы за их универсальность:
\0
Насколько мне известно, это будет разделять напечатанные элементы символом, который не разрешен ни в одной из файловых систем в именах файлов или папок, и поэтому должен охватывать все основы.xargs
подбирает их один за другим, затем ...источник
find -print0
иxargs -0
оба расширения GNU и не портативные (POSIX) аргументы. Невероятно полезный на тех системах, которые имеют их, хотя!read -r
могли бы исправить), или именами файлов, заканчивающимися пробелами (которыеIFS= read
могли бы исправить). Следовательно BashFAQ # 1 предлагаетwhile IFS= read -r filename; do ...
exit
, не будет работать должным образом, а переменные, установленные в теле цикла, не будут доступны после цикла.Имена файлов могут включать пробелы и даже управляющие символы. Пробелы являются (по умолчанию) разделителями для расширения оболочки в bash и в результате этого
x=$(find . -name "*.txt")
из вопроса вообще не рекомендуется. Если find получает имя файла с пробелами, например,"the file.txt"
вы получите 2 отдельные строки для обработки, если вы обрабатываетеx
в цикле. Вы можете улучшить это, изменив разделитель (IFS
переменную bash ), например, на\r\n
, но имена файлов могут включать управляющие символы - так что это не (полностью) безопасный метод.С моей точки зрения, есть 2 рекомендуемых (и безопасных) шаблона для обработки файлов:
1. Используйте для расширения цикла и имени файла:
2. Используйте поиск-чтение-и подстановка процесса
замечания
по шаблону 1:
nullglob
может быть использован, чтобы избежать этой дополнительной строки.failglob
опция оболочки и совпадений не найдено, выводится сообщение об ошибке и команда не выполняется». (из руководства Bash выше)globstar
: «Если установлено, шаблон« ** », используемый в контексте расширения имени файла, будет соответствовать всем файлам и нулю или более каталогов и подкаталогов. Если за шаблоном следует символ« / », совпадают только каталоги и подкаталоги». см. руководство по Bash, Shopt Builtinextglob
,nocaseglob
,dotglob
и переменная оболочкиGLOBIGNORE
по схеме 2:
имена файлов могут содержать пробелы, табуляции, пробелы, переводы строк, ... для безопасной обработки имен файлов
find
с-print0
использованием: имя файла печатается со всеми управляющими символами и заканчивается NUL. см. также Gnu Findutils Manpage, Небезопасная обработка имени файла , безопасная обработка имени файла , необычные символы в именах файлов . См. Дэвид А. Уилер ниже для подробного обсуждения этой темы.Есть несколько возможных шаблонов для обработки результатов поиска в цикле while. Другие (Кевин, Дэвид У.) показали, как это сделать, используя каналы:
Когда вы попробуете этот кусок кода, вы увидите, что он не работает:files_found
всегда "true" и код всегда будет отображать "файлы не найдены". Причина в том, что каждая команда конвейера выполняется в отдельной подоболочке, поэтому измененная переменная внутри цикла (отдельная подоболочка) не изменяет переменную в основном сценарии оболочки. Вот почему я рекомендую использовать процесс подстановки как «лучший», более полезный, более общий шаблон.Смотрите, я устанавливаю переменные в цикле, который находится в конвейере. Почему они исчезают ... (из Greg's Bash FAQ) для подробного обсуждения этой темы.
Дополнительные ссылки и источники:
Руководство по Gnu Bash, сопоставление с образцом
Имена файлов и пути в Shell: как это сделать правильно, Дэвид А. Уилер
Почему вы не читаете строки с "for", вики Грега
Почему вы не должны анализировать вывод ls (1), вики Грега
Руководство по Gnu Bash, процесс замены
источник
(Обновлено, чтобы включить отличное улучшение скорости @ Socowi)
С любым,
$SHELL
который поддерживает это (dash / zsh / bash ...):Готово.
Оригинальный ответ (короче, но медленнее):
источник
\;
вы можете использовать,+
чтобы передать как можно больше файлов в один файлexec
. Затем используйте"$@"
сценарий оболочки для обработки всех этих параметров.$@
он опускается, так как обычно это имя сценария. Нам просто нужно добавитьdummy
между ними,'
и{}
поэтому он может заменить имя скрипта, гарантируя, что все совпадения будут обработаны циклом.OTHERVAR=foo find . -na.....
должен позволить вам получить доступ$OTHERVAR
из этой недавно созданной оболочки.источник
for x in $(find ...)
сломается для любого имени файла с пробелами в нем. То же самое,find ... | xargs
если вы не используете-print0
и-0
find . -name "*.txt -exec process_one {} ";"
вместо этого. Почему мы должны использовать xargs для сбора результатов, которые у нас уже есть?process_one
есть. Если это заполнитель для фактической команды , убедитесь, что это сработает (если вы исправите опечатку и добавите закрывающие кавычки после"*.txt
). Но еслиprocess_one
это пользовательская функция, ваш код не будет работать.Вы можете сохранить свои
find
выходные данные в массиве, если вы хотите использовать выходные данные позже как:Теперь, чтобы распечатать каждый элемент в новой строке, вы можете либо использовать
for
итерации цикла для всех элементов массива, либо вы можете использовать оператор printf.или
Вы также можете использовать:
Это напечатает каждое имя файла в новой строке
Чтобы распечатать
find
выходные данные только в виде списка, вы можете использовать одно из следующих:или
Это удалит сообщения об ошибках и даст только имя файла в качестве вывода в новой строке.
Если вы хотите что-то сделать с именами файлов, хорошо хранить их в массиве, иначе нет необходимости использовать это пространство, и вы можете напрямую распечатать вывод
find
.источник
Если вы можете предположить, что имена файлов не содержат символов новой строки, вы можете прочитать вывод
find
в массив Bash, используя следующую команду:Примечание:
-t
вызываетreadarray
лишить новых строк.readarray
находится в трубе, следовательно, процесс подстановки.readarray
доступен с Bash 4.Bash 4.4 и выше также поддерживает
-d
параметр для указания разделителя. Использование нулевого символа вместо новой строки для разделения имен файлов работает и в том редком случае, когда имена файлов содержат символы новой строки:readarray
также может быть вызван какmapfile
с теми же параметрами.Ссылка: https://mywiki.wooledge.org/BashFAQ/005#Loading_lines_from_a_file_or_stream
источник
exit
при циклическом просмотре результатовreadarray -d '' x < <(find . -name '*.txt' -print0)
Мне нравится использовать find, которая сначала назначается переменной, а IFS переключается на новую строку следующим образом:
На тот случай, если вы захотите повторить больше действий с одним и тем же набором данных, и обнаружите, что на вашем сервере выполняется очень медленно (высокая загрузка I / 0)
источник
Вы можете поместить имена файлов, возвращенные
find
в массив, как это:Теперь вы можете просто перебирать массив, чтобы получить доступ к отдельным элементам и делать с ними все, что захотите.
Примечание: это безопасное пространство.
источник
mapfile -t -d '' array < <(find ...)
. УстановкаIFS
не нужна дляmapfile
.основываясь на других ответах и комментариях @phk, используя fd # 3:
(который по-прежнему позволяет использовать stdin внутри цикла)
источник
find <path> -xdev -type f -name *.txt -exec ls -l {} \;
Это перечислит файлы и даст подробную информацию об атрибутах.
источник
Как насчет того, чтобы использовать grep вместо find?
Теперь вы можете прочитать этот файл, а имена файлов представлены в виде списка.
источник