Скачивайте файлы параллельно в bash-скрипте

Я использую приведенную ниже логику, чтобы загрузить 3 файла из массива сразу, после того, как все 3 завершены, будут отобраны только следующие 3 файла.

parallel=3
       downLoad() {
                while (( "$#" )); do
                    for (( i=0; i<$parallel; i++ )); do
                        echo "downloading ${1}..."
                        curl -s -o ${filename}.tar.gz <download_URL> &
                        shift
                    done
                    wait
                                echo "#################################"
            done
        }

       downLoad ${layers[@]}

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

ПОЛНЫЙ СЦЕНАРИЙ:

#!/bin/bash

set -eu

reg="registry.hub.docker.com"
repo="hjd48"
image="redhat"
name="${repo}/${image}"
tag="latest"
parallel=3

# Get auth token
token=$( curl -s "https://auth.docker.io/token?service=registry.docker.io&scope=repository:${name}:pull" | jq -r .token )

# Get layers
resp=$(curl -s -H "Authorization: Bearer $token" "https://${reg}/v2/${name}/manifests/${tag}" | jq -r .fsLayers[].blobSum )
layers=( $( echo $resp | tr ' ' '\n' | sort -u ) )

prun() {
    PIDS=()
    while (( "$#" )); do
        if ( kill -0 ${PIDS[@]} 2>/dev/null ; [[ $(( ${#PIDS[@]} - $? )) -lt $parallel ]])
        then
                echo "Download: ${1}.tar.gz"
                curl -s -o $1.tar.gz -L -H "Authorization: Bearer $token" "https://${reg}/v2/${name}/blobs/${1}" &
                PIDS+=($!)
                shift
        fi
    done
    wait
}

prun ${layers[@]}

4 ответа

Решение

Если вы не против использования xargs тогда ты можешь:

xargs -I xxx -P 3 sleep xxx < sleep

и сон это:

1
2
3
4
5
6
7
8
9

и если вы смотрите фон с:

watch -n 1 -exec ps  --forest -g -p your-Bash-pid

(sleep может быть вашим массивом ссылок), тогда вы увидите, что 3 задания выполняются параллельно, и когда одно из этих трех завершено, добавляется следующее задание. Фактически всегда 3 задания выполняются до конца массива.

образец вывода watch(1):

12260 pts/3    S+     0:00  \_ xargs -I xxx -P 3 sleep xxx
12263 pts/3    S+     0:00      \_ sleep 1
12265 pts/3    S+     0:00      \_ sleep 2
12267 pts/3    S+     0:00      \_ sleep 3

xargs начинается с 3 заданий, и когда одно из них будет завершено, оно добавит следующее:

12260 pts/3    S+     0:00  \_ xargs -I xxx -P 3 sleep xxx
12265 pts/3    S+     0:00      \_ sleep 2
12267 pts/3    S+     0:00      \_ sleep 3
12269 pts/3    S+     0:00      \_ sleep 4 # this one was added

Я сделал это, используя trap обрабатывать SIGCHLD и начать другую передачу, когда она заканчивается.

Трудная часть состоит в том, что, как только ваш скрипт установит SIGCHLD обработчик с этим trap линия, вы не можете создавать дочерние процессы, кроме ваших процессов передачи. Например, если ваша оболочка не имеет встроенного echo звонит echo будет порождать дочерний процесс, который заставит вас начать еще одну передачу, когда echo процесс заканчивается

У меня нет доступной копии, но это было что-то вроде этого:

startDownload() {
   # only start another download if there are URLs left in
   # in the array that haven't been downloaded yet
   if [ ${ urls[ $fileno ] } ];
       # start a curl download in the background and increment fileno
       # so the next call downloads the next URL in the array
       curl ... ${ urls[ $fileno ] } &
       fileno=$((fileno+1))
   fi
}

trap startDownload SIGCHLD

# start at file zero and set up an array
# of URLs to download
fileno=0
urls=...

parallel=3

# start the initial parallel downloads
# when one ends, the SIGCHLD will cause
# another one to be started if there are
# remaining URLs in the array
for (( i=0; i<$parallel; i++ )); do
    startDownload
done

wait

Это совсем не проверено и, возможно, содержит всевозможные ошибки.

Помимо реализации параллельного bash-скрипта с нуля, GNU parallel это доступный инструмент для использования, который вполне подходит для выполнения задач такого типа.

parallel -j3 curl -s -o {}.tar.gz download_url ::: "${layers[@]}"
  • -j3 обеспечивает одновременное выполнение не более 3-х заданий
  • Вы можете добавить дополнительную опцию --dry-run после parallel чтобы убедиться, что встроенная команда именно так, как вы хотите

Я бы прочитал все предоставленные имена файлов в три переменные, а затем обработал каждый поток отдельно, например

PARALLEL=3
COUNTER=1
for FILENAME in $#
do
        eval FILESTREAM${COUNTER}="\$FILESTREAM${COUNTER} \${FILENAME}"
        COUNTER=`expr ${COUNTER} + 1`

        if [ ${COUNTER} -gt ${PARALLEL} ]
        then
                COUNTER=1
        fi
done

и теперь вызываем функцию загрузки для каждого из потоков параллельно:

COUNTER=1

while [ ${COUNTER} -le ${PARALLEL} ]
do
        eval "download \$FILESTREAM${COUNTER} &"
        COUNTER=`expr ${COUNTER} + 1`
done
Другие вопросы по тегам