Выполнить команду для всех файлов в каталоге
Может кто-нибудь, пожалуйста, предоставьте код, чтобы сделать следующее:
Предположим, что есть каталог файлов, все из которых нужно запускать через программу. Программа выводит результаты на стандарт. Мне нужен 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
Ответ 6
Основываясь на подходе @Jim Lewis:
Вот быстрое решение, использующее find
, а также сортировку файлов по дате их изменения:
$ find directory/ -maxdepth 1 -type f -print0 | \
xargs -r0 stat -c "%y %n" | \
sort | cut -d' ' -f4- | \
xargs -d "\n" -I{} cmd -op1 {}
Для сортировки см.
http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time
Ответ 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