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

17

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

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

foo bar

Как я могу findвернуть правильное имя?

Очевидно, я хочу:

foo\ bar

или:

"foo bar"

РЕДАКТИРОВАТЬ : Я не хочу проходить xargs, я хочу получить правильно отформатированную строку, findчтобы я мог передать строку имен файлов непосредственно в другую программу.

ошибка
источник
5
к чему ты клонишь? Вы знаете о -execфлаге с find? Вы могли бы потенциально устранить эту ошибку и сделать свою команду более эффективной, выполнив -execвместо передачи ее другим командам. Просто мои $ .02
h3rrmiller
6
@bug: findформатирует имена файлов просто отлично; они написаны по одному имени в строке. (Конечно, это неоднозначно, если имя файла содержит символ новой строки.) Таким образом, проблема заключается в том, что принимающая сторона «задыхается», когда получает пробел, а это означает, что вы должны сказать нам, что является получающей стороной, если вы хотите значимого ответа ,
Ричи
2
То, что вы называете «правильно отформатированным», действительно «сбрасывается для использования оболочкой». Большинство утилит, которые могут читать кучу имен файлов, будут задыхаться от имени, экранированного оболочкой, но на самом деле имеет смысл (скажем) findпредложить вариант вывода имен файлов в формате, подходящем для оболочки. В целом, однако, расширение -print0GNU findпрекрасно работает для многих других сценариев (тоже), и вы должны научиться использовать его в любом случае.
tripleee
2
@bug: Кстати, ls $(command...)не прокручивает список stdin. Он помещает вывод $(command...)непосредственно в командную строку. В этом случае это оболочка, которая читает из c, и она будет использовать текущее значение, $IFSчтобы решить, как разбить вывод на слово. В общем, вам лучше использовать xargs. Вы не заметите удар по производительности.
Ричи
2
find -printf '"%p"\n'добавит двойные кавычки вокруг каждого найденного имени, но не будет правильно заключать в кавычки двойные кавычки в имени файла. Если в именах ваших файлов нет встроенных двойных кавычек, вы можете проигнорировать проблему: или передать через sed 's/"/&&/g;s/^""/"/;s/""$/"/'. Если ваши имена файлов в конечном итоге обрабатываются оболочкой, вы, вероятно, должны использовать одинарные кавычки вместо двойных кавычек (в противном случае sweet$HOMEэто станет чем-то вроде sheet/home/you). И это все еще не очень устойчиво к именам файлов с символами новой строки в них. Как вы хотите справиться с этим?
tripleee

Ответы:

18

POSIXLY:

find . -type f -exec sh -c '
  for f do
    : command "$f"
  done
' sh {} +

С findопорами -print0и xargsопорами -0:

find . -type f -print0 | xargs -0 <command>

-0 option указывает xargs использовать символ ASCII NUL вместо пробела для окончания (разделения) имен файлов.

Пример:

find . -maxdepth 1 -type f -print0 | xargs -0 ls -l
cuonglm
источник
Не работает Когда я бегу, ls $(find . -maxdepth 1 -type f -print0 | xargs -0)я получаю ls: cannot access ./foo: No such file or directory ls: cannot access bar: No such file or directory
ошибка
1
Вы пробовали это так, как Gnouc на самом деле написал это? Если вы настаиваете на том, чтобы делать это по-своему, попробуйте $(..)"$(..)"
заключить
3
@bug: ваша команда неверна. Попробуй в точности, я читаю и читай man-страницу findи xargs.
cuonglm
Я вижу, тогда я снова хочу получить отформатированную строку, которую я мог бы передать напрямую.
ошибка
1
@bug: Просто используйте xargs -0 <ваша программа>
cuonglm
10

С помощью -print0 - это один из вариантов, но не все программы поддерживают использование потоков данных, разделенных нулем, поэтому вам придется использовать xargsэтот -0параметр для некоторых вещей, как отмечается в ответе Gnouc.

Альтернативой было бы использовать find«с -execили -execdirопций. Первое из следующего будет кормить имена файловsomecommand одному за раз, а второе - в список файлов:

find . -type f -exec somecommand '{}' \;
find . -type f -exec somecommand '{}' +

Вы можете обнаружить, что во многих случаях вам лучше использовать шаровидность. Если у вас есть современная оболочка (bash 4+, zsh, ksh), вы можете получить рекурсивную глобализацию с помощью globstar(** ). В bash вы должны установить это:

shopt -s globstar
somecommand ./**/*.txt ## feeds all *.txt files to somecommand, recursively

У меня есть строка с надписью shopt -s globstar extglobв моем .bashrc, поэтому она всегда включена для меня (как и расширенные глобусы, которые также полезны).

Если вам не нужна рекурсивность, просто используйте ./*.txtвместо нее каждый * .txt в рабочем каталоге. findимеет некоторые очень полезные возможности детального поиска и является обязательным для десятков тысяч файлов (в этот момент вы столкнетесь с максимальным числом аргументов оболочки), но для повседневного использования это часто не требуется.

evilsoup
источник
Эй, @evilsoup, что {} делает в этом сценарии?
Ayusman
3

Лично я бы использовал действие -execfind для решения такого рода проблем. Или, если необходимо,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 {} +
RICi
источник
Я не думаю, что lsсценарий адекватно отражает сценарий использования ОП, но это всего лишь предположение, так как нам не было показано, чего он на самом деле пытается достичь. Это решение на самом деле работает очень хорошо; Я получаю вывод, который я (смутно) ожидал для всех забавных имен файлов, которые я пробовал, в том числеtouch "$(tr a-z '\001-\026' <<<'the quick brown fox jumped over the lazy dogs')"
tripleee
@triplee: я понятия не имею, что хочет сделать ОП. Единственным реальным преимуществом построения строки в кавычках для передачи evalявляется то, что вам еще не нужно ее передавать eval; Вы можете сохранить его в параметре и использовать позже, возможно, несколько раз с разными командами. Однако OP не дает никаких указаний на то, что это тот случай использования (и если бы это было так, возможно, было бы лучше поместить имена файлов в массив, хотя это тоже сложно.)
rici
0
find ./  | grep " "

получит вам файлы и каталоги, содержащие пробелы

find ./ -type f  | grep " " 

вы получите файлы с пробелами

find ./ -type d | grep " "

вы получите каталоги, содержащие пробелы

Каннан Кумарасами
источник
-2
    find . -type f -name \*\  | sed -e 's/ /<thisisspace>/g'
user283965
источник
Это интересный ответ, но это не ответ на этот вопрос.
Скотт