각 환경에 대해 특정 파일을 재정의하여 여러 tar.gz 파일을 생성하는 방법은 무엇입니까?

각 환경에 대해 특정 파일을 재정의하여 여러 tar.gz 파일을 생성하는 방법은 무엇입니까?

루트 폴더가 있고 Products그 안에 여러 하위 폴더가 있습니다. 각 하위 폴더에는 현재 여러 개의 파일이 있습니다. 단순화를 위해 하위 폴더 이름은 as folder{number}와 파일 이름은 as 로 생각했지만 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

질문:-

루트 폴더 내부의 각 하위 폴더에는 , 및 Products3개의 환경 폴더가 있는 아래와 같이 새로운 디자인이 생겼습니다 .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개의 하위 폴더가 더 있고 다른 dev하위 폴더 및 에도 정확히 동일합니다 . 하위 폴더 내의 각 및 하위 폴더에는 재정의 된 파일 이 있습니다.stageprodfolder2folder3devstageprodfolder{number}

이제 세 가지 다른 파일을 생성해야 합니다 tar.gz. 즉, 각각에 대해 하나씩 dev, stage그리고 prod위의 구조에서 생성해야 합니다.

  • 내부에 있는 파일이 무엇 dev이든 하위 폴더(folder1,folder2 또는folder3)에도 있는 경우 해당 하위 폴더 파일을 재정의합니다 stage.prod
  • 따라서 하위 폴더 files1.json에 있고 동일한 파일이 다음 중 하나에도 있는 경우 패키징하는 동안 해당 환경 폴더에 있는 파일을 모두 사용해야 하며 하위 폴더 파일을 재정의해야 합니다. 그렇지 않으면 하위 폴더에 있는 파일을 모두 사용해야 합니다. 폴더.folder1devstageprod

결국 나는 이와 같은 3가지 다른 구조를 갖게 될 것입니다. 하나는 dev, 하나는 for, stage다른 하나는 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

많은 방법이 있을 수 있지만 재정의 사례를 처리하려면 모두 일종의 복잡성이 필요합니다.

한 줄로 작성하면 약간 길지만 한 번의 반복, 즉 하나의 "환경" 디렉토리에 대해 다음과 같이 수행할 수 있습니다.

(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의 경우 just로 find바꿔야 하고 BSD의 경우 just asel as (<- 최종 참고 ) 로 바꿔야 합니다.-regextype posix-extended-Etar--no-recursion-n--transform=ss-s
  2. 설명을 단순화하기 위해 코드 조각은 가 포함된 디렉터리에서 실행되는 것으로 가정 하고 보관할 "environments" 디렉터리 이름에 대한 Products사용자 지정 변수를 사용하는 반면, 이름을  포함하는 짧은 이름의 도우미 변수입니다.$e$rProducts
  3. 괄호로 묶어서 하위 쉘로 만듭니다. 쉘을 오염시키지 않고 명령줄에서 실행해야 하기 때문 $r입니다 $e.
  4. 원본 파일을 복사하거나 링크/참조하지 않으며, 유효한 파일 이름을 처리하고, 메모리 제약이 없으며, 이름의 양에 관계없이 처리할 수 있습니다. 유일한 가정은 첫 번째 수준 바로 아래의 모든 디렉터리가 "환경" 디렉터리로 간주되어 무시된다는 점에서 디렉터리 계층 구조의 처음 두 수준에 관한 것입니다( 에 표시된 디렉터리 제외 $e).

간단히 해당 조각을 for e in dev prod stage; do ...; done쉘 루프에 넣고 그냥 갈 수도 있습니다. (아마도 가장 바깥쪽 괄호를 제거하고 전체 for루프를 둘러쌀 수도 있습니다).

장점은 결국 매우 짧고 상대적으로 간단하다는 것입니다.

단점은 항상 보관된다는 것입니다.모두그만큼재정의됨파일(예: 기본 파일), 이중 명령이 재정의할 파일을 먼저 find공급 하므로 추출 중에 재정의 파일(예: "환경" 특정 파일)이 해당 파일을 덮어쓰게 됩니다. tar이로 인해 생성 및 추출 중에 더 많은 시간이 소요되는 더 큰 아카이브가 발생하며 이러한 "오버헤드"가 무시할 수 있는지 여부에 따라 바람직하지 않을 수 있습니다.

산문에서 설명하는 파이프라인은 다음과 같습니다.

  1. (가장 바깥쪽 괄호와 도우미 변수 제외)
  2. 첫 번째 find명령은 비특정 파일(및 업데이트에 따른 주요 디렉터리) 목록만 생성하고, 두 번째 명령은 find모든 환경 관련 파일 목록만 생성합니다.
  3. 두 명령은 그 자체로 괄호 안에 있으므로 두 명령의 출력이 차례로 find파이프에 공급됩니다.tar
  4. tar파일 이름을 얻기 위해 이러한 파이프를 읽고 해당 파일을 아카이브에 저장하는 동시에 --transform각 파일의 경로 이름에서 "환경" 구성 요소(있는 경우)를 제거하여 이름을 지정합니다.
  5. find명령은 하나가 아닌 분리되어 하나씩 실행되므로 tar환경별 파일보다 비특정 파일이 생성(소비용)되기 때문에 앞에서 설명한 트릭이 가능합니다.

포함에 따른 오버헤드를 피하기 위해항상 모두재정의된 파일을 실제로 제거하려면 추가 복잡성이 필요한 파일입니다. 한 가지 방법은 다음과 같을 수 있습니다.

# 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 구문에 관해 언급한 모든 내용 findtar여기에도 적용됩니다.
  2. 이전 솔루션과 마찬가지로 디렉터리 계층의 처음 두 수준에 대한 가정 외에는 어떠한 제약도 없습니다.
  3. sed여기서는 nul로 구분된 I/O(옵션)를 처리하기 위해 GNU를 사용하고 있지만 이 두 명령을 쉘 루프(Bash 버전 3 이상이 필요함)나 자신 있다고 생각하는 다른 언어 로 -z쉽게 대체할 수 있습니다. 유일한 권장 사항은 사용하는 도구가 Null로 구분된 I/O를 처리할 수 있다는 것입니다(예: GNU에서는 이를 수행할 수 있습니다). Bash 루프를 사용한 대체 방법은 아래를 참조하세요.sedwhile read ...gawk
  4. 나는 여기에서 하나의 싱글을 사용합니다 find. 왜냐하면 나는 암시적인 행동에 의존하지 않기 때문입니다.tar
  5. 명령 sed은 이름 목록을 조작하여 sort명령 의 길을 닦습니다.
  6. 특히, 첫 번째는 sed경로 시작 부분에서 "환경" 이름을 이동하고, 0환경이 아닌 파일보다 먼저 정렬되도록 도우미 번호를 접두어로 붙입니다 1. 정렬
  7. 이러한 준비는 명령의 "눈"에 있는 이름 목록을 정규화하여 sort"환경" 이름이 없는 모든 이름을 만들고 모두 처음에 슬래시로 구분된 동일한 양의 필드를 갖도록 하며 이는 sort의 키 정의 에 중요합니다.
  8. 첫 번째는 sort먼저 파일 이름을 기준으로 정렬을 적용하여 동일한 이름을 서로 인접하게 배치한 다음 이전에 명령 으로 표시된 숫자 값 0을 기준으로 하여 존재하는 경우 "환경" 특정 파일이 제공되도록 보장합니다. 비특이적 상대 앞에1sed
  9. 두 번째는 파일 이름을 sort병합(옵션 )하여 중복 이름 중 첫 번째 이름만 남깁니다. 이전 재정렬로 인해 존재하는 경우 항상 "환경" 특정 파일입니다.-u
  10. 마지막으로 두 번째는 sed첫 번째 작업이 수행된 작업을 취소하여 tar보관할 파일 이름을 재구성합니다.

이렇게 긴 파이프라인의 중간 부분을 탐색하고 싶다면, 모두 다음과 같이 작동한다는 점을 명심하세요.-구분된 이름으로 인해 화면에 잘 표시되지 않습니다. 인간에게 친숙한 출력을 표시하기 위해 중간 출력 중 하나(즉, 최소한 을 제거 tar)를 파이프할 수 있습니다 tr '\0' '\n'. 줄 바꿈이 있는 파일 이름은 화면에서 두 줄에 걸쳐 있다는 점을 기억하십시오.

확실히 완전히 매개변수화된 함수/스크립트로 만들거나 예를 들어 아래와 같이 "환경" 디렉토리에 대한 임의의 이름을 자동으로 감지함으로써 몇 가지 개선이 이루어질 수 있습니다.

중요한: 대화형 쉘에서는 잘 받아들여지지 않을 수 있으므로 주석에 주의하십시오.

(
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 {}
)

sedBash 루프를 사용하여 첫 번째 명령을 대체하는 예 :

(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)

두 번째 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조각도 .xargsbash -c

답변2

일반 솔루션

  1. 디렉터리 트리의 복사본을 만듭니다. 공간을 절약하려면 파일을 하드링크하세요.
  2. 사본을 수정하십시오. (하드링크의 경우 안전하게 할 수 있는 작업이 무엇인지 알아야 합니다. 아래를 참조하세요.)
  3. 사본을 보관하십시오.
  4. 사본을 제거하십시오.
  5. 필요한 경우 반복(다르게 수정)합니다.

제한사항:

  • 이 예에서는 POSIX가 아닌 옵션을 사용합니다(Debian 10에서 테스트됨).
  • 디렉토리 트리에 대해 몇 가지 가정을 합니다.
  • 파일이 너무 많으면 실패할 수 있습니다.

이를 개념 증명으로 간주하고 필요에 맞게 조정하세요.

  1. 복사본 만들기

    cd의 상위 디렉토리로 Products. 이 디렉토리 Products와 그 안에 포함된 모든 항목은 단일 파일 시스템에 속해야 합니다. 임시 디렉터리를 만들고 Products거기에 다시 만듭니다.

    mkdir -p tmp
    cp -la Products/ tmp/
    
  2. 사본 수정

    두 디렉터리 트리의 파일은 하드링크되어 있습니다. 당신이 그들을 수정하는 경우콘텐츠그러면 원본 데이터가 변경됩니다. 디렉터리에 포함된 정보를 수정하는 작업은 안전하며, 다른 트리에서 수행되는 경우 원본 데이터는 변경되지 않습니다. 이것들은:

    • 파일 제거,
    • 파일 이름 바꾸기,
    • 파일 이동(여기에는 를 사용하여 파일을 다른 파일 위로 이동하는 것도 포함 mv)
    • 완전히 독립적인 파일을 생성합니다.

    귀하의 경우 dev올바른 깊이로 이름이 지정된 모든 디렉토리에 대해 해당 내용을 한 수준 위로 이동하십시오.

    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;그리고이 깊이의 다른 디렉터리.

  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재귀적으로 디렉터리를 내려갑니다. 복원 시 더 깊은 파일은덮어쓰기상위 디렉토리의 파일

그러나 일관된 동작에 대해 좀 더 테스트가 필요합니다(확실하지 않음). 적절한 방법은 제외 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

관련 정보