Выполнить команду для всех файлов в каталоге

Может кто-нибудь, пожалуйста, предоставьте код, чтобы сделать следующее: Предположим, что есть каталог файлов, все из которых нужно запускать через программу. Программа выводит результаты на стандарт. Мне нужен script, который войдет в каталог, выполнит команду для каждого файла и разделит вывод на один большой выходной файл.

Например, для запуска команды в 1 файл:

$ cmd [option] [filename] > results.out

Ответ 1

Следующий bash код передаст $file для команды, где $file будет представлять каждый файл в каталоге /dir

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Пример

[email protected] ~/foo $ touch foo.txt bar.txt baz.txt
[email protected] ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt

Ответ 2

Как насчет этого:

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • Аргумент
  • -maxdepth 1 не позволяет найти из рекурсивного спуска в любые подкаталоги. (Если вы хотите, чтобы такие вложенные каталоги обрабатывались, вы можете опустить это.)
  • -type -f указывает, что будут обрабатываться только простые файлы.
  • -exec cmd option {} сообщает, что он запускает cmd с указанным option для каждого найденного файла, при этом имя файла заменяется на {}
  • \; обозначает конец команды.
  • Наконец, вывод из всех индивидуальных cmd исполнений перенаправляется на results.out

Однако, если вы заботитесь о порядке обработки файлов, вы может быть лучше писать петлю. Я думаю, что find обрабатывает файлы в порядке inode (хотя я мог ошибаться в этом), что может и не быть вы хотите.

Ответ 3

Я делаю это на моем малине pi из командной строки, запустив:

for i in *;do omxplayer "$i";done

Ответ 4

Мне нужно было скопировать все файлы .md из одного каталога в другой, вот что я сделал.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Это довольно трудно прочитать, поэтому давайте сломаем его.

первый cd в каталог с вашими файлами,

for i in **/*.md; для каждого файла в вашем шаблоне

mkdir -p ../docs/"$i" сделать этот каталог в папке docs за пределами папки, содержащей ваши файлы. Что создает дополнительную папку с тем же именем, что и этот файл.

rm -r ../docs/"$i" удалите дополнительную папку, созданную в результате mkdir -p

cp "$i" "../docs/$i" Скопировать фактический файл

echo "$i -> ../docs/$i" Эхо, что ты сделал

; done Жить счастливо после

Ответ 5

Один быстрый и грязный способ, который иногда выполняет эту работу:

find directory/ | xargs  Command 

Например, чтобы найти количество строк во всех файлах в текущем каталоге, вы можете:

find . | xargs wc -l

Ответ 7

я думаю, что простое решение:

sh /dir/* > ./result.txt

Ответ 8

Максимальная глубина

Я нашел, что это хорошо работает с ответом Джима Льюиса, просто добавьте немного так:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Порядок сортировки

Если вы хотите выполнить в порядке сортировки, измените его следующим образом:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Просто для примера, это будет выполнено в следующем порядке:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Неограниченная глубина

Если вы хотите выполнить на неограниченной глубине при определенных условиях, вы можете использовать это:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

затем поместите поверх каждого файла в дочерних каталогах, как это:

#!/bin/bash
[[ "$(dirname 'pwd')" == $DIR ]] && echo "Executing 'realpath $0'.." || return

и где-нибудь в теле родительского файла:

if <a condition is matched>
then
    #execute child files
    export DIR='pwd'
fi

Ответ 9

Принятые/высоко оцененные ответы великолепны, но в них отсутствуют некоторые мелкие детали. В этом посте рассматриваются случаи, как лучше справляться с неудачным расширением имени пути оболочки (glob), когда имена файлов содержат встроенные символы новой строки/тире и перемещение направления вывода команды из цикла 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 также могут содержать в себе новые строки. Таким образом, мы ограничиваем имена файлов единственным символом, который не может быть частью действительного имени файла - нулевым байтом (\0). Поскольку bash внутренне использует строки в стиле C в которых нулевые байты используются для обозначения конца строки, он является подходящим кандидатом для этого.

Поэтому, используя опцию printf оболочки для разделения файлов с этим -d байтом, используя -d команды read, мы можем сделать следующее

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

nullglob и printf обернуты вокруг (..) что означает, что они в основном выполняются в под-оболочке (nullglob оболочка), потому что во избежание nullglob опции 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] --

Другим важным исправлением является перемещение функции re -d из цикла 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