web-dev-qa-db-ja.com

巨大なフォルダのマルチtarアーカイブを作成する方法

私は3000万の小さなファイルを含む大きなフォルダを持っています。フォルダを30のアーカイブにバックアップしたいと思っています。各tar.gzファイルには100万個のファイルがあります。複数のアーカイブに分割する理由は、1つの大きなアーカイブを解凍するのに1か月かかるためです。ファイルを解凍するときにすべてのアーカイブをまとめる必要があるため、分割するパイプtarも機能しません。

また、この巨大なフォルダではlsでさえ非常に苦痛なので、各ファイルを新しいディレクトリにmvしないことを望んでいます。

5
Yan Zhu

私はそれを行うためにこのbashスクリプトを書きました。基本的に、各tarに入るファイルの名前を含む配列を形成し、次にtarすべてのファイルで並列で開始します。それは最も効率的な方法ではないかもしれませんが、それはあなたが望むように仕事を成し遂げます。ただし、大量のメモリを消費することが予想されます。

スクリプトの最初でオプションを調整する必要があります。最後の行のtarオプションcvjfを変更することもできます(パフォーマンスのために詳細出力vを削除したり、圧縮jzに変更したりします。など...)。

脚本

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

説明

まず、選択したパターンに一致するすべてのファイル名が配列filesに格納されます。次に、forループはこの配列をスライスし、スライスから文字列を形成します。スライスの数は、目的のタールボールの数と同じです。結果の文字列は、配列tar_filesに格納されます。 forループは、結果のtarballの名前も各文字列の先頭に追加します。 tar_filesの要素は、次の形式を取ります(5ファイル/ tarballを想定)。

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

スクリプトの最後の行であるxargsは、複数のtarプロセス(指定された最大数まで)を開始するために使用され、各プロセスはtar_files配列の1つの要素を並行して処理します。

テスト

ファイルのリスト:

$ls

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

生成されたTarball:$ ls/tmp/tar * tar0.tar.bz2 tar1.tar.bz2 tar2.tar.bz2 tar3.tar.bz2

6
Bichoy

これが別のスクリプトです。セグメントごとに正確に100万ファイルにするか、正確に30セグメントにするかを選択できます。このスクリプトでは前者を使用しましたが、splitキーワードでどちらを選択することもできます。

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

このスクリプトに適用できる優れた点がいくつかあります。ファイルリストプレフィックスとしての/tmp/chunk.の使用は、おそらく定数宣言にプッシュする必要があり、コードは/tmp/chunk.*に一致するものを削除できると実際に想定するべきではありませんが、このままにしておきます洗練されたユーティリティではなく、概念実証として。これを使用している場合は、mktempを使用して、ファイルリストを保持するための一時ディレクトリを作成します。

3
roaima

これは、要求されたことを正確に実行します。

#!/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(bash内)を使用してコールバック関数を実行することもできるため、次のように書き直すことができます。

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

GNU parallelを利用して、同様のことを行うことができます(テストされていません。現在の場所にparallelがインストールされていないので、ウィングしています):

find /source/path -not -type d -print0 \
  | parallel -j4 -d '\0' -N1000000 tar czf '/destination/path/thing_backup.{#}.tgz'

これはテストされていないため、--dry-run引数を追加して、実際に何が行われるかを確認できます。私はこれが一番好きですが、誰もがparallelをインストールしているわけではありません。 -j4は、一度に4つのジョブを使用し、-d '\0'find-print0と組み合わせて、ファイル名の特殊文字(空白など)を無視します。残りは自明でなければなりません。

parallelでも同様のことができますが、ランダムなファイル名が生成されるため、私はそれが好きではありません。

find /source/path -not -type d -print0 \
  | parallel -j4 -d '\0' -N1000000 --tmpdir /destination/path --files tar cz

シーケンシャルファイル名を生成する方法を[まだ?]知りません。

xargsも使用できますが、parallelとは異なり、出力ファイル名を生成する簡単な方法がないため、次のような愚かな/ハッキーなことをすることになります。

find /source/path -not -type d -print0 \
  | xargs -P 4 -0 -L 1000000 bash -euc 'tar czf $(mktemp --suffix=".tgz" /destination/path/backup_XXX) "$@"'

OPは、分割を使用したくないと言っていました... catが正常に再参加するので、奇妙に思えたと思いました。これによりtarが生成され、3GBのチャンクに分割されます。

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

...そしてこれはそれらを現在のディレクトリに解凍します:

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