
ルート フォルダーがありProducts
、その中に多数のサブフォルダーがあります。各サブフォルダーには、現在多数のファイルがあります。簡単にするために、サブフォルダーの名前をfolder{number}
、ファイル名をとしましたfiles{number}.json
が、通常は異なる名前が付けられています。
通常、ルート フォルダー内には 20 個の異なるサブフォルダーがあり、各サブフォルダーには最大約 30 個のファイルがあります。
(図1)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
tar.gz
今、私は以下のコマンドを実行してこれらすべてをファイルに圧縮しています-
tar cvzf ./products.tgz Products
質問:-
Products
以下に示すように、ルート フォルダー内の各サブフォルダーに 、 という 3 つの環境フォルダーが含まれる新しいデザインになりましたdev
。stage
prod
(図2)
Products
├── folder1
│ ├── dev
│ │ └── files1.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ │ └── files5.json
│ ├── files4.json
│ ├── files5.json
│ ├── files6.json
│ ├── prod
│ │ └── files5.json
│ └── stage
│ └── files5.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
たとえば、folder1
サブフォルダー内にはさらに 3 つのサブフォルダーdev
、stage
およびがありprod
、他のサブフォルダーfolder2
とについてもまったく同じですfolder3
。それらの各dev
、stage
およびサブフォルダーprod
内のサブフォルダーには、folder{number}
それらに対して上書きされるファイルがあります。
ここで、上記の構造から、それぞれに 1 つずつ、計 3 つの異なるtar.gz
ファイルを生成する必要があります。dev
stage
prod
- 内部にどのようなファイルがあっても
dev
、サブフォルダー (folder1、folder2、または folder3) にも存在する場合は、サブフォルダーのファイルが上書きされます。stage
prod
- したがって、がサブフォルダー
files1.json
に存在し、同じファイルが のいずれかの中にも存在する場合、パッケージ化中に、環境フォルダーに存在するものを使用してサブフォルダーのファイルを上書きする必要があります。それ以外の場合は、サブフォルダーに存在するものを使用します。folder1
dev
stage
prod
最終的には、次のような 3 つの異なる構造が作成されます。1 つは 用dev
、1 つは 用、stage
もう 1 つは 用です。prod
フォルダー 1 (または 2 と 3) には、優先される環境にあるファイルと、オーバーライドされないその他のファイルが格納されます。
(図3)
Products
├── folder1
│ ├── files1.json
│ ├── files2.json
│ └── files3.json
├── folder2
│ ├── files4.json
│ ├── files5.json
│ └── files6.json
└── folder3
├── files10.json
├── files7.json
├── files8.json
└── files9.json
そして、 からとを生成する必要があります。products-dev.gz
これには、のようなデータが含まれますが、各環境に固有です。唯一の違いは、各サブフォルダーのフォルダー 1 (2 または 3) には、特定の環境フォルダーから優先的に上書きされるファイルがあり、残りはサブフォルダーからのみ使用されることです。products-stage.gz
products-prod.gz
figure 2
figure 3
これは Linux コマンドで実行できますか? 唯一混乱しているのは、特定のサブフォルダー内の特定の環境ファイルを上書きし、tar.gz
その中に 3 つの異なるファイルを生成する方法です。
アップデート:
以下のようなケースも考慮してください。
Products
├── folder1
│ ├── dev
│ │ ├── files1.json
│ │ └── files5.json
│ ├── files1.json
│ ├── files2.json
│ ├── files3.json
│ ├── prod
│ │ ├── files10.json
│ │ └── files1.json
│ └── stage
│ └── files1.json
├── folder2
│ ├── dev
│ ├── prod
│ └── stage
└── folder3
├── dev
├── prod
└── stage
ご覧のとおりfolder2
、 とfolder3
には環境をオーバーライドするフォルダーがありますが、ファイルはありません。そのため、その場合は、各環境固有のファイルで空のfolder2
とfolder3
も生成する必要があります。tar.gz
答え1
方法は多数ありますが、オーバーライドのケースを処理するには、いずれも何らかの複雑さが必要になります。
少し長いですが、ワンライナーとして、1 回の反復、つまり 1 つの「環境」ディレクトリに対して次のように実行できます。
(r=Products; e=stage; (find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0; find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0) | tar --null --no-recursion -czf "$r-$e.tgz" -T- --transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%')
よりよく観察するために分解すると次のようになります。
(
r=Products; e=stage
(
find -- "$r" -regextype posix-extended -maxdepth 2 \( -regex '^[^/]+(/[^/]+)?' -o ! -type d \) -print0
find -- "$r" -mindepth 1 -path "$r/*/$e/*" -print0
) \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
注意事項:
- これはGNUツールの構文を示しています。BSDの場合は を に
find
置き換える必要があります。BSDの場合はを に置き換える必要があります。また、(<- 最後の に注意)を に置き換える必要があります。-regextype posix-extended
-E
tar
--no-recursion
-n
--transform=s
s
-s
- デモンストレーションを簡単にするために、このスニペットは を含むディレクトリから実行されるものと想定し
Products
、$e
アーカイブする「environments」ディレクトリの名前にカスタム変数を使用します。一方、は名前$r
を格納するための短い名前のヘルパー変数です。Products
- 括弧で囲まれているため、サブシェルとなり、シェルを汚染せず
$r
、$e
コマンドラインから実行しても - 元のファイルをコピーしたり、リンク/参照したりすることはなく、有効なファイル名を処理し、メモリの制約がなく、任意の数の名前を処理できます。唯一の前提は、ディレクトリ階層の最初の 2 つのレベルについてであり、最初のレベルの直下のディレクトリはすべて「環境」ディレクトリと見なされ、無視されます (に示されているものを除く
$e
)。
そのスニペットをfor e in dev prod stage; do ...; done
シェル ループで囲んでそのまま実行することもできます (最も外側の括弧を削除して、ループ全体を囲むこともできますfor
)。
良い点は、結局のところ、非常に短く、比較的シンプルだということです。
欠点は、常にアーカイブ化されることです全ての上書きされたfind
ファイル (つまり、基本ファイル) をダブルコマンドで最初に上書きするファイルを指定するというトリックがありtar
、そのため、抽出時に上書きファイル (つまり、「環境」固有のファイル) によって上書きされます。これにより、アーカイブが大きくなり、作成時と抽出時の両方で時間がかかるようになります。このような「オーバーヘッド」が無視できるかどうかによっては、望ましくない場合があります。
そのパイプラインを文章で説明すると次のようになります。
- (最も外側の括弧とヘルパー変数を除く)
- 最初の
find
コマンドは非特定ファイル(および更新に応じた先頭ディレクトリ)のリストのみを生成し、2番目のコマンドfind
は環境固有のファイルのみのリストを生成します。 - 2つのコマンドは括弧で囲まれており、両方の出力が順番に
find
パイプに送られます。tar
tar
ファイル名を取得するためにこのようなパイプを読み取り、--transform
各ファイルのパス名から「環境」コンポーネント(存在する場合)を削除してファイル名に - を付けながら、それらのファイルをアーカイブに格納します。- 2つの
find
コマンドは1つではなく分離されており、順番に実行されるため、tar
環境固有のファイルよりも先に非固有のファイルが生成され(使用するため)、前述のトリックが可能になります。
オーバーヘッドを避けるために常にすべて上書きされたファイルを完全に削除するには、さらに複雑な処理が必要になります。 1 つの方法は次のようになります。
# still a pipeline, but this time I won't even pretend it to be a one-liner
(
r=Products; e=stage; LC_ALL=C
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '^([^/]+/){2}[^/]+' ! -type d \) -o -regex '^[^/]+(/[^/]+)?' \) -print0 \
| sed -zE '\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE 's%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'
)
注意すべき点がいくつかあります:
- GNUとBSDの構文に関して先ほど述べたことはすべて
find
ここtar
でも当てはまります。 - 前のソリューションと同様に、ディレクトリ階層の最初の2つのレベルに関する仮定以外には何の制約もありません。
- ここでは、ヌル区切りの I/O (オプション)
sed
を処理するためにGNU を使用していますが、これらの 2 つのコマンドは、たとえばシェル ループ (Bash バージョン 3 以上が必要) や、自信のある他の言語で簡単に置き換えることができます。唯一の推奨事項は、使用するツールがヌル区切りの I/O を処理できることです (たとえば、GNU はそれができます)。Bash ループを使用した置き換えについては、以下を参照してください。-z
sed
while read ...
gawk
find
ここでは1つのシングルを使用しています。tar
- コマンド
sed
は名前のリストを操作し、sort
コマンドへの道を開きます - 具体的には、最初のものは
sed
「environments」の名前をパスの先頭に移動し、さらにヘルパー番号をプレフィックスとして追加して、非環境ファイルの前に並べるようにします。これは、後者のファイルには並べ替えの目的で0
先頭に「」を付けているからです。1
- このような準備により、コマンドの「目」にある名前のリストが正規化され
sort
、すべての名前に「環境」名が含まれなくなり、先頭にスラッシュで区切られたフィールドが同じ数だけ含まれるようになります。これは、sort
のキー定義にとって重要です。 - 最初の方法
sort
は、まずファイル名に基づいてソートを適用し、同じ名前を互いに隣接させ、次にコマンドによって以前にマークされた数値0
または数値に基づいてソートし、存在する場合は「環境」固有のファイルが非固有のファイルよりも前に来ることを保証します。1
sed
- 2番目はファイル名を
sort
結合し(オプション)、重複した名前の最初のものだけを残します。これは、前の並べ替えにより、存在する場合は常に「環境」固有のファイルになります。-u
- 最後に、2番目の
sed
コマンドは最初のコマンドで行われた処理を元に戻し、tar
アーカイブのファイル名を変更します。
このような長いパイプラインの中間部分を探索することに興味がある場合は、それらはすべてゼロは で区切られた名前なので、画面上ではうまく表示されません。中間出力のいずれか (つまり、少なくとも を除くtar
) を にパイプしてtr '\0' '\n'
、人間が理解しやすい出力を表示することができます。ただし、改行を含むファイル名は画面上で 2 行にまたがることを覚えておいてください。
完全にパラメータ化された関数/スクリプトにしたり、たとえば以下のように「environments」ディレクトリの任意の名前を自動的に検出したりすることで、いくつかの改善を行うことができます。
重要: コメントは対話型シェルではうまく受け入れられない可能性があるので注意してください
(
export r=Products LC_ALL=C
cd -- "$r/.." || exit
# make arguments out of all directories lying at the second level of the hierarchy
set -- "$r"/*/*/
# then expand all such paths found, take their basenames only, uniquify them, and pass them along xargs down to a Bash pipeline the same as above
printf %s\\0 "${@#*/*/}" \
| sort -zu \
| xargs -0I{} sh -c '
e="${1%/}"
echo --- "$e" ---
find -- "$r" -regextype posix-extended \( -path "$r/*/$e/*" -o \( -regex '\''^([^/]+/){2}[^/]+'\'' ! -type d \) -o -regex '\''^[^/]+(/[^/]+)?'\'' \) -print0 \
| sed -zE '\''\%^(([^/]+/){2})([^/]+/)%s%%0/\3\1%;t;s%^%1//%'\'' \
| sort -zt/ -k 3 -k 1,1n \
| sort -zut/ -k 3 \
| sed -zE '\''s%^[01]/(([^/]+/)|/)(([^/]+/?){2})%\3\2%'\'' \
| tar --null --no-recursion -czf "$r-$e.tgz" -T- \
--transform=s'\''%^\(\([^/]\{1,\}/\)\{2\}\)[^/]\{1,\}/%\1%'\''
' packetizer {}
)
最初のsed
コマンドを Bash ループに置き換える例:
(IFS=/; while read -ra parts -d $'\0'; do
if [ "${#parts[@]}" -gt 3 ]; then
env="${parts[2]}"; unset parts[2]
printf 0/%s/%s\\0 "$env" "${parts[*]}"
else
printf 1//%s\\0 "${parts[*]}"
fi
done)
2 番目のコマンドの場合sed
:
(IFS=/; while read -ra parts -d $'\0'; do
printf %s "${parts[*]:2:2}" "/${parts[1]:+${parts[1]}/}" "${parts[*]:4}"
printf \\0
done)
両方のスニペットは、上記のパイプライン内のそれぞれのコマンドのドロップイン置換となるようにsed
、囲む括弧を必要とし、もちろん、sh -c
の後の部分xargs
は に変換する必要がありますbash -c
。
答え2
一般的な解決策
- ディレクトリ ツリーのコピーを作成します。スペースを節約するためにファイルをハードリンクします。
- コピーを変更します。(ハードリンクの場合は、安全に実行できる操作を知っておく必要があります。以下を参照してください。)
- コピーをアーカイブします。
- コピーを削除します。
- 必要に応じて(別の方法で変更して)繰り返します。
例
制限事項:
- この例では非POSIXオプションを使用しています(Debian 10でテスト済み)。
- ディレクトリツリーについていくつかの仮定を立てている。
- ファイルが多すぎると失敗する可能性があります。
これを概念実証として扱い、ニーズに合わせて調整してください。
コピーを作成する
cd
の親ディレクトリに移動しますProducts
。このディレクトリProducts
とその中のすべては、単一のファイルシステムに属している必要があります。一時ディレクトリを作成し、Products
そこに再作成します。mkdir -p tmp cp -la Products/ tmp/
コピーの修正
2つのディレクトリツリー内のファイルはハードリンクされています。コンテンツ元のデータが変更されます。ディレクトリに保持されている情報を変更する操作は安全であり、他のツリーで実行された場合、元のデータは変更されません。これらは次のとおりです。
- ファイルの削除、
- ファイル名の変更、
- ファイルの移動(これには、 を使用したファイルの移動も含まれます
mv
)、 - 完全に独立したファイルを作成します。
あなたの場合、
dev
適切な深さで名前が付けられたすべてのディレクトリの内容を 1 レベル上に移動します。cd tmp/Products dname=dev find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
ノート:
mv -- * ../
傾向があるargument list too long
、- デフォルトでは
*
ドットファイルと一致しません。
次にディレクトリを削除します:
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
これにより、空になり不要になった , が削除されることに注意して
dev
くださいprod
。stage
そしてこの深さにある他のディレクトリ。コピーのアーカイブ
# still in tmp/Products because of the previous step cd .. tar cvzf "products-$dname.tgz" Products
コピーを削除する
# now in tmp because of the previous step rm -rf Products
繰り返し
正しいディレクトリに戻り、今度は
dname=stage
; などを使用してやり直します。
サンプルスクリプト(手っ取り早い)
#!/bin/bash
dir=Products
[ -d "$dir" ] || exit 1
mkdir -p tmp
for dname in dev prod stage; do
(
cp -la "$dir" tmp/
cd "tmp/$dir"
[ "$?" -eq 0 ] || exit 1
find . -mindepth 2 -maxdepth 2 -type d -name "$dname" -exec sh -c 'cd "$1" && mv -f -- * ../' sh {} \;
find . -mindepth 2 -maxdepth 2 -type d -exec rm -rf {} +
cd ..
[ "$?" -eq 0 ] || exit 1
tar cvzf "${dir,,}-$dname.tgz" "$dir"
rm -rf "$dir" || exit 1
) || exit "$?"
done
答え3
私はそれをもう少し汎用的にし、ソースディレクトリを実際に変更せずに、非自明なファイル名で作業しました
Products
引数として与えられます。キーワードはdev prod stage
スクリプト内にハードコードされています(ただし簡単に変更できます)
注: これはGNU固有--transform
の-print0
-z
拡張機能です
スクリプトを実行する
./script Products
#!/bin/sh
# environment
subdirs="dev prod stage"
# script requires arguments
[ -n "$1" ] || exit 1
# remove trailing /
while [ ${i:-0} -le $# ]
do
i=$((i+1))
dir="$1"
while [ "${dir#"${dir%?}"}" = "/" ]
do
dir="${dir%/}"
done
set -- "$@" "$dir"
shift
done
# search string
for sub in $subdirs
do
[ -n "$search" ] && search="$search -o -name $sub" || search="( -name $sub"
done
search="$search )"
# GNU specific zero terminated handling for non-trivial directory names
excludes="$excludes $(find -L "$@" -type d $search -print0 | sed -z 's,[^/]*/,*/,g' | sort -z | uniq -z | xargs -0 printf '--exclude=%s\n')"
# for each argument
for dir in "$@"
do
# for each environment
[ -e "$dir" ] || continue
for sub in $subdirs
do
# exclude other subdirs
exclude=$(echo "$excludes" | grep -v "$sub")
# # exclude files that exist in subdir (at least stable against newlines and spaces in file names)
# include=$(echo "$excludes" | grep "$sub" | cut -d= -f2)
# [ -n "$include" ] && files=$(find $include -mindepth 1 -maxdepth 1 -print0 | tr '\n[[:space:]]' '?' | sed -z "s,/$sub/,/," | xargs -0 printf '--exclude=%s\n')
# exclude="$exclude $files"
# create tarball archive
archive="${dir##*/}-${sub}.tgz"
[ -f "$archive" ] && echo "WARNING: '$archive' is overwritten"
tar --transform "s,/$sub$,," --transform "s,/$sub/,/," $exclude -czhf "$archive" "$dir"
done
done
アーカイブ内に重複があることに気付くかもしれません。tar
ディレクトリを再帰的に下降し、復元時に深いファイルは上書きする親ディレクトリ上のファイル
しかし、一貫した動作に対してもう少しテストする必要があります(それについてはよくわかりません)。適切な方法は、exlude files1.json
+ですがfiles5.json
、残念ながら-X
動作しません。--null
この動作を信頼できない場合、またはアーカイブ内に重複したファイルを置きたくない場合は、単純なファイル名に対して除外を追加できます。コメントを外す上記のコードtar
。ファイル名には改行と空白を使用できますが、?
除外パターンのワイルドカードで除外されるため、理論的には予想よりも多くのファイルが除外される可能性があります(そのパターンに一致する類似のファイルがある場合)。
echo
を前に置けばtar
、スクリプトが次のコマンドを生成するのがわかります。
tar --transform 's,/dev$,,' --transform 's,/dev/,/,' --exclude=*/*/prod --exclude=*/*/stage -czhf Products-dev.tgz Products
tar --transform 's,/prod$,,' --transform 's,/prod/,/,' --exclude=*/*/dev --exclude=*/*/stage -czhf Products-prod.tgz Products
tar --transform 's,/stage$,,' --transform 's,/stage/,/,' --exclude=*/*/dev --exclude=*/*/prod -czhf Products-stage.tgz Products