Используя цикл, такой как
for i in `find . -name \*.txt`
сломается, если в некоторых именах файлов есть пробелы.
Какую технику я могу использовать, чтобы избежать этой проблемы?
Используя цикл, такой как
for i in `find . -name \*.txt`
сломается, если в некоторых именах файлов есть пробелы.
Какую технику я могу использовать, чтобы избежать этой проблемы?
Ответы:
В идеале вы вообще так не делаете, потому что синтаксический разбор имен файлов в скрипте оболочки всегда затруднителен (исправьте это для пробелов, у вас все равно будут проблемы с другими встроенными символами, в частности с новой строкой). Это даже указано как первая запись на странице 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 вместо оболочки.
Используйте find -print0
и передайте ее xargs -0
или напишите свою собственную маленькую C-программу и передайте ее вашей маленькой C-программе. Это то, для чего -print0
и -0
были придуманы.
Сценарии оболочки - не лучший способ обработки имен файлов с пробелами в них: вы можете сделать это, но это становится неуклюжим.
Вы можете установить «внутренний разделитель полей» ( 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
цикла.
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
Примечания
Я не согласен с 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
\; `)
find -print0
иxargs -0
.