Как tar.gz много файлов одинакового размера в несколько архивов с ограничением размера

Я на Ubuntu 16.04.

У меня есть папка с большим количеством текстовых файлов (почти 12k). Мне нужно загрузить их все на сайт, который принимает .tar.gz загружает, а затем распаковывает их автоматически, но имеет ограничение 10 МБ (10000 КБ) на файл (поэтому, в частности, каждый файл должен быть распакован сам по себе). Если я tar.gz все эти файлы в результате около 72 МБ.

Что я хотел бы сделать, это создать восемь .tar.gz файлы, каждый из которых имеет размер / размер (строго) менее 10000 КБ.

В качестве альтернативы можно предположить, что все приведенные выше файлы имеют примерно одинаковое измерение, поэтому я хотел бы создать восемь .tar.gz файлы с более или менее одинаковым количеством файлов каждый.

Как я могу выполнить любую из этих двух задач?

Я совершенно в порядке с решением, которое включает в себя GUI, CLI или сценарии. Я не ищу скорость здесь, мне просто нужно это сделать.

2 ответа

Решение

Абсолютно лоскутная работа и быстрый, грубый набросок, как он есть, но протестированный на каталоге с 3000 файлами, приведенный ниже скрипт сделал чрезвычайно быструю работу:

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

Как пользоваться

  • Сохраните его в пустой файл как compress_split.py
  • В разделе заголовка установите количество файлов для сжатия. На практике всегда будет еще один, чтобы позаботиться об оставшихся немногих "остатках".
  • Запустите его с каталогом с вашими файлами в качестве аргумента:

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

нумерованный .tar.gz Файлы будут созданы в том же каталоге, где находятся файлы.

объяснение

Сценарий:

  • перечисляет все файлы в каталоге
  • CD в ​​каталог, чтобы предотвратить добавление информации о пути к файлу tar
  • читает список файлов, группируя их по заданному разделению
  • сжимает подгруппу (ы) в пронумерованные файлы

РЕДАКТИРОВАТЬ

Автоматически создавать куски по размеру в мб

Более сложным является использование максимального размера (в мб) кусков в качестве (второго) аргумента. В приведенном ниже сценарии чанки записываются в сжатый файл, как только чанк достигает (проходит) пороговое значение.

Поскольку сценарий запускается порциями, превышающими порог, это будет работать только в том случае, если размер (всех) файлов существенно меньше размера порции.

Сценарий:

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

Бежать:

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

... где chunk size - это размер ввода для команды tar.

В этом предложены улучшения, предложенные @DavidFoerster. Большое спасибо!

Чистый подход оболочки:

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

объяснение

  • files=(*): сохранить список файлов (также каталоги, если они есть, изменить на files=(*.txt) чтобы получить только вещи с txt расширение) в массиве $files,
  • num=$((${#files[@]}/8));: ${#files[@]} количество элементов в массиве $files, $(( )) это bash (ограниченный) способ выполнения арифметики. Итак, эта команда устанавливает $num на количество файлов, деленное на 8.
  • k=1: просто счетчик, чтобы назвать тарболы.
  • for ((i=0; i<${#files[@]}; i+=$num)); do: перебрать значения массива. $i инициализируется в 0 (первый элемент массива) и увеличивается на $num, Это продолжается до тех пор, пока мы не пройдем все элементы (файлы).
  • tar cvzf files$i.tgz -- ${files[@]:$i:$num}: в bash вы можете получить фрагмент массива (часть массива), используя ${array[@]:start:length}, Так ${array[@]:2:3} вернет три элемента, начиная со второго. Здесь мы берем фрагмент, который начинается с текущего значения $i и является $num элементы длинные. -- нужен в случае, если любое из ваших имен файлов может начинаться с -,
  • ((k++)): приращение $k
Другие вопросы по тегам