bash: ограничение подоболочек в цикле for со списком файлов

Я пытался заставить цикл for одновременно запускать несколько команд и пытался сделать это через подоболочки. Мне удалось собрать приведенный ниже сценарий для тестирования, и он, кажется, работает нормально.

#!/bin/bash
for i in {1..255}; do
  (
    #commands
  )&

done
wait

Единственная проблема заключается в том, что мой фактический цикл будет для i в файлах *, а затем он просто падает, я полагаю, потому что он запускает слишком много подоболочек для обработки. Поэтому я добавил

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
if (( $i % 10 == 0 )); then wait; fi
done
wait

что сейчас не получается. Кто-нибудь знает способ обойти это? Либо использовать другую команду для ограничения количества подоболочек, либо указать номер для $i?

Ваше здоровье


person Nathaniel Saxe    schedule 19.12.2014    source источник
comment
что ты делаешь с файлами?   -  person SMA    schedule 19.12.2014
comment
В это - потом просто крашится - очень трудно поверить. Я почти уверен, что вместо этого он выдает какое-то сообщение об ошибке.   -  person Dummy00001    schedule 19.12.2014


Ответы (4)


Вы можете найти полезным подсчитать количество заданий с помощью jobs. например.:

wc -w <<<$(jobs -p)

Итак, ваш код будет выглядеть так:

#!/bin/bash
for i in files*; do
  (
    #commands
  )&
  if (( $(wc -w <<<$(jobs -p)) % 10 == 0 )); then wait; fi
done
wait

Как предложил @chepner:

В bash 4.3 вы можете использовать wait -n для продолжения, как только завершится какое-либо задание, а не ждать их всех.

person whoan    schedule 19.12.2014

xargs/параллельный

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

printf '%s\0' files* | xargs -0 -P6 -n1 yourScript

-P6 — это максимальное количество одновременных процессов, которые запустит xargs. Сделайте 10, если хотите.

Я предлагаю xargs, потому что он, скорее всего, уже есть в вашей системе. Если вам нужно действительно надежное решение, посмотрите GNU Parallel.

Имена файлов в массиве

Для другого явного ответа на ваш вопрос: получить счетчик как индекс массива?

files=( files* )
for i in "${!files[@]}"; do
    commands "${files[i]}" &
    (( i % 10 )) || wait
done

(Скобки вокруг составной команды не важны, потому что фоновое задание в любом случае будет иметь тот же эффект, что и использование подоболочки.)

Функция

Просто другая семантика:

simultaneous() {
    while [[ $1 ]]; do
        for i in {1..11}; do
            [[ ${@:i:1} ]] || break
            commands "${@:i:1}" &
        done
        shift 10 || shift "$#"
        wait
    done
}
simultaneous files*
person kojiro    schedule 19.12.2014
comment
Еще один плагин для wait -n, чтобы быстрее приступить к новой работе. - person chepner; 19.12.2014

Явное определение счетчика

#!/bin/bash
for f in files*; do
  (
    #commands
  )&
  (( i++ % 10 == 0 )) && wait
done
wait

Нет необходимости инициализировать i, так как по умолчанию он будет равен 0 при первом использовании. Также нет необходимости сбрасывать значение, так как i %10 будет равно 0 для i=10, 20, 30 и т. д.

person chepner    schedule 19.12.2014
comment
Мне нравится этот. Это дешевле +1 - person whoan; 19.12.2014
comment
При ближайшем рассмотрении я думаю, что это недостаточно удобно, потому что вы можете иметь i == 10, хотя количество фоновых заданий может быть меньше 10 (они могут завершиться). - person whoan; 19.12.2014
comment
Если цель состоит в том, чтобы ядра были максимально загружены, я бы использовал планировщик заданий, такой как parallel, а не писал его с нуля в bash. Это просто способ не допустить одновременного запуска слишком большого количества заданий, а не поддерживать выполнение как можно большего числа. - person chepner; 19.12.2014
comment
В bash 4.3 вы могли использовать wait -n для ожидания завершения любого отдельного задания перед запуском следующего. Однако это страдает от состояния гонки (я думаю, неизбежного), в котором wait -n вызывается после завершения задания, что может привести к периоду времени, когда может быть добавлено новое задание, но мы' повторно ожидая завершения другого задания. - person chepner; 19.12.2014
comment
Если вы действительно хотите, чтобы все ядра были заняты, запустите больше процессов, чем вы можете запустить одновременно, и позвольте ОС выполнять планирование. Это особенно желательно для заданий, связанных с вводом-выводом, которые могут простаивать, в то время как другие процессы могут выполняться. - person chepner; 19.12.2014
comment
Я довольно новичок во всем этом, но идея заключалась в том, чтобы запустить эту программу прогнозирования целей. Поэтому мне нужно было запустить одну и ту же команду с каждым из списка входных файлов для каждого файла в списке целевых файлов. дело было в том, что каждый отдельный запуск не требовал такой большой вычислительной мощности, но занимал много времени, поэтому я подумал, что могу сократить время, запустив несколько одинаковых, но не мог найти простой способ сделай это. - person Nathaniel Saxe; 19.01.2015

Если у вас Bash ≥ 4.3, вы можете использовать wait -n:

#!/bin/bash

max_nb_jobs=10

for i in file*; do
    # Wait until there are less than max_nb_jobs jobs running
    while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=max_nb_jobs)); do
        wait -n
    done
    {
        # Your commands here: no useless subshells! use grouping instead
    } &
done
wait

Если у вас нет wait -n, вы можете использовать что-то вроде этого:

#!/bin/bash

set -m

max_nb_jobs=10

sleep_jobs() {
   # This function sleeps until there are less than $1 jobs running
   local n=$1
   while mapfile -t < <(jobs -pr) && ((${#MAPFILE[@]}>=n)); do
      coproc read
      trap "echo >&${COPROC[1]}; trap '' SIGCHLD" SIGCHLD
      [[ $COPROC_PID ]] && wait $COPROC_PID
   done
}

for i in files*; do
    # Wait until there are less than 10 jobs running
    sleep_jobs "$max_nb_jobs"
    {
        # Your commands here: no useless subshells! use grouping instead
    } &
done
wait

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

person gniourf_gniourf    schedule 19.12.2014