como criar arquivos multi-tar para uma pasta enorme

3

Eu tenho uma pasta grande com 30 milhões de arquivos pequenos. Espero fazer o backup da pasta em 30 arquivos, cada arquivo tar.gz terá arquivos de 1M. O motivo para dividir em multi-arquivos é que, para descompactar, um único arquivo grande demorará meses. O tar tar para dividir também não funcionará, porque quando descompactar o arquivo, eu tenho que catar todos os arquivos juntos.

Além disso, espero não converter cada arquivo em um novo diretório, porque mesmo o ls é muito doloroso para essa pasta enorme.

    
por Yan Zhu 20.04.2015 / 20:58

3 respostas

4

Eu escrevi este script para fazer isso. Basicamente forma uma matriz contendo os nomes dos arquivos para cada tar, então inicia tar em paralelo em todos eles . Pode não ser a maneira mais eficiente, mas fará o trabalho como você deseja. Posso esperar que consuma grandes quantidades de memória.

Você precisará ajustar as opções no início do script. Você também pode querer alterar as opções de tar cvjf na última linha (como remover a saída detalhada v para desempenho ou alterar a compactação j para z , etc ...).

Script

#!/bin/bash

# User configuratoin
#===================
files=(*.log)           # Set the file pattern to be used, e.g. (*.txt) or (*)
num_files_per_tar=5 # Number of files per tar
num_procs=4         # Number of tar processes to start
tar_file_dir='/tmp' # Tar files dir
tar_file_name_prefix='tar' # prefix for tar file names
tar_file_name="$tar_file_dir/$tar_file_name_prefix"

# Main algorithm
#===============
num_tars=$((${#files[@]}/num_files_per_tar))  # the number of tar files to create
tar_files=()  # will hold the names of files for each tar

tar_start=0 # gets update where each tar starts
# Loop over the files adding their names to be tared
for i in 'seq 0 $((num_tars-1))'
do
  tar_files[$i]="$tar_file_name$i.tar.bz2 ${files[@]:tar_start:num_files_per_tar}"
  tar_start=$((tar_start+num_files_per_tar))
done

# Start tar in parallel for each of the strings we just constructed
printf '%s\n' "${tar_files[@]}" | xargs -n$((num_files_per_tar+1)) -P$num_procs tar cjvf

Explicação

Primeiro, todos os nomes de arquivos que correspondem ao padrão selecionado são armazenados na matriz files . Em seguida, o loop for divide essa matriz e forma cadeias de caracteres das fatias. O número de fatias é igual ao número de tarballs desejados. As sequências resultantes são armazenadas na matriz tar_files . O loop for também inclui o nome do tarball resultante no início de cada string. Os elementos de tar_files tomam o seguinte formato (assumindo 5 arquivos / tarball):

tar_files[0]="tar0.tar.bz2  file1 file2 file3 file4 file5"
tar_files[1]="tar1.tar.bz2  file6 file7 file8 file9 file10"
...

A última linha do script, xargs é usada para iniciar vários tar processos (até o número máximo especificado), onde cada um processará um elemento da matriz tar_files em paralelo.

Teste

Lista de arquivos:

$ls

a      c      e      g      i      k      m      n      p      r      t
b      d      f      h      j      l      o      q      s

Tarballs gerados:     $ ls / tmp / tar *     tar0.tar.bz2 tar1.tar.bz2 tar2.tar.bz2 tar3.tar.bz2

    
por 20.04.2015 / 21:45
2

Aqui está outro script. Você pode escolher se deseja exatamente um milhão de arquivos por segmento ou precisamente 30 segmentos. Eu fui com o primeiro neste script, mas a palavra-chave split permite a escolha.

#!/bin/bash
#
DIR="$1"        # The source of the millions of files
TARDEST="$2"    # Where the tarballs should be placed

# Create the million-file segments
rm -f /tmp/chunk.*
find "$DIR" -type f | split -l 1000000 - /tmp/chunk.

# Create corresponding tarballs
for CHUNK in $(cd /tmp && echo chunk.*)
do
    test -f "$CHUNK" || continue

    echo "Creating tarball for chunk '$CHUNK'" >&2
    tar cTf "/tmp/$CHUNK" "$TARDEST/$CHUNK.tar"
    rm -f "/tmp/$CHUNK"
done

Existem várias sutilezas que podem ser aplicadas a esse script. O uso de /tmp/chunk. como o prefixo da lista de arquivos provavelmente deve ser empurrado para fora em uma declaração constante, e o código não deve realmente assumir que pode excluir qualquer coisa que corresponda a /tmp/chunk.* , mas deixei assim como uma prova de conceito em vez de um utilitário polido. Se eu estivesse usando isso, usaria mktemp para criar um diretório temporário para armazenar as listas de arquivos.

    
por 20.04.2015 / 21:59
0

Este faz exatamente o que foi pedido:

#!/bin/bash
ctr=0;
# Read 1M lines, strip newline chars, put the results into an array named "asdf"
while readarray -n 1000000 -t asdf; do
  ctr=$((${ctr}+1));
# "${asdf[@]}" expands each entry in the array such that any special characters in
# the filename won't cause problems
  tar czf /destination/path/asdf.${ctr}.tgz "${asdf[@]}";
# If you don't want compression, use this instead:
  #tar cf /destination/path/asdf.${ctr}.tar "${asdf[@]}";
# this is the canonical way to generate output
# for consumption by read/readarray in bash
done <(find /source/path -not -type d);

readarray (no bash) também pode ser usado para executar uma função de retorno de chamada, para que possa ser reescrita para se parecer com:

function something() {...}
find /source/path -not -type d \
  | readarray -n 1000000 -t -C something asdf

O GNU parallel pode ser aproveitado para fazer algo semelhante (não testado; não tenho parallel instalado em que estou, por isso estou voando):

find /source/path -not -type d -print0 \
  | parallel -j4 -d '
find /source/path -not -type d -print0 \
  | parallel -j4 -d '
find /source/path -not -type d -print0 \
  | xargs -P 4 -0 -L 1000000 bash -euc 'tar czf $(mktemp --suffix=".tgz" /destination/path/backup_XXX) "$@"'
' -N1000000 --tmpdir /destination/path --files tar cz
' -N1000000 tar czf '/destination/path/thing_backup.{#}.tgz'

Como isso não foi testado, você pode adicionar o --dry-run arg para ver o que ele realmente faz. Eu gosto mais desta, mas nem todo mundo tem parallel instalado. -j4 faz com que ele use 4 jobs por vez, -d 'find' combinado com -print0 ' parallel faz com que ele ignore caracteres especiais no nome do arquivo (espaço em branco, etc). O resto deve ser auto-explicativo.

Algo semelhante pode ser feito com xargs , mas não gosto porque gera nomes de arquivo aleatórios:

tar c /source/path | split -b $((3*1024*1024*1024)) - /destination/path/thing.tar.

Eu ainda não conheço uma maneira de gerar nomes de arquivos sequenciais.

parallel também pode ser usado, mas ao contrário de cat , não há uma maneira simples de gerar o nome do arquivo de saída, então você acaba fazendo algo estúpido / hacky assim:

cat $(\ls -1 /destination/path/thing.tar.* | sort) | tar x

O OP disse que eles não queriam usar o split ... Eu achei isso estranho, já que %code% irá se juntar a eles muito bem; isso produz um alcatrão e o divide em pedaços de 3 gb:

#!/bin/bash
ctr=0;
# Read 1M lines, strip newline chars, put the results into an array named "asdf"
while readarray -n 1000000 -t asdf; do
  ctr=$((${ctr}+1));
# "${asdf[@]}" expands each entry in the array such that any special characters in
# the filename won't cause problems
  tar czf /destination/path/asdf.${ctr}.tgz "${asdf[@]}";
# If you don't want compression, use this instead:
  #tar cf /destination/path/asdf.${ctr}.tar "${asdf[@]}";
# this is the canonical way to generate output
# for consumption by read/readarray in bash
done <(find /source/path -not -type d);

... e isso os desarruma no diretório atual:

function something() {...}
find /source/path -not -type d \
  | readarray -n 1000000 -t -C something asdf
    
por 13.02.2018 / 20:31