it-roy-ru.com

Bash-скрипт для выполнения команды над всеми файлами в каталоге

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

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

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

Следующий код 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
343
Andrew Logvinov

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

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

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

144
Jim Lewis

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

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

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

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

Который довольно трудно читать, поэтому давайте разберем его.

сначала перейдите в каталог со своими файлами,

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

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

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

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

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

; done Живи долго и счастливо

2
Eric Wooley

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

find directory/ | xargs  Command 

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

find . | xargs wc -l
2
Rahul

Основываясь на подходе @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

1
tuxdna

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

sh /dir/* > ./result.txt
1
yovie

Принятые/высоко оцененные ответы великолепны, но в них отсутствуют некоторые мелкие детали. В этом посте рассматриваются случаи, как лучше справляться с ошибками раскрытия имени пути (glob) в Shell, когда имена файлов содержат встроенные символы новой строки/тире и перемещения направления вывода команды из цикла for.

При запуске расширения глобуса оболочки с помощью * существует возможность сбоя расширения, если в каталоге есть нет файлы и нераскрытая строка глобуса передана команде, которая будет запущена в файле что может иметь нежелательные результаты. Оболочка bash предоставляет расширенную опцию Shell для этого, используя 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 команды 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

который в основном будет помещать содержимое вашей команды для каждой итерации ввода вашего файла в стандартный вывод, а когда цикл завершится, откройте целевой файл один раз для записи содержимого стандартного вывода и его сохранения. Эквивалентная find версия того же

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
0
Inian

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

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

$ 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
0
Chetabahana