각 환경에 대해 특정 파일을 덮어써 여러 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루트 폴더 내의 각 하위 폴더에는 세 개의 환경 폴더( 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있으며 완전히 동일합니다. 하위 폴더 내의 각 하위 폴더에는 덮어쓴 파일이 포함됩니다.devstageprodfolder2folder3devstageprodfolder{number}

이제 위의 구조 에서 각 tar.gz파일마다 하나씩 세 개의 서로 다른 파일을 생성해야 합니다 .devstageprod

  • 그 안에 어떤 파일이 있든 관계없이 dev하위 폴더(folder1,folder2,folder3)에도 하위 폴더 파일이 있으면 stage해당 파일을 덮어씁니다.prod
  • 따라서 하위 폴더 files1.json에 존재 folder1하고 동일한 파일이 어느 파일에도 존재하는 dev경우 패키징할 stageprod해당 환경 폴더에 있는 모든 항목을 사용하고 해당 하위 폴더 파일을 덮어써야 합니다. 그렇지 않으면 해당 하위 폴더 내에 존재하는 모든 콘텐츠 폴더를 사용하면 됩니다.

결국, 아래와 같이 3가지 다른 구조를 가지게 됩니다. 하나는 폴더 1(또는 2와 3) 에 대해 dev하나 는 덮어쓰기되었기 때문에 해당 환경 파일에서 첫 번째 선택 항목으로 갖게 됩니다. , 다른 파일은 덮어쓰지 않았습니다.stageprod

(이미지 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재정의 폴더가 있지만 파일이 없으므로 이 경우 folder2각 환경별 파일에 빈 파일을 생성하려고 합니다.folder3tar.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 를 just로 바꿔야 find합니다 ( <-마지막 참고 ).-regextype posix-extended-Etar--no-recursion-n--transform=ss-s
  2. 데모를 단순화하기 위해 코드 조각은 포함된 디렉터리에서 실행된다고 가정 하고 보관할 "환경" 디렉터리 이름에 대한 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 구문에 대해 이전에 말한 모든 내용이 여기에도 적용됩니다 find.tar
  2. 이전 솔루션과 마찬가지로 디렉터리 계층의 처음 두 수준에 대한 가정 외에는 제약 조건이 없습니다.
  3. sed여기서는 널로 구분된 I/O(옵션)를 처리하기 위해 GNU를 사용하고 있지만 이 두 명령을 쉘 루프(Bash 버전 3 이상 필요) 또는 고유하다고 확신하는 다른 언어로 -z쉽게 대체할 수 있습니다. 권장 사항은 다음과 같습니다. 널로 구분된 I/O를 처리할 수 있는 도구를 사용해야 합니다(예: GNU에서 이를 수행할 수 있음). Bash 루프를 사용한 대체 방법은 아래를 참조하세요.sedwhile read ...gawk
  4. find나는 암시적인 행동에 의존하지 않기 때문에 여기서는 싱글을 사용하고 있습니다.tar
  5. 명령의 길을 열어주는 명령 sed작업 이름 목록sort
  6. 특히 첫 번째는 sed"환경" 이름을 경로의 시작 부분으로 이동하고 0그 앞에 보조 번호를 추가합니다. 이는 비환경 파일 앞에 정렬되도록 하기 위한 것입니다. 왜냐하면 제가 후자 앞에 접두사를 추가했기 때문입니다. , 1목적은 정렬입니다
  7. 이 준비는 "eyes" 명령의 이름 목록을 정규화하여 sort모든 이름이 "환경" 이름을 사용하지 않고 모든 이름의 시작 부분에 동일한 수의 슬래시로 구분된 필드를 갖도록 하며 이는 sort키 정의에 중요합니다.
  8. 첫 번째는 sort먼저 파일 이름을 기준으로 정렬하여 동일한 이름을 나란히 배치한 다음 이전 표시의 숫자 값을 명령하여 0"환경" 특정 파일(존재하는 경우)이 표시되도록 보장하여 적용됩니다. 비특이적 대응물 옆에1sed
  9. 파일 이름에 대한 두 번째 sort병합(옵션 ) -u은 첫 번째 중복 이름만 남깁니다. 이는 이전 재정렬로 인해 존재하는 경우 항상 "환경"별 파일입니다.
  10. 마지막으로 두 번째는 sed첫 번째 작업을 취소하여 tar보관 용 파일 이름을 다시 만듭니다.

이렇게 긴 파이프의 중간 부분을 탐험하는 데 관심이 있다면, 그 부분이 모두 다음과 관련이 있다는 것을 기억하세요.없음- 화면에 잘 보이지 않도록 이름을 구분합니다. 인간에게 친숙한 출력을 표시하기 위해 중간 출력(즉, 적어도 Stripped 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

관련 정보