Як я розбираю висновок команди 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 замість оболонки.


1
Мені подобається ідея просто використовувати Python, щоб уникнути всього цього.
Скотт C Вілсон

12

Використовуйте find -print0та передайте програму xargs -0, або напишіть свою власну маленьку програму C та передайте її на свою маленьку програму C. Саме для цього -print0і -0були винайдені.

Сценарії оболонки - не найкращий спосіб обробляти назви файлів з пробілами в них: ви можете це зробити, але він стає незграбним.


Працює на моїй машині ^ ТМ!
mcandre

2

Ви можете встановити "внутрішній роздільник поля" ( 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циклу.


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
Примітки

1

Я не погоджуюся з 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\; `)


1
Існує деяка обгрунтованість обох точок зору. Коли я працював лише над власними файлами, я б просто використовував find і не хвилювався з цього приводу, тому що у моїх файлах немає пробілів (або повернення каретки!) У своїх назвах. Але коли ви починаєте працювати з файлами інших людей, вам доведеться використовувати більш надійні методи.
Скотт C Вілсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.