Вопрос или проблема
Последовательно один за другим:
for i in $(ls -1) ; do command $i ; done
Все за один раз:
for i in $(ls -1); do \
( \
command $i ; done ; \
) & \
done; wait
Как сказать, например, 4 или 8 за раз?
Если переход на zsh
— это вариант:
autoload zargs
zargs --eof= -rl1 -P4 -- *(N) '' cmd --
Это выполнит cmd -- file
, 4 параллельно для file
, находящихся в текущем рабочем каталоге.
По умолчанию разделитель eof
— это --
, но здесь мы переходим к пустой строке на случай, если в текущем рабочем каталоге есть файл с именем --
.
Если ваш cmd
принимает параметры, но не принимает --
в качестве разделителя параметров, вы можете заменить cmd --
на cmd
, а *(N)
на ./*(N)
, чтобы избежать проблем с именами файлов, начинающимися с -
(так как cmd
будет выполняться как cmd ./-те-файлы-
).
Обратите внимание, что он выполняет их партиями по 4 и не начинает новую партию, пока первая не завершится. Вам может быть предпочтительнее поведение GNU xargs -P
, которое всегда пытается запустить до 4 задач параллельно:
xargs -r0 -P4 -l1 -a <(print -rNC1 -- *(N)) cmd --
Это можно сделать и с помощью bash
(и если ваша система имеет bash (GNU shell), есть шансы, что в ней будет GNU xargs
):
print0() { [ "$#" -eq 0 ] || printf '%s\0' "$@"; }
xargs -r0 -P4 -l1 -a <(
shopt -u failglob
shopt -s nullglob
print0 *) cmd --
С помощью команды parallel
из moreutils (но не GNU parallel
, хотя GNU parallel
будет иметь эквивалентный способ сделать это):
parallel -j4 cmd -- ./*(N) # zsh
(shopt -u failglob; shopt -s nullglob
exec parallel -j4 cmd -- ./*) # bash
--
здесь обозначает для параллельного конца команды, которую нужно выполнить, это не аргумент, переданный --
, следовательно, необходим префикс ./
, чтобы защититься от имен файлов, начинающихся с -
, как отмечено выше.
Я предполагаю, что ссылка OP на command
— это заполнитель для другого двоичного файла (в отличие от вызова встроенной функции bash
с именем command
).
Создайте несколько файлов:
$ touch a.txt b.txt 'c d e.pdf' g.dat i.txt k.txt 'l m n.pdf' qrst.pdf$'\r'
$ mkdir dir1
$ touch dir1/XXX.txt
$ ls -1F
a.txt
b.txt
'c d e.pdf'
dir1/
g.dat
i.txt
k.txt
'l m n.pdf'
'qrst.pdf'$'\r'
$ tree
.
├── a.txt
├── b.txt
├── c d e.pdf
├── dir1
│ └── XXX.txt
├── g.dat
├── i.txt
├── k.txt
├── l m n.pdf
└── qrst.pdf\015
Простой сценарий для обработки имен файлов:
$ cat my_command
#!/bin/bash
printf "my input:%s:\n" "$@"
sleep 4
Общий подход:
- использовать
find
, чтобы сгенерировать список файлов из текущего каталога - передать файлы в
xargs
и позволитьxargs
обрабатывать запуск N экземпляровmy_command
одновременно
Один из подходов:
find . -maxdepth 1 -type f -print0 | xargs -0 -r -P3 -n1 my_command
Где:
find / -print0
иxargs / -0
используются для правильной обработки имен файлов, которые включают пробелы и другие нечитаемые символы-P3
– запускать не более 3 экземпляровmy_command
одновременно-n1
– отправлять одно имя файла за раз вmy_command
Это выдает:
my input:./b.txt: # 3 строки вывода, напечатанные быстро
my input:./g.dat:
my input:./l m n.pdf:
# 4-секундная задержка
my input:./i.txt: # 3 строки вывода, напечатанные быстро
my input:./c d e.pdf:
my input:./k.txt:
# 4-секундная задержка
my input:./a.txt: # осталось только 2 имени файла, поэтому только 2 экземпляра my_command вызваны для генерации 2 строк вывода быстро
:y input:./qrst.pdf # обратите внимание, что последний ':' отображается в начале строки; это указывает на то, что завершающий '\r' имени файла был сохранен
# 4-секундная задержка
Другой вариант — поместить все файлы в массив и затем брать последовательные срезы массива:
#!/bin/bash
## Нет ошибки, если шаблон не совпадает ни с чем
shopt -u failglob
## Вернуть null, если шаблон не совпадает вместо
## возвращения самого шаблона как строки
shopt -s nullglob
## установить целевой каталог; по умолчанию текущий, если аргумент не указан
target=${1:-.}
## сохранить содержимое $target в массиве $files
files=( "$target"/* )
## Перебирать массив группами по 4
i=0
while [ $i -lt ${#files[@]} ]; do
end=$((i+4))
subset=("${files[@]:$i:4}")
echo "subset $i - $end: ${subset[@]}"
((i+=4))
done
Этот сценарий будет обрабатывать группы по 4 (обратите внимание, что он также будет обрабатывать каталоги, а не только файлы) в любом каталоге, который вы укажете. Если вы запустите его без аргументов, он пройдет через текущий каталог. Например:
$ tree
.
├── file1
├── file10
├── file11
├── file12
├── file2
├── file3
├── file4
├── file5
├── file6
├── file7
├── file8
└── file9
1 каталог, 12 файлов
$ foo.sh
subset 0 - 4: ./file1 ./file10 ./file11 ./file12
subset 4 - 8: ./file2 ./file3 ./file4 ./file5
subset 8 - 12: ./file6 ./file7 ./file8 ./file9
Ответ или решение
Для обработки последовательности элементов группами по N одновременно, существует несколько подходов в Bash и Zsh. Ниже приведены основные методы реализации:
1. Использование xargs
Если ваша задача состоит в том, чтобы выполнять команды над файлами в текущем каталоге с ограничением на количество одновременно запущенных процессов, вы можете использовать xargs
:
find . -maxdepth 1 -type f -print0 | xargs -0 -r -P4 -n1 my_command
В этом примере:
find . -maxdepth 1 -type f -print0
— находит все файлы в текущем каталоге и выводит их с разделением нулевым символом, чтобы правильно обрабатывать имена файлов с пробелами и специальными символами.xargs -0
— принимает входные данные, разделенные нулем.-r
— предотвращает запуск команды, если входные данные отсутствуют.-P4
— указываетxargs
запускать максимум 4 процесса одновременно.-n1
— передает один файл за раз вmy_command
.
2. Использование GNU parallel
Если у вас установлен parallel
(часть пакета moreutils
), это тоже отличный способ обработки файлов группами:
parallel -j4 my_command -- ./*
где:
-j4
— указывает на запуск до 4 процессов одновременно.my_command --
— это ваша команда, которая будет применяться к каждому файлу в текущем каталоге.
3. Применение массивов и срезов
Вы можете создать массив файлов и обрабатывать их по частям:
#!/bin/bash
shopt -u failglob
shopt -s nullglob
target=${1:-.}
files=( "$target"/* )
for (( i=0; i<${#files[@]}; i+=4 )); do
subset=("${files[@]:$i:4}")
echo "Processing subset: ${subset[@]}"
# Выполняем команду для файлов в subset
for file in "${subset[@]}"; do
my_command "$file" &
done
wait # ждем завершения всех процессов в текущем наборе
done
В этом примере:
- Мы сохраняем все файлы из указанного каталога в массив
files
. - Затем обрабатываем их по 4 файла за раз, создавая подмассивы и запуская команду параллельно, после чего останавливаем выполнение, дожидаясь завершения всех запущенных процессов.
4. Использование zargs
в Zsh
Если у вас Zsh, можно использовать zargs
для более утонченной обработки:
autoload zargs
zargs --eof= -rl1 -P4 -- *(N) '' cmd --
Это позволяет запускать cmd
для каждого файла до 4 процессов одновременно.
Заключение
Все предложенные методы позволяют эффективно обрабатывать файлы группами, соблюдая ограничения на количество одновременно выполняемых процессов. Выбор метода зависит от ваших потребностей и доступных инструментов. К примеру, использование xargs
и GNU parallel
более универсально и подходит для Bash, в то время как zargs
может использовать специфические возможности Zsh.