Скачивайте файлы параллельно в 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