Чому зациклювання на поганій практиці пошуку результатів пошуку?


170

Це питання надихає

Чому використання циклу оболонки для обробки тексту вважається поганою практикою?

Я бачу ці конструкції

for file in `find . -type f -name ...`; do smth with ${file}; done

і

for dir in $(find . -type d -name ...); do smth with ${dir}; done

використовуються тут майже щодня, навіть якщо деякі люди витрачають час, щоб коментувати ці пости, пояснюючи, чому подібних матеріалів слід уникати ...
Бачачи кількість таких публікацій (і той факт, що іноді ці коментарі просто ігноруються) Я подумав, що міг би також задати питання:

Чому зациклість на findвиході з поганої практики та який правильний спосіб запустити одну або кілька команд для кожного імені файлу / шляху, який повертається find?


12
Я думаю, що це на кшталт "Ніколи не аналізуйте вихід!" - Ви, звичайно, можете робити їх одноразово, але вони швидше злому, ніж якість виробництва. Або, загалом, напевно ніколи не бути догматичним.
Брюс Едігер


Це слід перетворити на канонічну відповідь
Заїд

6
Тому що сенс знаходження - це перекинути те, що воно знаходить.
OrangeDog

2
Один допоміжний момент - можливо, ви захочете надіслати вихід у файл, а потім обробити його згодом у сценарії. Таким чином, список файлів доступний для огляду, якщо вам потрібно налагодити сценарій.
користувач117529

Відповіді:


87

Проблема

for f in $(find .)

поєднує дві несумісні речі.

findдрукує список шляхів до файлів, розділених символами нового рядка. У той час як оператор split + glob, який викликається, коли ви залишаєте це без $(find .)котирування у контексті списку, розбиває його на символи $IFS(за замовчуванням включає новий рядок, але також пробіл та вкладку (і NUL в zsh)) і виконує глобулювання на кожному отриманому слові (за винятком в zsh) (і навіть дуже розширення в ksh93 або pdksh похідних!).

Навіть якщо ви це зробите:

IFS='
' # split on newline only
set -o noglob # disable glob (also disables brace expansion in pdksh
              # but not ksh93)
for f in $(find .) # invoke split+glob

Це все ще неправильно, оскільки символ нового рядка так само дійсний, як і будь-який шлях до файлу. Результат find -printпросто не може бути надійно оброблений надійним способом (за винятком випадків, коли використовується певна хитрість, як показано тут ).

Це також означає, що оболонці потрібно повністю зберігати вихід find, а потім розділити + glob його (що передбачає збереження цього виводу вдруге в пам'яті), перш ніж починати циклічно працювати над файлами.

Зауважте, що find . | xargs cmdє подібні проблеми (проблема, пробіли, новий рядок, одинарна цитата, подвійна цитата та зворотна косою риски (і деякі xargбайти реалізації не є частиною дійсних символів)

Більш правильні альтернативи

Єдиним способом використання forциклу на виході з програми findє використання zshта підтримка IFS=$'\0'та:

IFS=$'\0'
for f in $(find . -print0)

(Замінити -print0з -exec printf '%s\0' {} +для findреалізацій , які не підтримують нестандартні (але досить поширене явище в наші дні) -print0).

Тут правильним і портативним способом є використання -exec:

find . -exec something with {} \;

Або якщо somethingможна взяти більше одного аргументу:

find . -exec something with {} +

Якщо вам потрібен цей список файлів для обробки оболонки:

find . -exec sh -c '
  for file do
    something < "$file"
  done' find-sh {} +

(будьте обережні, це може почати більше одного sh).

У деяких системах ви можете використовувати:

find . -print0 | xargs -r0 something with

хоча це має невелику перевагу перед стандартним синтаксисом, а засоби something' stdinє або труба, або /dev/null.

Однією з причин, яку ви можете скористатися, може бути використання -Pпараметра GNU xargsдля паралельної обробки. stdinПитання також можна вирішити за GNU xargsз -aопцією з черепашками , що підтримують заміну процесу:

xargs -r0n 20 -P 4 -a <(find . -print0) something

наприклад, для запуску до 4 паралельних викликів з somethingкожного з 20 аргументів файлу.

За допомогою, zshабо bash, ще один спосіб перевести цикл на висновок: за find -print0допомогою:

while IFS= read -rd '' file <&3; do
  something "$file" 3<&-
done 3< <(find . -print0)

read -d '' читає записи з обмеженою кількістю NUL замість розділених на новий рядок.

bash-4.4і вище можуть також зберігати файли, повернуті find -print0в масив із:

readarray -td '' files < <(find . -print0)

zshЕквівалент (який має перевагу збереження find«и статус виходу):

files=(${(0)"$(find . -print0)"})

З zsh, ви можете перевести більшість findвиразів до комбінації рекурсивного глобулювання з глобальними класифікаторами. Наприклад, перегляд циклу find . -name '*.txt' -type f -mtime -1буде таким:

for file (./**/*.txt(ND.m-1)) cmd $file

Або

for file (**/*.txt(ND.m-1)) cmd -- $file

(будьте обережні, як і в --тому випадку **/*, шлях до файлів не починається ./, тому може починатися, -наприклад).

ksh93і bashврешті-решт додала підтримку **/(хоча і не більше прогресуючих форм рекурсивного глобулінгу), але все ж не глобальних кваліфікаторів, що робить використання **там дуже обмеженим. Також майте на увазі, що bashдо 4,3 слід виконувати посилання під час спадання дерева каталогів.

Як і для циклічного перегляду $(find .), це також означає збереження всього списку файлів у пам'яті 1 . Це може бути бажано, хоча в деяких випадках, коли ви не хочете, щоб ваші дії над файлами впливали на пошук файлів (наприклад, коли ви додаєте більше файлів, які можуть виявитись самі).

Інші міркування щодо надійності / безпеки

Умови гонки

Тепер, якщо ми говоримо про надійність, ми повинні згадати умови перегонів між часом find/ zshзнаходженням файлу і перевіряє, чи відповідає він критеріям та часом, коли він використовується ( гонка TOCTOU ).

Навіть під час спускання дерева каталогів потрібно переконатися, що не слідкувати за посиланнями та робити це без гонки TOCTOU. find(GNU findпринаймні) робить це, відкриваючи каталоги за openat()допомогою правильних O_NOFOLLOWпрапорів (де вони підтримуються) і тримаючи дескриптор файлу відкритим для кожної директорії, zsh/ bash/ kshне робіть цього. Таким чином, перед тим, як зловмисник зможе в потрібний час замінити каталог на символьне посилання, ви можете в кінцевому рахунку зійти з невірного каталогу.

Навіть якщо findфайл спускається належним чином, з -exec cmd {} \;тим більше, з тим -exec cmd {} +, як тільки він cmdбуде виконаний, наприклад, як, cmd ./foo/barабо cmd ./foo/bar ./foo/bar/baz, до моменту cmdвикористання ./foo/bar, атрибути barможуть більше не відповідати критеріям, відповідним find, але ще гірше, ./fooможливо, були замінюється символьним посиланням на якесь інше місце (і вікно гонки зростає набагато більше, -exec {} +де findчекає, щоб було достатньо файлів для виклику cmd).

Деякі findреалізації мають (ще нестандартний) -execdirпредикат для полегшення другої проблеми.

З:

find . -execdir cmd -- {} \;

find chdir()s у батьківський каталог файлу перед запуском cmd. Замість виклику cmd -- ./foo/barвін викликає дзвінки cmd -- ./bar( cmd -- barз деякими реалізаціями, звідси --), тому проблема зі ./fooзміною на символьну посилання уникається. Це робить використання команд на зразок rmбільш безпечним (він все ще може видалити інший файл, але не файл у іншому каталозі), але не команди, які можуть змінювати файли, якщо вони не були призначені для не слідування посиланнями.

-execdir cmd -- {} +іноді також працює, але з кількома реалізаціями, включаючи деякі версії GNU find, це еквівалентно -execdir cmd -- {} \;.

-execdir також має перевагу вирішити деякі проблеми, пов’язані із занадто глибокими деревами каталогів.

В:

find . -exec cmd {} \;

розмір заданого шляху cmdзростатиме з глибиною каталогу, в якому знаходиться файл. Якщо цей розмір стає більшим, ніж PATH_MAX(щось на зразок 4k в Linux), то будь-який системний виклик, який cmdробить на цьому шляху, вийде з ENAMETOOLONGпомилки.

З -execdir, ./передається лише ім'я файлу (можливо, з префіксом ) cmd. Самі імена файлів у більшості файлових систем мають набагато нижчу межу ( NAME_MAX) PATH_MAX, тому ENAMETOOLONGпомилка рідше трапляється.

Байти проти символів

Крім того, що часто не помічається при розгляді безпеки навколо findта в цілому поводження з іменами файлів в цілому, це факт, що в більшості Unix-подібних систем імена файлів - це послідовності байтів (будь-яке значення байта, але 0 в шляху файлу, і в більшості систем ( На основі ASCII ми поки ігноруємо рідкісні на основі EBCDIC) 0x2f - це роздільник шляху).

Додатки вирішують, чи хочуть вони вважати ці байти текстом. І вони, як правило, роблять, але, як правило, переклад з байтів в символи здійснюється на основі мови користувача, на основі середовища.

Це означає, що дане ім'я файлу може мати різне представлення тексту, залежно від мови. Наприклад, послідовність байтів 63 f4 74 e9 2e 74 78 74буде côté.txtдля програми, що інтерпретує це ім'я файлу в локалі, де набір символів ISO-8859-1, і cєtщ.txtв локалі, де замість цього діапазону IS0-8859-5.

Гірше. У місцевості, де набором є UTF-8 (норма в даний час), 63 f4 74 e9 2e 74 78 74 просто не вдалося відобразити на символи!

findє одним з таких додатків, який розглядає назви файлів як текст для їх -name/ -pathпредикатів (і більше, як-от -inameабо -regexз деякими реалізаціями).

Це означає, що, наприклад, з декількома findреалізаціями (включаючи GNU find).

find . -name '*.txt'

Не знайдемо наш 63 f4 74 e9 2e 74 78 74файл вище, коли він буде викликаний у локальній локалізації UTF-8, оскільки *(який відповідає 0 або більше символів , а не байтам) не міг відповідати цим не символам.

LC_ALL=C find... буде вирішувати проблему, оскільки локальний код C передбачає один байт на символ і (як правило) гарантує, що всі байтові значення відображаються в символі (хоч, можливо, і не визначені для деяких значень байта).

Тепер, коли мова заходить про перекидання цих імен файлів з оболонки, цей байт проти символу також може стати проблемою. У цьому плані ми зазвичай бачимо 4 основних типи снарядів:

  1. Ті, які досі не знають багатобайтів dash. Для них байт відображає персонажа. Наприклад, в UTF-8 - côtéце 4 символи, але 6 байт. У локалі, де UTF-8 - шафа, в

    find . -name '????' -exec dash -c '
      name=${1##*/}; echo "${#name}"' sh {} \;
    

    findуспішно знайде файли, ім'я яких складається з 4 символів, закодованих в UTF-8, але dashповідомить про довжину від 4 до 24.

  2. yash: протилежність. Це стосується лише персонажів . Весь вхід, який він бере, внутрішньо перекладається символами. Він створює найбільш послідовну оболонку, але це також означає, що вона не може впоратися з довільними послідовностями байтів (тими, що не перекладаються на дійсні символи). Навіть у мові C він не справляється зі значеннями байтів вище 0x7f.

    find . -exec yash -c 'echo "$1"' sh {} \;
    

    в локалі UTF-8 не вдасться, наприклад, на нашому ISO-8859-1, наприклад, côté.txtраніше.

  3. Такі, як bashабо zshде багатобайтову підтримку поступово додавали. Вони повернуться до розгляду байтів, які неможливо відобразити у символах, як ніби вони були символами. У них все ще є кілька помилок тут і там, особливо з менш поширеними багатобайтовими символами, такими як GBK або BIG5-HKSCS (ті, які є досить противними, оскільки багато їхніх багатобайтових символів містять байти в діапазоні 0-127 (як символи ASCII) ).

  4. Такі, як shFreeBSD (принаймні, 11) або mksh -o utf8-modeпідтримують багатобайтові, але лише для UTF-8.

Примітки

1 Для повноти ми можемо відзначити хакітний спосіб zshперебирати файли за допомогою рекурсивного глобулювання без збереження всього списку в пам’яті:

process() {
  something with $REPLY
  false
}
: **/*(ND.m-1+process)

+cmdце глобальний класифікатор, який викликає cmd(як правило, функцію) з поточного шляху до файлу $REPLY. Функція повертає true чи false, щоб вирішити, чи слід обрати файл (а також може змінити $REPLYабо повернути кілька файлів у $replyмасив). Тут ми виконуємо обробку в цій функції і повертаємо помилкову, щоб файл не був обраний.


Якщо zsh і bash доступні, вам може бути краще просто використовувати глобулінг та конструкції оболонок, а не намагатися спонукати findсебе до безпечної поведінки. Глоббінг за замовчуванням безпечний, тоді як пошук за замовчуванням небезпечний.
Кевін

@Kevin, див. Редагування.
Стефан Шазелас

182

Чому зациклість на findвиході є поганою практикою?

Проста відповідь:

Оскільки назви файлів можуть містити будь-який символ.

Тому немає символу для друку, який можна надійно використовувати для розмежування імен файлів.


Нові рядки часто використовуються (неправильно) для розмежування імен файлів, оскільки незвично включати символи нового рядка у назви файлів.

Однак якщо ви будуєте своє програмне забезпечення на основі довільних припущень, ви, в кращому випадку, просто не справляєтеся з незвичними випадками, а в гіршому випадку відкриваєтеся перед шкідливими подвигами, які передають контроль над вашою системою. Тож це питання надійності та безпеки.

Якщо ви можете писати програмне забезпечення двома різними способами, і один з них правильно обробляє крайові корпуси (незвичайні входи), а інший читається простіше, ви можете стверджувати, що відбувається компроміс. (Я б не хотів. Я вважаю за краще правильний код.)

Однак якщо правильну, надійну версію коду також легко читати, немає ніякого приводу для написання коду, який не вдається на кращих випадках. Це стосується findі необхідності запуску команди на кожен знайдений файл.


Давайте будемо детальніше: У системі UNIX або Linux назви файлів можуть містити будь-який символ, за винятком /(який використовується як роздільник компонентів контуру), і вони можуть містити нульовий байт.

Отже, нульовий байт є єдиним правильним способом розмежування імен файлів.


Оскільки GNU findвключає -print0первинний, який буде використовувати нульовий байт для обмеження імен файлів, які він друкує, GNU find може безпечно використовуватися з GNU xargsта його -0прапором (та -rпрапором) для обробки результатів find:

find ... -print0 | xargs -r0 ...

Однак немає вагомих причин використовувати цю форму, оскільки:

  1. Це додає залежність від GNU findutils, яка не повинна бути там, і
  2. findбуде розроблений , щоб мати можливість запускати команди на файли , які він знаходить.

Також GNU xargsвимагає -0і -r, тоді як FreeBSD xargsвимагає лише -0(і не має -rможливості), а деякі xargsвзагалі не підтримують -0. Тому краще просто дотримуватися POSIX-функцій find(див. Наступний розділ) та пропускати xargs.

Щодо точки 2 find- вміння виконувати команди на знайдені файли - я думаю, що Майк Лукідес сказав це найкраще:

findбізнес оцінює вирази, а не розміщення файлів. Так, findбезумовно, знаходить файли; але це насправді лише побічний ефект.

- електроінструменти Unix


POSIX вказане використання find

Який правильний спосіб запустити одну або кілька команд для результатів кожного з них find?

Щоб запустити одну команду для кожного знайденого файлу, використовуйте:

find dirname ... -exec somecommand {} \;

Для запуску декількох команд послідовно для кожного знайденого файлу, де друга команда повинна бути запущена лише у випадку успіху першої команди, використовуйте:

find dirname ... -exec somecommand {} \; -exec someothercommand {} \;

Щоб запустити одну команду на декількох файлах одночасно:

find dirname ... -exec somecommand {} +

find у поєднанні з sh

Якщо вам потрібно використовувати функції командної оболонки в команді, такі як перенаправлення виводу або зняття розширення з назви файла або щось подібне, ви можете скористатися sh -cконструкцією. Ви повинні знати кілька речей про це:

  • Ніколи не вставляйте {}безпосередньо в shкод. Це дозволяє виконувати довільне виконання коду зі зловмисно створених імен файлів. Крім того, POSIX навіть не вказав, що він буде працювати взагалі. (Див. Наступний пункт.)

  • Не використовуйте {}кілька разів і не використовуйте його як частину більш тривалого аргументу. Це не портативно. Наприклад, не робіть цього:

    find ... -exec cp {} somedir/{}.bak \;

    Процитуємо специфікації POSIX дляfind :

    Якщо рядок utility_name або аргумент містить два символи "{}", але не лише два символи "{}", це визначається реалізацією того, чи знаходить заміну цих двох символів чи використовує рядок без змін.

    ... Якщо присутні більше одного аргументу, що містить два символи "{}", поведінку не визначено.

  • Аргументи після командного рядка оболонки, передані до -cпараметра, встановлюються на позиційні параметри оболонки, починаючи з$0 . Не починаючи з $1.

    З цієї причини добре включити значення "фіктивного" $0, наприклад find-sh, яке буде використовуватися для повідомлення про помилки зсередини породженої оболонки. Крім того, це дозволяє використовувати такі конструкції, як, наприклад, "$@"при передачі декількох файлів в оболонку, тоді як, якщо пропустити значення для $0означатиме, що перший переданий файл буде встановлений $0і, таким чином, не включений в "$@".


Щоб запустити одну команду оболонки на файл, використовуйте:

find dirname ... -exec sh -c 'somecommandwith "$1"' find-sh {} \;

Однак зазвичай дасть кращу ефективність для обробки файлів у циклі оболонки, щоб ви не породили оболонку для кожного знайденого файлу:

find dirname ... -exec sh -c 'for f do somecommandwith "$f"; done' find-sh {} +

(Зауважте, що for f doце еквівалентно for f in "$@"; doта обробляє кожен з позиційних параметрів по черзі - іншими словами, він використовує кожен з файлів, знайдених find, незалежно від будь-яких спеціальних символів у їхніх назвах.)


Подальші приклади правильного findвикористання:

(Примітка. Сміливо розширюйте цей список.)


5
Є один випадок, коли я не знаю альтернативи розбору findрезультатів - де потрібно запустити команди в поточній оболонці (наприклад, тому, що ви хочете встановити змінні) для кожного файлу. У цьому випадку while IFS= read -r -u3 -d '' file; do ... done 3< <(find ... -print0)це найкраща ідіома, яку я знаю. Примітки: <( )не є портативним - використовуйте bash або zsh. Крім того, є -u3і 3<є на випадок, якщо всередині циклу намагається прочитати stdin.
Гордон Девіссон

1
@GordonDavisson, можливо, але то , що вам потрібно встановити ці змінні для ? Я вважаю , що все , що він повинен бути оброблений всередині на find ... -execвиклик. Або просто скористайтеся глобусом оболонки, якщо він буде обробляти ваш випадок використання.
Wildcard

1
Я часто хочу надрукувати резюме після обробки файлів ("2 перетворені, 3 пропущені; у наступних файлах були помилки: ..."), і ці рахунки / списки повинні бути накопичені в змінних оболонок. Також бувають ситуації, коли я хочу створити масив імен файлів, щоб я міг робити більш складні речі, ніж ітерацію в порядку (у такому випадку це так filelist=(); while ... do filelist+=("$file"); done ...).
Гордон Девіссон

3
Ваша відповідь правильна. Однак мені не подобається догма. Незважаючи на те, що я краще знаю, є багато (спеціально інтерактивних) випадків використання, коли це безпечно і просто простіше набрати циклічне завершення над findрезультатами або ще гірше використовувати ls. Я роблю це щодня без проблем. Я знаю про -print0, --null, -z або -0 опцій усіх видів інструментів. Але я б не витрачав час на використання їх у своєму інтерактивному підказці оболонки, якщо насправді це не потрібно. Це можна також зазначити у вашій відповіді.
rudimeier

16
@rudimeier, аргумент щодо догми та найкращої практики вже зроблений до смерті . Не зацікавлений. Якщо ви використовуєте його інтерактивно, і він працює, добре, добре для вас, але я не збираюся сприяти цьому. Відсоток авторів сценаріїв, які намагаються дізнатися, що таке надійний код, а потім роблять лише це, коли пишуть виробничі сценарії, замість того, щоб робити все, що вони звикли робити в інтерактивному режимі, надзвичайно мінімальний. Поводження полягає у просуванні кращих практик постійно. Люди повинні дізнатися, що існує правильний спосіб робити речі.
Wildcard

10

Ця відповідь стосується дуже великих наборів результатів і стосується в основному продуктивності, наприклад, при отриманні списку файлів через повільну мережу. Для невеликої кількості файлів (скажімо, декілька 100 або, можливо, навіть 1000 на локальному диску), більшість із них є суперечливими.

Паралелізм та використання пам'яті

Крім інших наведених відповідей, пов'язаних з проблемами розлуки та іншим, є ще одне питання

for file in `find . -type f -name ...`; do smth with ${file}; done

Частина всередині задників повинна бути оцінена спочатку повністю, перш ніж розділятися на рядкові рядки. Це означає, що якщо ви отримуєте величезну кількість файлів, він може або задихатися в будь-яких обмеженнях розміру в різних компонентах; у вас може не вистачити пам'яті, якщо немає обмежень; і в будь-якому випадку вам доведеться почекати, поки весь список буде виведений, findа потім проаналізований, forперш ніж навіть запустити свій перший smth.

Кращим Unix-способом є робота з трубами, які за своєю суттю працюють паралельно, а також взагалі не потребують довільно величезних буферів. Це означає: ви б більше хотіли, findщоб параметр виконувався паралельно вашому smth, і зберігати тільки поточне ім'я файлу в оперативній пам'яті, поки він передає це smth.

Одне хоча б частково рішення OKish для цього - вищезгадане find -exec smth. Це знімає необхідність зберігати всі імена файлів у пам’яті і добре працює паралельно. На жаль, він також запускає один smthпроцес на файл. Якщо smthможна працювати лише над одним файлом, то так і має бути.

Якщо це взагалі можливо, оптимальне рішення було б find -print0 | smth, з smthможливістю обробляти імена файлів на його STDIN. Тоді у вас є лише один smthпроцес, незалежно від того, скільки файлів є, і вам потрібно буферувати лише невелику кількість байтів (незалежно від буферизації внутрішньої труби) між двома процесами. Звичайно, це досить нереально, якщо smthце стандартна команда Unix / POSIX, але це може бути підхід, якщо ви пишете її самостійно.

Якщо це неможливо, то find -print0 | xargs -0 smth, швидше за все, це одне з кращих рішень. Як згадується в коментарях @ dave_thompson_085, xargsвін розбиває аргументи на декілька етапів, smthколи досягаються системні ліміти (за замовчуванням в діапазоні 128 Кб або будь-який ліміт, встановлений execсистемою), і має можливість впливати на кількість Файли надаються одному виклику smth, отже, знаходить баланс між кількістю smthпроцесів та початковою затримкою.

EDIT: вилучено поняття "кращий" - важко сказати, чи вийде щось краще. ;)


find ... -exec smth {} +є рішенням.
Wildcard

find -print0 | xargs smthвзагалі не працює, але find -print0 | xargs -0 smth(зауважте -0) або find | xargs smthякщо у назви файлів немає цитат пробілу або зворотний косий рядок виконує одне smthстільки найменувань файлів, скільки доступних, і вони входять до одного списку аргументів ; якщо ви перевищуєте maxargs, він працює smthстільки разів, скільки потрібно для обробки всіх заданих аргументів (без обмеження). Ви можете встановити менші "шматки" (таким чином дещо раніше паралелізм) за допомогою -L/--max-lines -n/--max-args -s/--max-chars.
dave_thompson_085


4

Однією з причин є те, що пробіл кидає ключі у творах, завдяки чому файл "foo bar" оцінюється як "foo" та "bar".

$ ls -l
-rw-rw-r-- 1 ec2-user ec2-user 0 Nov  7 18:24 foo bar
$ for file in `find . -type f` ; do echo filename $file ; done
filename ./foo
filename bar
$

Працює нормально, якщо замість нього використовується -exec

$ find . -type f -exec echo filename {} \;
filename ./foo bar
$ find . -type f -exec stat {} \;
  File: ‘./foo bar’
  Size: 0               Blocks: 0          IO Block: 4096   regular empty file
Device: ca01h/51713d    Inode: 9109        Links: 1
Access: (0664/-rw-rw-r--)  Uid: (  500/ec2-user)   Gid: (  500/ec2-user)
Access: 2016-11-07 18:24:42.027554752 +0000
Modify: 2016-11-07 18:24:42.027554752 +0000
Change: 2016-11-07 18:24:42.027554752 +0000
 Birth: -
$

Особливо у випадку, findоскільки існує можливість виконання команди на кожному файлі, це легко найкращий варіант.
Centimane

1
Також розглянемо -exec ... {} \;проти-exec ... {} +
тричі

1
якщо ви використовуєте, for file in "$(find . -type f)" і echo "${file}"тоді це працює навіть з пробілами, інші спеціальні символи, я думаю, спричиняють більше проблем, хоча
маз

9
@mazs - ні, цитування не робить те, що ти думаєш. У каталозі з декількома файлами спробуйте, for file in "$(find . -type f)";do printf '%s %s\n' name: "${file}";doneякий повинен (згідно з вами) друкувати кожне ім'я файлу в окремому рядку, перед яким передує name:. Це не так.
don_crissti

2

Оскільки вихід будь-якої команди - це один рядок, але для вашого циклу потрібен масив рядків, щоб перетворити його на цикл. Причина, по якій це "працює", полягає в тому, що снаряди зради розділили рядок на пробіл.

По-друге, якщо вам не потрібна певна особливість find, майте на увазі, що ваша оболонка, швидше за все, вже може розширити рекурсивний глобальний візерунок сам по собі, і головне, що він розшириться до належного масиву.

Приклад баша:

shopt -s nullglob globstar
for i in **
do
    echo «"$i"»
done

Те саме в рибі:

for i in **
    echo «$i»
end

Якщо вам потрібні функції find, переконайтеся, що розділите лише NUL (наприклад, find -print0 | xargs -r0ідіому).

Риба може повторювати обмежений вихід NUL. Так що це один насправді НЕ погано:

find -print0 | while read -z i
    echo «$i»
end

Як остання маленька готча, у багатьох оболонках (не Риба, звичайно) переведення циклу на команду зробить тіло циклу підшлубком (тобто ви не можете встановити змінну будь-яким способом, який видно після завершення циклу), тобто ніколи того, чого ти хочеш.


@don_crissti саме. Це не завжди працює. Я намагався бути саркастичним, кажучи, що це "працює" (з цитатами).
user2394284

Зауважте, що рекурсивний глобулінг зародився zshна початку 90-х (хоча вам це знадобиться **/*). fishяк і раніше реалізація еквівалентної функції bash, слід слідувати за посиланнями при зниженні дерева каталогів. Див . Результат ls *, ls ** та ls *** для відмінностей між реалізаціями.
Стефан Хазелас

1

Перегляд результатів пошуку не є поганою практикою - те, що погана практика (у цій та всіх ситуаціях) передбачає, що ваш вклад - це певний формат, а не знати (тестувати та підтверджувати), що це певний формат.

tldr / cbf: find | parallel stuff

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.