Принятые / высоко оцененные ответы великолепны, но в них отсутствуют некоторые мелкие детали. В этом посте рассматриваются случаи, как лучше справляться с неудачным раскрытием имени оболочки (glob), когда имена файлов содержат встроенные символы новой строки / тире и перемещение направления вывода команды из цикла for при записи результатов в файл.
При запуске расширения оболочки glob с использованием *
существует возможность сбоя раскрытия, если в каталоге нет файлов, а в команду, которая будет запущена, будет передана нераскрытая строка глобуса, что может привести к нежелательным результатам. 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
опцию оболочки для разделения файлов с этим нулевым байтом, используя -d
опцию read
команды, мы можем сделать ниже
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Символы nullglob
and 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
который будет в основном помещать содержимое вашей команды для каждой итерации ввода вашего файла в стандартный вывод, а когда цикл завершится, откройте целевой файл один раз для записи содержимого стандартного вывода и его сохранения. Эквивалентная 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