Вопрос или проблема
Я хочу передавать имена файлов другим программам, но все они зависают, когда в именах есть пробелы.
Допустим, у меня есть файл с именем.
foo bar
Как мне сделать так, чтобы find
возвращал правильное имя?
Очевидно, мне нужно:
foo\ bar
или:
"foo bar"
ИЗМЕНЕНИЕ: Я не хочу использовать xargs
, я хочу получить правильно отформатированную строку из find
, чтобы можно было передать строку имён файлов напрямую другой программе.
POSIXLY:
find . -type f -exec sh -c '
for f do
: command "$f"
done
' sh {} +
find
поддерживает -print0
, и xargs
поддерживает -0
:
find . -type f -print0 | xargs -0 <команда>
Опция -0
заставляет xargs
использовать ASCII NULL символы вместо пробелов, чтобы закончить (разделить) имена файлов.
Пример:
find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
Таким образом, если вы не хотите использовать xargs
(также, вероятно, и, например, parallel
), вывод find
можно читать и обрабатывать построчно следующим образом:
find . -type f | while read x; do
# сделать что-то с $x
done
Использование -print0
— это один из вариантов, но не все программы поддерживают использование потоков данных, разделяемых нулевыми байтами, поэтому вам, возможно, придется использовать xargs
с опцией -0
для некоторых задач, как заметил ответ Gnouc.
Альтернативой будет использование опций -exec
или -execdir
в find
. Первый из следующих примеров передаст имена файлов в somecommand
по одному, в то время как второй развернется в список файлов:
find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +
Вы можете обнаружить, что в большинстве случаев вам будет удобнее использовать глоббинг. Если у вас современная оболочка (bash 4+, zsh, ksh), вы можете использовать рекурсивный глоббинг с globstar
(**
). В bash это нужно установить:
shopt -s globstar
somecommand ./**/*.txt ## передает все *.txt файлы в somecommand, рекурсивно
У меня в .bashrc есть строка shopt -s globstar extglob
, так что это всегда включено для меня (и расширенные глобы, которые также полезны, тоже включены).
Если вам не нужен рекурсивный поиск, очевидно, просто используйте ./*.txt
вместо этого, чтобы использовать каждый *.txt в рабочем каталоге. find
имеет очень полезные возможности тонкой настройки поиска и незаменим для десятков тысяч файлов (на этом этапе вы столкнетесь с максимальным количеством аргументов в оболочке), но для повседневного использования он часто излишен.
find ./ | grep " "
получит вам файлы и директории, содержащие пробелы
find ./ -type f | grep " "
получит вам файлы, содержащие пробелы
find ./ -type d | grep " "
получит вам директории, содержащие пробелы
Лично я бы использовал действие -exec
в find, чтобы решить подобную проблему. Или, если необходимо, xargs
, который позволяет выполнять команды параллельно.
Тем не менее, есть способ заставить find
выдать список имён файлов, читаемый bash. Неудивительно, это использует -exec
и bash
, в частности, расширение для команды printf
:
find ... -exec bash -c 'printf "%q " "$@"' printf {} ';'
Тем не менее, хотя это правильно выведет соответствующим образом экранированные слова, не получится использовать с $(...)
, потому что $(...)
не интерпретирует кавычки или экранирование. (Результат $(...)
подвержен разделению на слова и расширению путей, если не окружен кавычками.) Поэтому следующее не сделает то, что вам нужно:
ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)
Что вам нужно сделать, так это:
eval "ls $(find ... -exec bash -c 'printf "%q " "$@"' printf {} +)"
(Обратите внимание, что я не пытался по-настоящему протестировать вышеуказанную конструкцию.)
Но тогда вы можете просто сделать:
find ... -exec ls {} +
Установив внутренний разделитель полей на новую строку, оболочка будет игнорировать пробелы:
IFS=$'\n' eval 'for i in `find . -type f -name "*"`;do echo $i;done'
Существует много различных ответов, в зависимости от того, как именно вы хотите использовать вывод, а также какие предположения вы делаете о том, какие необычные символы отсутствуют в именах файлов. Команда find не имеет опции для экранирования специальных символов, но если бы таковая существовала, ее выбор того, что экранировать, может не соответствовать точным потребностям вашей программы. Учитывая, что единственными недопустимыми символами в именах файлов являются “https://unix.stackexchange.com/” и NULL, существует множество крайних случаев.
В моем случае я хотел обрабатывать имена файлов как элементы в массиве в Bash, поэтому мне нужно было что-то вроде:
FILES=( $(find . -type f) )
Это не работает с пробелами (или табуляцией, как таковой). Это также убивает новые строки из команды find, что делает их бесполезными в качестве разделителей. Вы можете установить разделитель полей в Bash на что-то другое. В идеале вы бы установили его на нуль и использовали -print0 в find, но нуль не допускается в качестве разделителя полей в Bash. Мое решение – выбрать символ, который мы предполагаем отсутствует в любых именах файлов, например 0x01 (ctrl-a), и использовать его:
IFS=$'\x01'
FILES=( $(find . -type f | sed -e 's/$/\x01/') )
unset IFS
for F in "${FILES[@]}"; do
useful_command "$F"
done
Обратите внимание на необходимость сброса IFS, чтобы восстановить его до значения по умолчанию. Это не сработает с именами файлов, в которых есть новые строки, но должно сработать с большинством других имен файлов.
Если вы действительно параноидальны, вам нужно будет выполнить find, переданный в hexdump, выделить результаты, чтобы получить все шестнадцатеричные значения, и найти одно, которого нет в результатах. Затем используйте это значение. Я уверен, что у Johnny Drop Tables есть файлы с каждым шестнадцатеричным кодом в именах файлов. Если вы параноидальны, создайте имена файлов и директорий, используя все 253 допустимых символа, и протестируйте. Вероятно, единственные решения, которые бы прошли этот тест, это те, которые используют ‘find -print0’ в комбинации с xargs или индивидуальной программой на C.
Если вам просто нужно экранировать пробелы, вы можете сделать это:
find (...) | sed -e's/ /\\ /g' | whatever...
Но, насколько я знаю, только xargs требует такой обработки. Например:
find . -type f -name '* *' | sed -e's/ /\\ /g' | xargs ls -l
Это может сработать для вас. Он не экранирует ничего, кроме пробелов, но охватывает самый распространенный случай. Кавычки в именах файлов все еще могут быть проблемой.
Если вы собираетесь использовать имена файлов в сценарии, возможно, вам захочется объединить массивы BASH с find.
Что-то вроде этого:
readarray -d '' ALL_JSON_FILES < <(find "my_src_dir" -name "*.json" -print0)
for TEAM in "${ALL_JSON_FILES[@]}"
do
Вдохновлено вопросами на SO:
https://stackoverflow.com/a/54561526/671282\
https://stackoverflow.com/a/8880633/671282
Чтобы найти файл или директорию с одним или несколькими пробелами в именах, можно использовать один из (эквивалентных):
find . -name \*\ \*
find . -name '* *'
Рабочий пример тестирования:
# создайте некоторые директории и файлы, некоторые с пробелами, некоторые без
$ mkdir -p test/nospace/spa\ ce/leafdir test/nospace/nospace/leafdir
$ touch test/nospace/spa\ ce/leafdir/bork test/nospace/nospace/leafdir/bork test/nospace/file\ name
# найдите любые с помощью -name
$ find test -name '* *'
test/nospace/spa ce
test/nospace/file name
# найдите любые с помощью -path
$ find test -path '* *'
test/nospace/spa ce
test/nospace/spa ce/leafdir
test/nospace/spa ce/leafdir/bork
test/nospace/file name
# найдите директории с помощью -name
$ find test -type d -name '* *'
test/nospace/spa ce
# найдите файлы с помощью -name
$ find test -type f -name '* *'
test/nospace/file name
# найдите директории с помощью -path
$ find test -type d -path '* *'
test/nospace/spa ce
test/nospace/spa ce/leafdir
# найдите файлы с помощью -path
$ find test -type f -path '* *'
test/nospace/spa ce/leafdir/bork
test/nospace/file name
Программы, читающие этот вывод, могут интерпретировать пробелы как разделители, поэтому можно использовать -print0
find test -type f -path '* *' -print0 | xargs -0 ls -l
Меня довольно удивляет, что никто не упомянул просто использовать флаг -printf
из find
так:
find "$MY_DIR" -printf '"%p"\n'
Это обернет пути результатов в двойные кавычки.
Это даст что-то вроде:
"foo and bar"
"foo"
"caz with spaces here"
...
Я думаю, что этот подход проще, легче запоминается и не требует дополнительных трубок или других процессов.
Если %p
не то, что вам нужно, то у вас все еще есть другие директивы. Просто посмотрите справку man find
.
find . -type f -name \*\ | sed -e 's/ /<thisisspace>/g'
Ответ или решение
Когда вы работаете с командами в Unix/Linux и сталкиваетесь с файлами, в именах которых присутствуют пробелы, использование команды find
может вызывать определённые трудности, особенно когда вы хотите передать эти имена в другие программы. В данной статье мы рассмотрим несколько подходов, чтобы корректно обрабатывать имена файлов, содержащие пробелы.
1. Использование -print0
Одним из наиболее универсальных решений является использование опции -print0
в команде find
. Эта опция позволяет выводить имена файлов, разделённые нулевыми байтами (NULL), вместо пробелов, что делает возможным их корректную обработку вместе с утилитами, такими как xargs
.
Пример:
find . -type f -print0 | xargs -0 ls -l
В этом примере:
find . -type f -print0
находит все файлы в текущем каталоге и выводит их, разделяя нулями.xargs -0 ls -l
принимает выходные данные отfind
и передаёт их программеls -l
.
2. Пайпинг через while read
Если вы не хотите использовать xargs
, можно воспользоваться циклом while read
, который позволяет читать имена файлов по одному. Этот метод также обеспечивает корректное обслуживание пробелов.
Пример:
find . -type f | while IFS= read -r file; do
echo "$file" # Здесь вы можете заменить `echo` на вашу команду
done
3. Использование exec
Вы можете использовать встроенную функциональность find
для выполнения команды -exec
, что позволяет передавать имя файла непосредственно в команду. Например:
find . -type f -exec somecommand '{}' +
Этот способ обрабатывает файлы по одному или группами, передавая имена файлов в команду.
4. Форматирование через -printf
Команда find
поддерживает опцию -printf
, которая позволяет форматировать вывод.
Пример:
find . -type f -printf "\"%p\"\n"
Этот вариант добавляет кавычки вокруг каждого имени файла, что устраняет проблемы с пробелами, когда вы передаёте имена в другие команды.
5. Использование массива Bash
Если вы хотите сохранить имена файлов в массив Bash, вы можете использовать readarray
вместе с find
.
Пример:
readarray -d '' files < <(find . -type f -print0)
for file in "${files[@]}"; do
echo "$file" # Здесь вы можете заменить `echo` на вашу команду
done
6. Обработка через sed
для экранирования пробелов
Если вашей целью является экранирование только пробелов, можно использовать sed
для замены:
find . -type f | sed -e 's/ /\\ /g' | xargs ls -l
Это решение может быть полезно в ситуациях, когда только пробелы являются проблемой.
Заключение
Обработка имён файлов с пробелами в командной строке Unix/Linux может быть сложной задачей, однако, с помощью подходящих инструментов и техник, таких как -print0
, xargs
, -exec
, а также использование массивов в Bash, этот процесс можно значительно упростить. Выбор конкретного метода зависит от ваших потребностей и контекста использования.