télécharger des fichiers en parallèle dans un script bash

J’utilise la logique ci-dessous pour télécharger 3 fichiers à partir de la baie, une fois que tous les 3 fichiers terminés, seuls les 3 fichiers suivants seront captés.

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

Mais comment je m’attends à “à tout moment 3 téléchargements devraient courir” – je veux dire supposons que nous avons envoyé 3 téléchargements de fichiers en arrière-plan et un des 3 se termine très bientôt en raison de la taille moindre, je veux un autre fichier de la la queue doit être envoyée pour téléchargement.

SCRIPT COMPLET:

 #!/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[@]} 

Si vous ne voulez pas utiliser xargs vous pouvez:

 xargs -I xxx -P 3 sleep xxx < sleep 

et dormir c'est:

 1 2 3 4 5 6 7 8 9 

et si vous regardez le fond avec:

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

(le sumil pourrait être votre tableau de liens), vous verrez alors que 3 travaux sont exécutés en parallèle et que lorsque l’un de ces trois travaux est terminé, le travail suivant est ajouté. En fait, toujours 3 travaux sont exécutés jusqu'à la fin du tableau.

sortie d'échantillon de 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 commence avec 3 emplois et quand l'un d'entre eux est terminé, il appenda le prochain qui bacomes:

 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 

Je l’ai fait en utilisant un trap pour gérer SIGCHLD et commencer un autre transfert quand on se termine.

La partie difficile est que, une fois que votre script installe un gestionnaire SIGCHLD avec cette ligne d’ trap , vous ne pouvez créer aucun processus enfant autre que vos processus de transfert. Par exemple, si votre shell ne possède pas d’ echo intégré, l’appel de echo engendrerait un processus enfant qui vous obligerait à démarrer un transfert supplémentaire à la fin du processus d’ echo .

Je n’ai pas de copie disponible, mais c’était quelque chose comme ceci:

 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 

Cela n'a pas été testé du tout, et a probablement toutes sortes d'erreurs.

Je voudrais lire tous les noms de fichiers fournis en trois variables, puis traiter chaque stream séparément, par exemple

 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 

et maintenant appeler la fonction de téléchargement pour chacun des stream en parallèle:

 COUNTER=1 while [ ${COUNTER} -le ${PARALLEL} ] do eval "download \$FILESTREAM${COUNTER} &" COUNTER=`expr ${COUNTER} + 1` done 

Outre l’implémentation d’un script bash parallèle à partir de zéro, GNU parallel est un outil disponible qui convient parfaitement pour effectuer ce type de tâches.

 parallel -j3 curl -s -o {}.tar.gz download_url ::: "${layers[@]}" 
  • -j3 assure un maximum de 3 jobs en même temps
  • vous pouvez append une option supplémentaire --dry-run after parallel pour vous assurer que la commande construite est exactement comme vous le souhaitez