So tarieren Sie viele Dateien ähnlicher Größe in mehrere Archive mit einer Größenbeschränkung

11

Ich bin auf Ubuntu 16.04.

Ich habe einen Ordner mit vielen Textdateien (fast 12 KB). Ich muss sie alle auf eine Website hochladen, die .tar.gzUploads akzeptiert und sie dann automatisch dekomprimiert, aber ein Limit von 10 MB (10000 KB) pro Datei hat (daher muss insbesondere jede Datei für sich dekomprimiert werden). Wenn ich tar.gzalle diese Dateien habe, ist die resultierende Datei von etwa 72 MB.

Was ich tun möchte, ist, acht .tar.gzDateien zu erstellen , deren Größe / Dimension (streng) kleiner als 10000 KB ist.

Alternativ kann man davon ausgehen, dass alle oben genannten Dateien ungefähr die gleiche Dimension haben. Daher möchte ich acht .tar.gzDateien mit jeweils mehr oder weniger der gleichen Anzahl von Dateien erstellen.

Wie kann ich eine dieser beiden Aufgaben ausführen?

Ich bin vollkommen in Ordnung mit einer Lösung, die GUI, CLI oder Scripting beinhaltet. Ich bin hier nicht auf der Suche nach Geschwindigkeit, ich muss es nur tun.

dadexix86
quelle
Vermutlich haben die 12k-Dateien, die Sie haben, Muster oder wiederholte Zeichen in ihren Namen. Sie können sie möglicherweise tarhinzufügen, indem Sie alle Dateien hinzufügen, die mit einem bestimmten Muster beginnen, bis Sie alle haben. Dies kann einfach per Skript ausgeführt werden, garantiert jedoch nicht, dass die Größe bei Bedarf unter 9 MB liegt. Sie können die Größe der zu großen Dateien jedoch manuell anpassen, indem Sie sie weiter aufteilen.
Juan Antonio

Antworten:

9

Völlig Patchwork und eine schnelle, grobe Skizze wie sie ist, aber in einem Verzeichnis mit 3000 Dateien getestet, hat das folgende Skript einen extrem schnellen Job gemacht:

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

Wie benutzt man

  • Speichern Sie es in einer leeren Datei als compress_split.py
  • Legen Sie im Kopfbereich die Anzahl der zu komprimierenden Dateien fest. In der Praxis wird es immer noch einen geben, der sich um die verbleibenden "Reste" kümmert.
  • Führen Sie es mit dem Verzeichnis mit Ihren Dateien als Argument aus:

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

nummerierte .tar.gzDateien werden in demselben Verzeichnis erstellt, in dem sich die Dateien befinden.

Erläuterung

Das Skript:

  • listet alle Dateien im Verzeichnis auf
  • CDs in das Verzeichnis, um zu verhindern, dass die Pfadinformationen zur TAR-Datei hinzugefügt werden
  • Liest die Dateiliste durch und gruppiert sie nach der eingestellten Abteilung
  • Komprimiert die Untergruppe (n) in nummerierte Dateien

BEARBEITEN

Erstellen Sie automatisch Chunks nach Größe in MB

Anspruchsvoller ist es, die maximale Größe (in MB) der Chunks als (zweites) Argument zu verwenden. Im folgenden Skript werden die Chunks in eine komprimierte Datei geschrieben, sobald der Chunk den Schwellenwert erreicht (überschreitet).

Da das Skript von den Chunks ausgelöst wird und den Schwellenwert überschreitet, funktioniert dies nur, wenn die Größe (aller) Dateien wesentlich kleiner als die Chunk-Größe ist.

Das Skript:

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

Laufen:

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

... wobei Chunksize die Größe der Eingabe für den Befehl tar ist.

In diesem sind die vorgeschlagenen Verbesserungen von @DavidFoerster enthalten. Dank viel !

Jacob Vlijm
quelle
@ dadexix86 herzlich willkommen!
Jacob Vlijm
Ich habe den Shell-Aufruf losgeworden und eine Argumentliste direkt verwendet. Trotzdem können große Argumentlisten problematisch sein, und ich werde versuchen, den tarAufruf weiter zu verbessern, indem ich die Dateiliste im Standardeingabestream bereitstelle.
David Foerster
Hallo @DavidFoerster, ich vertraue deiner Einsicht, aber was ist der Vorteil?
Jacob Vlijm
In den meisten Laufzeitumgebungen ist die Gesamtlänge der Argumentzeichenfolgen eines Befehls (weich und hart) begrenzt, die Sie schnell erreichen, wenn Sie mit Tausenden von Dateien arbeiten. Aus diesem Grund tarkönnen Sie Dateien angeben, die bei Standardeingaben mit einer geeigneten Option hinzugefügt (oder extrahiert) werden sollen.
David Foerster
@ DavidFoerster Es gibt jedoch ein Problem, das zweite läuft nicht mehr. Eigentlich tut keiner von ihnen ...
Jacob Vlijm
6

Ein reiner Shell-Ansatz:

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

Erläuterung

  • files=(*): Speichern Sie die Liste der Dateien (auch Verzeichnisse, falls vorhanden, ändern Sie in files=(*.txt), um nur Dinge mit einer txtErweiterung zu erhalten) im Array $files.
  • num=$((${#files[@]}/8));: ${#files[@]}ist die Anzahl der Elemente im Array $files. Das $(( ))ist Bashs (begrenzte) Art zu rechnen. Dieser Befehl setzt also $numdie Anzahl der Dateien geteilt durch 8.
  • k=1 : nur ein Zähler, um die Tarballs zu benennen.
  • for ((i=0; i<${#files[@]}; i+=$num)); do: iteriere über die Werte des Arrays. $iwird bei 0(dem ersten Element des Arrays) initialisiert und um erhöht $num. Dies wird fortgesetzt, bis wir alle Elemente (Dateien) durchlaufen haben.
  • tar cvzf files$i.tgz -- ${files[@]:$i:$num}: In Bash können Sie ein Array-Slice (Teil eines Arrays) mit erhalten. Es ${array[@]:start:length}werden also ${array[@]:2:3}drei Elemente ab dem zweiten zurückgegeben. Hier nehmen wir ein Slice, das beim aktuellen Wert von beginnt $iund $numElemente lang ist. Dies --wird für den Fall benötigt, dass einer Ihrer Dateinamen mit a beginnen kann -.
  • ((k++)) : Zuwachs $k
Terdon
quelle
Nett! Zum ersten Mal habe ich eine praktische Verwendung von Bash-Array-Indexbereichen gesehen.
Joe
Sehr sauber und prägnant. Für mich verständlicher als die Python-Lösungen, obwohl beide ziemlich gut sind. Frage mich, wie sie alle in der Leistung vergleichen?
DocSalvager