各環境の特定のファイルを上書きして複数の tar.gz ファイルを生成するにはどうすればよいでしょうか?

各環境の特定のファイルを上書きして複数の tar.gz ファイルを生成するにはどうすればよいでしょうか?

ルート フォルダーがあり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 つの環境フォルダーが含まれる新しいデザインになりましたdevstageprod

(図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 つのサブフォルダーdevstageおよびがありprod、他のサブフォルダーfolder2とについてもまったく同じですfolder3。それらの各devstageおよびサブフォルダーprod内のサブフォルダーには、folder{number}それらに対して上書きされるファイルがあります。

ここで、上記の構造から、それぞれに 1 つずつ、計 3 つの異なるtar.gzファイルを生成する必要があります。devstageprod

  • 内部にどのようなファイルがあってもdev、サブフォルダー (folder1、folder2、または folder3) にも存在する場合は、サブフォルダーのファイルが上書きされます。stageprod
  • したがって、がサブフォルダーfiles1.jsonに存在し、同じファイルが のいずれかの中にも存在する場合、パッケージ化中に、環境フォルダーに存在するものを使用してサブフォルダーのファイルを上書きする必要があります。それ以外の場合は、サブフォルダーに存在するものを使用します。folder1devstageprod

最終的には、次のような 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.gzproducts-prod.gzfigure 2figure 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には環境をオーバーライドするフォルダーがありますが、ファイルはありません。そのため、その場合は、各環境固有のファイルで空のfolder2folder3も生成する必要があります。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%'
)

注意事項:

  1. これはGNUツールの構文を示しています。BSDの場合は を にfind置き換える必要があります。BSDの場合はを に置き換える必要があります。また、(<- 最後の に注意)を に置き換える必要があります。-regextype posix-extended-Etar--no-recursion-n--transform=ss-s
  2. デモンストレーションを簡単にするために、このスニペットは を含むディレクトリから実行されるものと想定しProducts$eアーカイブする「environments」ディレクトリの名前にカスタム変数を使用します。一方、は名前$r を格納するための短い名前のヘルパー変数です。Products
  3. 括弧で囲まれているため、サブシェルとなり、シェルを汚染せず$r$eコマンドラインから実行しても
  4. 元のファイルをコピーしたり、リンク/参照したりすることはなく、有効なファイル名を処理し、メモリの制約がなく、任意の数の名前を処理できます。唯一の前提は、ディレクトリ階層の最初の 2 つのレベルについてであり、最初のレベルの直下のディレクトリはすべて「環境」ディレクトリと見なされ、無視されます (に示されているものを除く$e)。

そのスニペットをfor e in dev prod stage; do ...; doneシェル ループで囲んでそのまま実行することもできます (最も外側の括弧を削除して、ループ全体を囲むこともできますfor)。

良い点は、結局のところ、非常に短く、比較的シンプルだということです。

欠点は、常にアーカイブ化されることです全て上書きされたfindファイル (つまり、基本ファイル) をダブルコマンドで最初に上書きするファイルを指定するというトリックがありtar、そのため、抽出時に上書きファイル (つまり、「環境」固有のファイル) によって上書きされます。これにより、アーカイブが大きくなり、作成時と抽出時の両方で時間がかかるようになります。このような「オーバーヘッド」が無視できるかどうかによっては、望ましくない場合があります。

そのパイプラインを文章で説明すると次のようになります。

  1. (最も外側の括弧とヘルパー変数を除く)
  2. 最初のfindコマンドは非特定ファイル(および更新に応じた先頭ディレクトリ)のリストのみを生成し、2番目のコマンドfindは環境固有のファイルのみのリストを生成します。
  3. 2つのコマンドは括弧で囲まれており、両方の出力が順番にfindパイプに送られます。tar
  4. tarファイル名を取得するためにこのようなパイプを読み取り、--transform各ファイルのパス名から「環境」コンポーネント(存在する場合)を削除してファイル名に - を付けながら、それらのファイルをアーカイブに格納します。
  5. 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%'
)

注意すべき点がいくつかあります:

  1. GNUとBSDの構文に関して先ほど述べたことはすべてfindここtarでも当てはまります。
  2. 前のソリューションと同様に、ディレクトリ階層の最初の2つのレベルに関する仮定以外には何の制約もありません。
  3. ここでは、ヌル区切りの I/O (オプション)sedを処理するためにGNU を使用していますが、これらの 2 つのコマンドは、たとえばシェル ループ (Bash バージョン 3 以上が必要) や、自信のある他の言語で簡単に置き換えることができます。唯一の推奨事項は、使用するツールがヌル区切りの I/O を処理できることです (たとえば、GNU はそれができます)。Bash ループを使用した置き換えについては、以下を参照してください。-zsedwhile read ...gawk
  4. findここでは1つのシングルを使用しています。tar
  5. コマンドsedは名前のリストを操作し、sortコマンドへの道を開きます
  6. 具体的には、最初のものはsed「environments」の名前をパスの先頭に移動し、さらにヘルパー番号をプレフィックスとして追加して、非環境ファイルの前に並べるようにします。これは、後者のファイルには並べ替えの目的で0先頭に「」を付けているからです。1
  7. このような準備により、コマンドの「目」にある名前のリストが正規化されsort、すべての名前に「環境」名が含まれなくなり、先頭にスラッシュで区切られたフィールドが同じ数だけ含まれるようになります。これは、sortのキー定義にとって重要です。
  8. 最初の方法sortは、まずファイル名に基づいてソートを適用し、同じ名前を互いに隣接させ、次にコマンドによって以前にマークされた数値0または数値に基づいてソートし、存在する場合は「環境」固有のファイルが非固有のファイルよりも前に来ることを保証します。1sed
  9. 2番目はファイル名をsort結合し(オプション)、重複した名前の最初のものだけを残します。これは、前の並べ替えにより、存在する場合は常に「環境」固有のファイルになります。-u
  10. 最後に、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

一般的な解決策

  1. ディレクトリ ツリーのコピーを作成します。スペースを節約するためにファイルをハードリンクします。
  2. コピーを変更します。(ハードリンクの場合は、安全に実行できる操作を知っておく必要があります。以下を参照してください。)
  3. コピーをアーカイブします。
  4. コピーを削除します。
  5. 必要に応じて(別の方法で変更して)繰り返します。

制限事項:

  • この例では非POSIXオプションを使用しています(Debian 10でテスト済み)。
  • ディレクトリツリーについていくつかの仮定を立てている。
  • ファイルが多すぎると失敗する可能性があります。

これを概念実証として扱い、ニーズに合わせて調整してください。

  1. コピーを作成する

    cdの親ディレクトリに移動しますProducts。このディレクトリProductsとその中のすべては、単一のファイルシステムに属している必要があります。一時ディレクトリを作成し、Productsそこに再作成します。

    mkdir -p tmp
    cp -la Products/ tmp/
    
  2. コピーの修正

    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くださいprodstageそしてこの深さにある他のディレクトリ。

  3. コピーのアーカイブ

    # still in tmp/Products because of the previous step
    cd ..
    tar cvzf "products-$dname.tgz" Products
    
  4. コピーを削除する

    # now in tmp because of the previous step
    rm -rf Products
    
  5. 繰り返し

    正しいディレクトリに戻り、今度は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

関連情報