Comment tar.gz de nombreux fichiers de taille similaire dans plusieurs archives avec une taille limite

11

Je suis sur Ubuntu 16.04.

J'ai un dossier avec beaucoup de fichiers texte (presque 12k). Je dois tous les télécharger sur un site Web qui accepte les .tar.gztéléchargements, puis les décompresse automatiquement, mais a une limite de 10 Mo (10000 Ko) par fichier (donc, en particulier, chaque fichier doit être décompressé seul). Si je tar.gztous ces fichiers, le fichier résultant est d'environ 72 Mo.

Ce que je voudrais faire, c'est créer huit .tar.gzfichiers, chacun de taille / dimension (strictement) inférieure à 10000 Ko.

Alternativement, on peut supposer que tous les fichiers ci-dessus ont approximativement la même dimension, donc je voudrais créer huit .tar.gzfichiers avec plus ou moins la même quantité de fichiers chacun.

Comment puis-je effectuer l'une de ces deux tâches?

Je suis parfaitement d'accord avec une solution qui implique une interface graphique, une CLI ou un script. Je ne cherche pas de vitesse ici, j'ai juste besoin de le faire.

dadexix86
la source
Vraisemblablement, les fichiers 12k que vous avez auront des motifs ou des caractères répétés dans leurs noms. Vous pouvez éventuellement tarles ajouter en ajoutant tous les fichiers en commençant par un certain modèle jusqu'à ce que vous les ayez tous. Cela peut être facilement scripté mais ne garantit pas que la taille sera inférieure à 9 Mo selon vos besoins. Vous pouvez cependant ajuster manuellement la taille de ces fichiers trop volumineux en les divisant davantage.
Juan Antonio

Réponses:

9

Totalement patchwork et une esquisse rapide et approximative, mais testée sur un répertoire de 3000 fichiers, le script ci-dessous a fait un travail extrêmement rapide:

#!/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)

Comment utiliser

  • Enregistrez-le dans un fichier vide sous compress_split.py
  • Dans la section head, définissez le nombre de fichiers dans lesquels compresser. Dans la pratique, il y en aura toujours un de plus pour s'occuper des quelques «restes» restants.
  • Exécutez-le avec le répertoire avec vos fichiers comme argument:

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

les .tar.gzfichiers numérotés seront créés dans le même répertoire que celui où se trouvent les fichiers.

Explication

Le scénario:

  • répertorie tous les fichiers du répertoire
  • cd dans le répertoire pour éviter d'ajouter les informations de chemin au fichier tar
  • lit la liste des fichiers en les regroupant par division définie
  • compresse le ou les sous-groupes dans des fichiers numérotés

ÉDITER

Créer automatiquement des morceaux par taille en Mo

Plus sophistiqué consiste à utiliser la taille maximale (en mb) des morceaux comme un (deuxième) argument. Dans le script ci-dessous, les morceaux sont écrits dans un fichier compressé dès que le morceau atteint (dépasse) le seuil.

Étant donné que le script est déclenché par les blocs, dépassant le seuil, cela ne fonctionnera que si la taille de (tous) les fichiers est sensiblement inférieure à la taille du bloc.

Le scénario:

#!/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)

Courir:

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

... où chunksize est la taille d' entrée de la commande tar.

Dans celui-ci, les améliorations suggérées par @DavidFoerster sont incluses. Merci beaucoup !

Jacob Vlijm
la source
@ dadexix86 vous êtes les bienvenus!
Jacob Vlijm
Je me suis débarrassé de l'invocation du shell et j'ai utilisé une liste d'arguments directement. Pourtant, les grandes listes d'arguments peuvent être problématiques et j'essaierai d'améliorer tardavantage l' invocation en fournissant la liste des fichiers sur le flux d'entrée standard.
David Foerster
Salut @DavidFoerster, je fais confiance à votre perspicacité, mais quel est l'avantage?
Jacob Vlijm
La plupart des environnements d'exécution ont une limite (douce et dure) sur la longueur totale des chaînes d'arguments d'une commande que vous atteindrez rapidement lorsque vous opérez sur des milliers de fichiers. C'est pourquoi tarvous permet de spécifier des fichiers à ajouter (ou extraire) sur une entrée standard avec une option appropriée.
David Foerster
@DavidFoerster il y a un problème cependant, le second ne fonctionne plus. En fait, aucun d'eux ne le fait ...
Jacob Vlijm
6

Une approche pure shell:

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

Explication

  • files=(*): enregistrez la liste des fichiers (également les répertoires s'il y en a, changez files=(*.txt)pour obtenir uniquement les choses avec une txtextension) dans le tableau $files.
  • num=$((${#files[@]}/8));: ${#files[@]}est le nombre d'éléments dans le tableau $files. C'est $(( ))la façon (limitée) de bash de faire de l'arithmétique. Ainsi, cette commande définit $numle nombre de fichiers divisé par 8.
  • k=1 : juste un compteur pour nommer les tarballs.
  • for ((i=0; i<${#files[@]}; i+=$num)); do: itère sur les valeurs du tableau. $iest initialisé à 0(le premier élément du tableau) et incrémenté de $num. Cela continue jusqu'à ce que nous ayons parcouru tous les éléments (fichiers).
  • tar cvzf files$i.tgz -- ${files[@]:$i:$num}: en bash, vous pouvez obtenir une tranche de tableau (partie d'un tableau) en utilisant ${array[@]:start:length}, So ${array[@]:2:3}renverra trois éléments à partir du second. Ici, nous prenons une tranche qui commence à la valeur actuelle de $iet est $numlongue. Le --est nécessaire dans le cas où l'un de vos noms de fichiers peut commencer par un -.
  • ((k++)) : incrément $k
terdon
la source
Agréable! La première fois que j'ai vu une utilisation pratique des plages d'index de tableau bash.
Joe
Très propre et succinct. Pour moi, plus compréhensible que les solutions Python bien que les deux soient assez bonnes. Vous vous demandez comment ils se comparent tous en termes de performances?
DocSalvager