Cómo tar.gz muchos archivos de tamaño similar en múltiples archivos con un límite de tamaño

11

Estoy en Ubuntu 16.04.

Tengo una carpeta con muchos archivos de texto (casi 12k). Necesito cargarlos todos en un sitio web que acepte .tar.gzcargas y luego los descomprima automáticamente, pero tiene un límite de 10 MB (10000 KB) por archivo (por lo que, en particular, cada archivo debe descomprimirse por sí solo). Si uso tar.gztodos estos archivos, el archivo resultante es de aproximadamente 72 MB.

Lo que me gustaría hacer es crear ocho .tar.gzarchivos, cada uno de tamaño / dimensión (estrictamente) más pequeño que 10000 KB.

Alternativamente, se puede suponer que todos los archivos anteriores tienen aproximadamente la misma dimensión, por lo que me gustaría crear ocho .tar.gzarchivos con más o menos la misma cantidad de archivos cada uno.

¿Cómo puedo hacer alguna de estas dos tareas?

Estoy perfectamente bien con una solución que involucra GUI, CLI o secuencias de comandos. No estoy buscando velocidad aquí, solo necesito que se haga.

dadexix86
fuente
Presumiblemente, los archivos de 12k que tenga tendrán patrones o caracteres repetidos en sus nombres. Es posible que pueda taragregar todos los archivos que comienzan con un cierto patrón hasta que los tenga todos. Esto se puede programar fácilmente, pero no garantiza que el tamaño sea inferior a 9 MB según lo necesite. Sin embargo, puede ajustar manualmente el tamaño de los archivos que son demasiado grandes dividiéndolos aún más.
Juan Antonio

Respuestas:

9

Totalmente patchwork y un boceto rápido y aproximado, pero probado en un directorio con 3000 archivos, el siguiente script hizo un trabajo extremadamente rápido:

#!/usr/bin/env python3
import subprocess
import os
import sys

splitinto = 2

dr = sys.argv[1]
os.chdir(dr)

files = os.listdir(dr)
n_files = len(files)
size = n_files // splitinto

def compress(tar, files):
    command = ["tar", "-zcvf", "tarfile" + str(tar) + ".tar.gz", "-T", "-", "--null"]
    proc = subprocess.Popen(command, stdin=subprocess.PIPE)
    with proc:
        proc.stdin.write(b'\0'.join(map(str.encode, files)))
        proc.stdin.write(b'\0')
    if proc.returncode:
        sys.exit(proc.returncode)

sub = []; tar = 1
for f in files:
    sub.append(f)
    if len(sub) == size:
        compress(tar, sub)
        sub = []; tar += 1

if sub:
    # taking care of left
    compress(tar, sub)

Cómo utilizar

  • Guárdelo en un archivo vacío como compress_split.py
  • En la sección de cabecera, establezca el número de archivos para comprimir. En la práctica, siempre habrá uno más para ocuparse de los pocos "sobrantes" restantes.
  • Ejecútelo con el directorio con sus archivos como argumento:

    python3 /path/tocompress_split.py /directory/with/files/tocompress

los .tar.gzarchivos numerados se crearán en el mismo directorio donde están los archivos.

Explicación

La secuencia de comandos:

  • enumera todos los archivos en el directorio
  • CD's en el directorio para evitar agregar la información de ruta al archivo tar
  • lee la lista de archivos, agrupándolos por la división establecida
  • comprime los subgrupos en archivos numerados

EDITAR

Crear automáticamente trozos por tamaño en mb

Más sofisticado es usar el tamaño máximo (en mb) de los fragmentos como (segundo) argumento. En el siguiente script, los fragmentos se escriben en un archivo comprimido tan pronto como el fragmento alcanza (pasa) el umbral.

Dado que la secuencia de comandos se desencadena por los fragmentos, que exceden el umbral, esto solo funcionará si el tamaño de (todos) los archivos es sustancialmente menor que el tamaño del fragmento.

La secuencia de comandos:

#!/usr/bin/env python3
import subprocess
import os
import sys

dr = sys.argv[1]
chunksize = float(sys.argv[2])
os.chdir(dr)

files = os.listdir(dr)
n_files = len(files)

def compress(tar, files):
    command = ["tar", "-zcvf", "tarfile" + str(tar) + ".tar.gz", "-T", "-", "--null"]
    proc = subprocess.Popen(command, stdin=subprocess.PIPE)
    with proc:
        proc.stdin.write(b'\0'.join(map(str.encode, files)))
        proc.stdin.write(b'\0')
    if proc.returncode:
        sys.exit(proc.returncode)

sub = []; tar = 1; subsize = 0
for f in files:
    sub.append(f)
    subsize = subsize + (os.path.getsize(f)/1000000)
    if subsize >= chunksize:
        compress(tar, sub)
        sub = []; tar += 1; subsize = 0

if sub:
    # taking care of left
    compress(tar, sub)

Correr:

python3 /path/tocompress_split.py /directory/with/files/tocompress chunksize

... donde chunksize es el tamaño de entrada para el comando tar.

En este, se incluyen las mejoras sugeridas por @DavidFoerster. Gracias un montón !

Jacob Vlijm
fuente
@ dadexix86 de nada!
Jacob Vlijm el
Me libré de la invocación de shell y utilicé una lista de argumentos directamente. Aún así, las listas de argumentos grandes pueden ser problemáticas y trataré de mejorar taraún más la invocación proporcionando la lista de archivos en la secuencia de entrada estándar.
David Foerster el
Hola @DavidFoerster, confío en tu conocimiento, pero ¿cuál es la ventaja?
Jacob Vlijm el
La mayoría de los entornos de tiempo de ejecución tienen un límite (suave y duro) en la longitud total de las cadenas de argumentos de un comando que alcanzará rápidamente cuando opere en miles de archivos. Es por eso tarque le permite especificar archivos para agregar (o extraer) en la entrada estándar con una opción adecuada.
David Foerster el
@DavidFoerster hay un problema, sin embargo, el segundo ya no se ejecuta. En realidad ninguno de ellos lo hace ...
Jacob Vlijm
6

Un enfoque de concha pura:

files=(*); 
num=$((${#files[@]}/8));
k=1
for ((i=0; i<${#files[@]}; i+=$num)); do 
    tar cvzf files$k.tgz -- "${files[@]:$i:$num}"
    ((k++))
done

Explicación

  • files=(*): guarde la lista de archivos (también directorios si hay alguno, cambie a files=(*.txt)para obtener solo cosas con una txtextensión) en la matriz $files.
  • num=$((${#files[@]}/8));: ${#files[@]}es el número de elementos en la matriz $files. Esta $(( ))es la forma (limitada) de bash de hacer aritmética. Entonces, este comando establece $numel número de archivos dividido por 8.
  • k=1 : solo un contador para nombrar los tarballs.
  • for ((i=0; i<${#files[@]}; i+=$num)); do: iterar sobre los valores de la matriz. $ise inicializa en 0(el primer elemento de la matriz) y se incrementa en $num. Esto continúa hasta que hayamos pasado por todos los elementos (archivos).
  • tar cvzf files$i.tgz -- ${files[@]:$i:$num}: en bash, puede obtener un segmento de matriz (parte de una matriz) utilizando ${array[@]:start:length}, por lo ${array[@]:2:3}que devolverá tres elementos a partir del segundo. Aquí, estamos tomando un segmento que comienza en el valor actual de $iy es $numelementos largos. Se --necesita en caso de que alguno de sus nombres de archivo pueda comenzar con a -.
  • ((k++)) : incremento $k
terdon
fuente
¡Agradable! La primera vez que he visto un uso práctico de los rangos de índice de matriz bash.
Joe
Muy limpio y sucinto. Para mí, más comprensible que las soluciones de Python, aunque ambas son bastante buenas. ¿Se pregunta cómo se comparan todos en rendimiento?
DocSalvager