Как использовать find, когда имя файла содержит пробелы?

Вопрос или проблема

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

Допустим, у меня есть файл с именем.

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, этот процесс можно значительно упростить. Выбор конкретного метода зависит от ваших потребностей и контекста использования.

Оцените материал
Добавить комментарий

Капча загружается...