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

12

Используя цикл, такой как

for i in `find . -name \*.txt` 

сломается, если в некоторых именах файлов есть пробелы.

Какую технику я могу использовать, чтобы избежать этой проблемы?

Скотт К Уилсон
источник
1
Обратите внимание, что в файлах также могут быть новые строки в имени файла. Вот почему есть find -print0и xargs -0.
Даниэль Бек

Ответы:

12

В идеале вы вообще так не делаете, потому что синтаксический разбор имен файлов в скрипте оболочки всегда затруднителен (исправьте это для пробелов, у вас все равно будут проблемы с другими встроенными символами, в частности с новой строкой). Это даже указано как первая запись на странице BashPitfalls.

Тем не менее, есть способ почти сделать то, что вы хотите:

oIFS=$IFS
IFS=$'\n'

find . -name '*.txt' | while read -r i; do
  # use "$i" with whatever you're doing
done

IFS=$oIFS

Не забывайте также цитировать $iпри его использовании, чтобы потом не интерпретировать пробелы. Также не забывайте $IFSвозвращаться после его использования, потому что если вы этого не сделаете, это приведет к ошибкам позже.

К этому действительно прикреплено еще одно предупреждение: то, что происходит внутри whileцикла, может происходить в подоболочке, в зависимости от используемой вами оболочки, поэтому настройки переменных могут не сохраняться. В forверсии петли позволяет избегать этого , но по цене , которая, даже если применить $IFSрешение вопросов , остерегайтесь с пробелами, вы затем попасть в беду , если findвозвращается слишком много файлов.

В какой-то момент правильным решением для всего этого становится выполнение этого на языке, таком как Perl или Python вместо оболочки.

geekosaur
источник
1
Мне нравится идея просто использовать Python, чтобы избежать всего этого.
Скотт С. Уилсон
12

Используйте find -print0и передайте ее xargs -0или напишите свою собственную маленькую C-программу и передайте ее вашей маленькой C-программе. Это то, для чего -print0и -0были придуманы.

Сценарии оболочки - не лучший способ обработки имен файлов с пробелами в них: вы можете сделать это, но это становится неуклюжим.

DW
источник
Работает на моей машине ^ ТМ!
17
2

Вы можете установить «внутренний разделитель полей» ( IFS) на что-то другое, чем пространство для разделения аргументов цикла, например

ORIGIFS=${IFS}
NL='
'
IFS=${NL}
for i in $(find . -name '*.txt'); do
    IFS=${ORIGIFS}
    #do stuff
done
IFS=${ORIGIFS}

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

Другой метод, в зависимости от того, что вы хотите сделать с выходным сигналом find, является либо непосредственно использовать -execс findкомандой, или использования -print0и трубы его в xargs -0. В первом случае findзаботится о экранировании имени файла. В этом -print0случае findвыводит вывод с нулевым разделителем, а затем xargsразбивает на него. Поскольку ни одно имя файла не может содержать этот символ (что я знаю), это также всегда безопасно. Это в основном полезно в простых случаях; и обычно не является хорошей заменой для полного forцикла.

Даниэль Андерссон
источник
1

Использование find -print0сxargs -0

Использование в find -print0сочетании с xargs -0полностью устойчиво к допустимым именам файлов и является одним из наиболее расширяемых доступных методов. Например, допустим, что вы хотите получить список всех файлов PDF в текущем каталоге. Вы могли бы написать

$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 echo

Он найдет каждый PDF (через -iname '*.pdf') в текущем каталоге ( .) и любом подкаталоге и передаст каждый из них в качестве аргумента echoкоманде. Поскольку мы указали -n 1опцию, xargsбудет передавать только один аргумент за раз echo. Если бы мы пропустили эту опцию, xargsпередали бы как можно больше echo. (Вы можете echo short input | xargs --show-limitsувидеть, сколько байтов разрешено в командной строке.)

Что именно делает xargs?

Мы можем ясно увидеть эффект, который он xargsоказывает на его ввод - и, -nв частности, эффект - используя скрипт, который более точно повторяет его аргументы echo.

$ cat > echoArgs.sh <<'EOF'
#!/bin/bash
echo "Number of arguments: $#"

[[ $# -eq 0 ]] && exit

for i in $(seq 1 $#); do
    echo "Arg $i: <$1>"
    shift
done
EOF

$ find . -iname '*.pdf' -print0 | xargs -0 ./echoArgs.sh
$ find . -iname '*.pdf' -print0 | xargs -0 -n 1 ./echoArgs.sh

Обратите внимание, что он отлично обрабатывает пробелы и переводы строк,

$ touch 'A space-age
new line of vending machines.pdf'
$ find . -iname '*space*' -print0 | xargs -0 -n 1 ./echoArgs.sh

что было бы особенно проблематичным со следующим общим решением:

chmod +x ./echoArgs.sh
for file in $(ls *spacey*); do
  ./echoArgs.sh "$file"
done
Примечания
jpaugh
источник
1

Я не согласен с bashрасшифровщиками, потому что bash, наряду с набором инструментов * nix, весьма искусен в обработке файлов (включая те, чьи имена имеют встроенный пробел).

На самом деле, findдает вам точный контроль над выбором файлов для обработки ... Что касается bash, вам действительно нужно только осознать, что вы должны создавать свои строки bash words; как правило, с помощью «двойных кавычек», или другого механизма, такого как IFS, или find{}

Обратите внимание, что в большинстве / многих ситуациях вам не нужно устанавливать и сбрасывать IFS; просто используйте IFS локально, как показано в примерах ниже. Все три отлично справляются с пробелами. Также вам не нужна «стандартная» структура цикла, потому что find - \; это фактически цикл; просто поместите свою логику цикла в функцию bash (если вы не вызываете стандартный инструмент).

IFS=$'\n' find ~/ -name '*.txt' -exec  function-or-util {} \;  

И еще два примера

IFS=$'\n' find ~/ -name '*.txt' -exec  printf 'Hello %s\n' {} \;  
IFS=$'\n' find ~/ -name '*.txt' -exec  echo {} \+ |sed 's/home//'  

'find also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)

Peter.O
источник
1
Существует некоторая обоснованность для обеих точек зрения. Когда я работал только с моими собственными файлами, я просто использовал find и не беспокоился об этом, потому что в моих файлах нет пробелов (или возврат каретки!) В их именах. Но когда вы начинаете работать с файлами других людей, вы должны использовать более надежные методы.
Скотт С. Уилсон