Прийняті / голосовані відповіді чудові, але їм не вистачає декількох дрібнозернистих деталей. У цій публікації висвітлено випадки, як краще впоратися, коли помилка розширення імені шляху оболонки (глобус) не вдається, коли назви файлів містять вбудовані символи нових рядків / тире і переміщення виводу команди перенаправляється з циклу for-циклу під час запису результатів у файл.
При запуску розширення глобальної оболонки з використанням *
є можливість розширення не вдатися, якщо в каталозі відсутні файли, а нерозширена рядок глобулів буде передана команді, яку слід запустити, що може мати небажані результати. bash
Оболонка забезпечує розширений варіант оболонки для цього з допомогою nullglob
. Таким чином, цикл в основному стає наступним всередині каталогу, що містить ваші файли
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Це дозволяє безпечно вийти з циклу for, коли вираз ./*
не повертає жодних файлів (якщо каталог порожній)
або сумісним чином POSIX ( nullglob
це bash
специфічний)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Це дозволяє зайти всередину циклу, коли вираз не вдається за один раз, і умова [ -f "$file" ]
перевірити, чи нерозширений рядок ./*
є дійсним іменем файлу в тому каталозі, якого не було б. Тож за цієї відмови, використовуючи, continue
ми повертаємося до for
циклу, який згодом не працюватиме.
Також зверніть увагу на використання --
безпосередньо перед передачею аргументу імені файлу. Це потрібно, оскільки, як зазначалося раніше, назви файлів оболонки можуть містити тире в будь-якому місці імені файлу. Деякі команди оболонки інтерпретують це та розглядають їх як варіант команди, коли ім'я не котирується належним чином і виконує мислення команди, якщо прапор надається.
У --
сигналізує кінець параметрів командного рядка в тому випадку , що означає, що команда не повинна аналізувати всі рядки за межами цієї точки , як командні прапори , але тільки як імена файлів.
Подвійне цитування імен файлів належним чином вирішує випадки, коли імена містять глобальні символи або пробіли. Але імена файлів * nix також можуть містити в них нові рядки. Отже, ми обмежуємо обмеження імен файлів єдиним символом, який не може бути частиною дійсного імені файлу - null byte ( \0
). Оскільки bash
внутрішньо використовуються C
рядки стилів, в яких нульові байти використовуються для позначення кінця рядка, це правильний кандидат для цього.
Отже, використовуючи printf
опцію оболонки для розмежування файлів за допомогою цього байта NULL, використовуючи -d
параметр read
команди, ми можемо зробити нижче
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Оголошення nullglob
і і printf
обернуті навколо, (..)
що означає, що вони в основному працюють у підколонці (дочірній оболонці), оскільки, щоб уникнути nullglob
можливості відображення на батьківській оболонці, як тільки команда завершиться. -d ''
Варіант read
команди є НЕ POSIX сумісним, тому потребує bash
оболонці для цього потрібно зробити. За допомогою find
команди це можна зробити як
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Для find
реалізацій, які не підтримують -print0
(крім GNU та FreeBSD-реалізацій), це може бути емульовано за допомогоюprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Ще одне важливе виправлення полягає в переміщенні повторного напрямку з циклу for, щоб зменшити велику кількість вводу / виводу файлів. При використанні всередині циклу оболонка повинна виконувати системні виклики двічі за кожну ітерацію for-циклу, один раз для відкриття та один раз для закриття дескриптора файлу, пов'язаного з файлом. Це стане твоїм вирізом для виконання великих ітерацій. Рекомендованою пропозицією було б перемістити його поза циклом.
Розширивши наведений вище код за допомогою цих виправлень, ви могли б зробити
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
яка в основному помістить вміст вашої команди для кожної ітерації вхідного файлу в stdout, і коли цикл закінчиться, відкрийте цільовий файл один раз для запису вмісту stdout та збереження його. Еквівалентна find
версія тієї самої була б
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out