私は3000万の小さなファイルを含む大きなフォルダを持っています。フォルダを30のアーカイブにバックアップしたいと思っています。各tar.gzファイルには100万個のファイルがあります。複数のアーカイブに分割する理由は、1つの大きなアーカイブを解凍するのに1か月かかるためです。ファイルを解凍するときにすべてのアーカイブをまとめる必要があるため、分割するパイプtarも機能しません。
また、この巨大なフォルダではlsでさえ非常に苦痛なので、各ファイルを新しいディレクトリにmvしないことを望んでいます。
私はそれを行うためにこのbashスクリプトを書きました。基本的に、各tarに入るファイルの名前を含む配列を形成し、次にtar
をすべてのファイルで並列で開始します。それは最も効率的な方法ではないかもしれませんが、それはあなたが望むように仕事を成し遂げます。ただし、大量のメモリを消費することが予想されます。
スクリプトの最初でオプションを調整する必要があります。最後の行のtarオプションcvjf
を変更することもできます(パフォーマンスのために詳細出力v
を削除したり、圧縮j
をz
に変更したりします。など...)。
#!/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
これが別のスクリプトです。セグメントごとに正確に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
を使用して、ファイルリストを保持するための一時ディレクトリを作成します。
これは、要求されたことを正確に実行します。
#!/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