Escribí este script bash para hacerlo. Básicamente forma una matriz que contiene los nombres de los archivos para ir a cada tar, luego comienza tar
en paralelo en todos ellos Puede que no sea la forma más eficiente, pero hará el trabajo como usted quiere. Sin embargo, puedo esperar que consuma grandes cantidades de memoria.
Deberá ajustar las opciones al comienzo del script. También puede cambiar las opciones tar cvjf
en la última línea (como eliminar la salida detallada v
para rendimiento o cambio de compresión j
a z
, etc...).
Guión
#!/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
Explicación
Primero, todos los nombres de archivo que coinciden con el patrón seleccionado se almacenan en la matriz files
. A continuación, el bucle for divide este arreglo y forma cadenas a partir de los segmentos. El número de cortes es igual al número de tarballs deseados. Las cadenas resultantes se almacenan en la matriz tar_files
. El ciclo for también agrega el nombre del tarball resultante al comienzo de cada cadena. Los elementos de tar_files
tome la siguiente forma (suponiendo 5 archivos/tarball):
tar_files[0]="tar0.tar.bz2 file1 file2 file3 file4 file5"
tar_files[1]="tar1.tar.bz2 file6 file7 file8 file9 file10"
...
La última línea del script, xargs
se utiliza para iniciar múltiples tar
procesos (hasta el número máximo especificado) donde cada uno procesará un elemento de tar_files
matriz en paralelo.
Prueba
Lista de archivos:
$ls
a c e g i k m n p r t
b d f h j l o q s
Tarballs generados:$ls /tmp/tar*tar0.tar.bz2 tar1.tar.bz2 tar2.tar.bz2 tar3.tar.bz2
Aquí hay otro guión. Puede elegir si desea exactamente un millón de archivos por segmento o exactamente 30 segmentos. He optado por el primero en este script, pero el split
palabra clave permite cualquier elección.
#!/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
Hay una serie de sutilezas que podrían aplicarse a este script. El uso de /tmp/chunk.
ya que el prefijo de la lista de archivos probablemente debería insertarse en una declaración constante, y el código no debería asumir que puede eliminar cualquier cosa que coincida con /tmp/chunk.*
, pero lo he dejado así como una prueba de concepto en lugar de una utilidad pulida. Si estuviera usando esto, usaría mktemp
para crear un directorio temporal para almacenar las listas de archivos.
Este hace exactamente lo que se solicitó:
#!/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) también se puede usar para ejecutar una función de devolución de llamada, por lo que podría reescribirse para parecerse a:
function something() {...}
find /source/path -not -type d \
| readarray -n 1000000 -t -C something asdf
GNU parallel
podría aprovecharse para hacer algo similar (no probado; no tengo parallel
instalado donde estoy, así que lo estoy improvisando):
find /source/path -not -type d -print0 \
| parallel -j4 -d '\0' -N1000000 tar czf '/destination/path/thing_backup.{#}.tgz'
Como eso no se ha probado, puede agregar el --dry-run
arg para ver lo que realmente hará. Me gusta más este, pero no todo el mundo tiene parallel
instalado. -j4
hace que use 4 trabajos a la vez, -d '\0'
combinado con find
de -print0
hace que ignore los caracteres especiales en el nombre del archivo (espacios en blanco, etc.). El resto debe explicarse por sí mismo.
Se podría hacer algo similar con parallel
pero no me gusta porque genera nombres de archivos aleatorios:
find /source/path -not -type d -print0 \
| parallel -j4 -d '\0' -N1000000 --tmpdir /destination/path --files tar cz
No [¿todavía?] conozco una forma de hacer que genere nombres de archivo secuenciales.
xargs
también podría usarse, pero a diferencia de parallel
no hay una forma sencilla de generar el nombre del archivo de salida, por lo que terminaría haciendo algo estúpido/incorrecto como este:
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]"'
El OP dijo que no querían usar split... Pensé que parecía extraño como cat
se volverá a unir a ellos muy bien; esto produce un alquitrán y lo divide en fragmentos de 3 gb:
tar c /source/path | split -b $((3*1024*1024*1024)) - /destination/path/thing.tar.
... y esto los desata en el directorio actual:
cat $(\ls -1 /destination/path/thing.tar.* | sort) | tar x