Como tar.gz muitos arquivos de tamanho semelhante em vários arquivos com um limite de tamanho

11

Estou no Ubuntu 16.04.

Eu tenho uma pasta com muitos arquivos de texto (quase 12k). Preciso fazer o upload de todos eles para um site que aceite .tar.gzenvios e descompacte-os automaticamente, mas tem um limite de 10 MB (10000 KB) por arquivo (portanto, cada arquivo deve ser descomprimido por conta própria). Se eu tar.gztodos esses arquivos, o arquivo resultante é de cerca de 72MB.

O que eu gostaria de fazer é criar oito .tar.gzarquivos, cada um de tamanho / dimensão (estritamente) menor que 10000 KB.

Como alternativa, pode-se supor que todos os arquivos acima tenham aproximadamente a mesma dimensão, então eu gostaria de criar oito .tar.gzarquivos com mais ou menos a mesma quantidade de arquivos cada.

Como posso executar qualquer uma dessas duas tarefas?

Estou perfeitamente bem com uma solução que envolve GUI, CLI ou script. Não estou procurando velocidade aqui, só preciso fazê-lo.

dadexix86
fonte
Presumivelmente, os arquivos de 12k que você possui terão padrões ou caracteres repetidos em seus nomes. Você pode taradicioná-los adicionando todos os arquivos começando com um determinado padrão até ter todos eles. Isso pode ser facilmente roteirizado, mas não garante que o tamanho seja menor que 9 MB, conforme necessário. No entanto, você pode ajustar manualmente o tamanho dos arquivos muito grandes, dividindo-os ainda mais.
Juan Antonio

Respostas:

9

Totalmente patchwork e um esboço rápido e atual, mas testado em um diretório com 3000 arquivos, o script abaixo fez um trabalho extremamente 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)

Como usar

  • Salve-o em um arquivo vazio como compress_split.py
  • Na seção principal, defina o número de arquivos para compactar. Na prática, sempre haverá mais uma para cuidar dos poucos "remanescentes" restantes.
  • Execute-o com o diretório com seus arquivos como argumento:

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

os .tar.gzarquivos numerados serão criados no mesmo diretório em que os arquivos estão.

Explicação

O script:

  • lista todos os arquivos no diretório
  • CDs no diretório para evitar adicionar as informações do caminho ao arquivo tar
  • lê a lista de arquivos, agrupando-os pela divisão definida
  • comprime o subgrupo (s) em arquivos numerados

EDITAR

Criar automaticamente pedaços por tamanho em mb

Mais sofisticado é usar o tamanho máximo (em mb) dos blocos como argumento (segundo). No script abaixo, os pedaços são gravados em um arquivo compactado assim que o pedaço atinge (passa) o limite.

Como o script é acionado pelos blocos, excedendo o limite, isso funcionará apenas se o tamanho dos arquivos (todos) for substancialmente menor que o tamanho do bloco.

O script:

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

Para correr:

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

... onde chunksize é o tamanho da entrada para o comando tar.

Neste, as melhorias sugeridas pelo @DavidFoerster estão incluídas. Graças muito !

Jacob Vlijm
fonte
@ dadexix86 de nada!
Jacob Vlijm
Eu me livrei da invocação do shell e usei uma lista de argumentos diretamente. Ainda assim, grandes listas de argumentos podem ser problemáticas e tentarei melhorar tarainda mais a chamada fornecendo a lista de arquivos no fluxo de entrada padrão.
David Foerster
Olá @DavidFoerster, confio na sua compreensão, mas qual é a vantagem?
Jacob Vlijm
A maioria dos ambientes de tempo de execução tem um limite (flexível e flexível) no comprimento total das sequências de argumentos de um comando que você alcançará rapidamente ao operar em milhares de arquivos. É por isso que tarpermite especificar arquivos para adicionar (ou extrair) na entrada padrão com uma opção apropriada.
David Foerster
@DavidFoerster Há um problema, porém, o segundo não funciona mais. Na verdade, nenhum deles faz ...
Jacob Vlijm
6

Uma abordagem pura do 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

Explicação

  • files=(*): salve a lista de arquivos (também diretórios, se houver algum, altere files=(*.txt)para obter apenas itens com uma txtextensão) na matriz $files.
  • num=$((${#files[@]}/8));: ${#files[@]}é o número de elementos na matriz $files. A $(( ))é a maneira do bash (limitada) de fazer aritmética. Portanto, esse comando define $numo número de arquivos dividido por 8.
  • k=1 : apenas um contador para nomear os tarballs.
  • for ((i=0; i<${#files[@]}; i+=$num)); do: itere sobre os valores da matriz. $ié inicializado em 0(o primeiro elemento da matriz) e incrementado por $num. Isso continua até passarmos por todos os elementos (arquivos).
  • tar cvzf files$i.tgz -- ${files[@]:$i:$num}: no bash, você pode obter uma fatia da matriz (parte de uma matriz) usando ${array[@]:start:length}, Então ${array[@]:2:3}retornará três elementos a partir do segundo. Aqui, estamos pegando uma fatia que começa no valor atual de $ie possui $numelementos. Isso --é necessário caso qualquer um dos seus nomes de arquivos possa começar com a -.
  • ((k++)) : incremento $k
Terdon
fonte
Agradável! Primeira vez que vi um uso prático dos intervalos de índices do array bash.
10116 Joe
Muito limpo e sucinto. Para mim, mais compreensível que as soluções Python, embora ambas sejam muito boas. Pergunto como todos eles se comparam no desempenho?
DocSalvager