Використання циклу типу
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, є або безпосередньо використовувати -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, або пошуку{}
Зауважте, що у більшості / багатьох ситуаціях вам не потрібно встановлювати та скидати IFS; просто використовуйте IFS локально, як показано в прикладах нижче. Усі троє справляються з пробілом. Також вам не потрібна "стандартна" структура циклу, оскільки find's \; - це ефективно цикл; просто введіть свою логіку циклу в функцію 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//'
'знайти also allows you to pass multiple filenames as args to you script ..(if it suits your need: use+ instead\; `)
find -print0іxargs -0.