J'ai écrit ce script bash pour le faire. Il forme essentiellement un tableau contenant les noms des fichiers à insérer dans chaque tar, puis démarre tar
en parallèle sur chacun d'eux .Ce n'est peut-être pas le moyen le plus efficace, mais le travail sera fait comme vous le souhaitez. Je peux cependant m'attendre à ce qu'il consomme de grandes quantités de mémoire.
Vous devrez ajuster les options au début du script. Vous pouvez également modifier les options tar cvjf
dans la dernière ligne (comme supprimer la sortie détaillée v
pour les performances ou la modification de la compression j
à z
,etc...).
Script
#!/bin/bash
# User configuratoin
#===================
files=(*.log) # Set the file pattern to be used, e.g. (*.txt) or (*)
num_files_per_tar=5 # Number of files per tar
num_procs=4 # Number of tar processes to start
tar_file_dir='/tmp' # Tar files dir
tar_file_name_prefix='tar' # prefix for tar file names
tar_file_name="$tar_file_dir/$tar_file_name_prefix"
# Main algorithm
#===============
num_tars=$((${#files[@]}/num_files_per_tar)) # the number of tar files to create
tar_files=() # will hold the names of files for each tar
tar_start=0 # gets update where each tar starts
# Loop over the files adding their names to be tared
for i in `seq 0 $((num_tars-1))`
do
tar_files[$i]="$tar_file_name$i.tar.bz2 ${files[@]:tar_start:num_files_per_tar}"
tar_start=$((tar_start+num_files_per_tar))
done
# Start tar in parallel for each of the strings we just constructed
printf '%s\n' "${tar_files[@]}" | xargs -n$((num_files_per_tar+1)) -P$num_procs tar cjvf
Explication
Tout d'abord, tous les noms de fichiers correspondant au motif sélectionné sont stockés dans le tableau files
. Ensuite, la boucle for tranche ce tableau et forme des chaînes à partir des tranches. Le nombre de tranches est égal au nombre d'archives souhaitées. Les chaînes résultantes sont stockées dans le tableau tar_files
. La boucle for ajoute également le nom de l'archive résultante au début de chaque chaîne. Les éléments de tar_files
prendre la forme suivante (en supposant 5 fichiers/tarball) :
tar_files[0]="tar0.tar.bz2 file1 file2 file3 file4 file5"
tar_files[1]="tar1.tar.bz2 file6 file7 file8 file9 file10"
...
La dernière ligne du script, xargs
est utilisé pour démarrer plusieurs tar
processus (jusqu'au nombre maximum spécifié) où chacun traitera un élément de tar_files
tableau en parallèle.
Tester
Liste des fichiers :
$ls
a c e g i k m n p r t
b d f h j l o q s
Tarballs générés :$ls /tmp/tar*tar0.tar.bz2 tar1.tar.bz2 tar2.tar.bz2 tar3.tar.bz2
Voici un autre script. Vous pouvez choisir si vous voulez précisément un million de fichiers par segment, ou précisément 30 segments. Je suis allé avec le premier dans ce script, mais le split
mot-clé permet l'un ou l'autre choix.
#!/bin/bash
#
DIR="$1" # The source of the millions of files
TARDEST="$2" # Where the tarballs should be placed
# Create the million-file segments
rm -f /tmp/chunk.*
find "$DIR" -type f | split -l 1000000 - /tmp/chunk.
# Create corresponding tarballs
for CHUNK in $(cd /tmp && echo chunk.*)
do
test -f "$CHUNK" || continue
echo "Creating tarball for chunk '$CHUNK'" >&2
tar cTf "/tmp/$CHUNK" "$TARDEST/$CHUNK.tar"
rm -f "/tmp/$CHUNK"
done
Il y a un certain nombre de subtilités qui pourraient être appliquées à ce script. L'utilisation de /tmp/chunk.
car le préfixe de la liste de fichiers devrait probablement être poussé dans une déclaration constante, et le code ne devrait pas vraiment supposer qu'il peut supprimer tout ce qui correspond à /tmp/chunk.*
, mais je l'ai laissé ainsi comme une preuve de concept plutôt qu'un utilitaire raffiné. Si j'utilisais ceci, j'utiliserais mktemp
pour créer un répertoire temporaire pour contenir les listes de fichiers.
Celui-ci fait exactement ce qui a été demandé :
#!/bin/bash
ctr=0;
# Read 1M lines, strip newline chars, put the results into an array named "asdf"
while readarray -n 1000000 -t asdf; do
ctr=$((${ctr}+1));
# "${asdf[@]}" expands each entry in the array such that any special characters in
# the filename won't cause problems
tar czf /destination/path/asdf.${ctr}.tgz "${asdf[@]}";
# If you don't want compression, use this instead:
#tar cf /destination/path/asdf.${ctr}.tar "${asdf[@]}";
# this is the canonical way to generate output
# for consumption by read/readarray in bash
done <(find /source/path -not -type d);
readarray
(en bash) peut également être utilisé pour exécuter une fonction de rappel, ce qui pourrait potentiellement être réécrit pour ressembler à :
function something() {...}
find /source/path -not -type d \
| readarray -n 1000000 -t -C something asdf
GNU parallel
pourrait être exploité pour faire quelque chose de similaire (non testé ; je n'ai pas parallel
installé là où je suis donc je m'en sors) :
find /source/path -not -type d -print0 \
| parallel -j4 -d '\0' -N1000000 tar czf '/destination/path/thing_backup.{#}.tgz'
Comme cela n'a pas été testé, vous pouvez ajouter le --dry-run
arg pour voir ce qu'il va réellement faire. J'aime celui-ci le meilleur, mais tout le monde n'a pas parallel
installée. -j4
lui fait utiliser 4 tâches à la fois, -d '\0'
combiné avec find
est -print0
lui fait ignorer les caractères spéciaux dans le nom de fichier (espaces, etc.). Le reste devrait être explicite.
Quelque chose de similaire pourrait être fait avec parallel
mais je n'aime pas ça car ça génère des noms de fichiers aléatoires :
find /source/path -not -type d -print0 \
| parallel -j4 -d '\0' -N1000000 --tmpdir /destination/path --files tar cz
Je ne connais pas [encore ?] un moyen de générer des noms de fichiers séquentiels.
xargs
pourrait également être utilisé, mais contrairement à parallel
il n'y a pas de moyen simple de générer le nom du fichier de sortie, vous finirez donc par faire quelque chose de stupide/hacky comme ceci :
find /source/path -not -type d -print0 \
| xargs -P 4 -0 -L 1000000 bash -euc 'tar czf $(mktemp --suffix=".tgz" /destination/path/backup_XXX) "[email protected]"'
L'OP a dit qu'ils ne voulaient pas utiliser split ... J'ai pensé que cela semblait bizarre comme cat
les rejoindra très bien ; cela produit un tar et le divise en morceaux de 3 Go :
tar c /source/path | split -b $((3*1024*1024*1024)) - /destination/path/thing.tar.
... et cela les décompresse dans le répertoire courant :
cat $(\ls -1 /destination/path/thing.tar.* | sort) | tar x