루트 폴더가 있고 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
있으며 완전히 동일합니다. 하위 폴더 내의 각 하위 폴더에는 덮어쓴 파일이 포함됩니다.dev
stage
prod
folder2
folder3
dev
stage
prod
folder{number}
이제 위의 구조 에서 각 tar.gz
파일마다 하나씩 세 개의 서로 다른 파일을 생성해야 합니다 .dev
stage
prod
- 그 안에 어떤 파일이 있든 관계없이
dev
하위 폴더(folder1,folder2,folder3)에도 하위 폴더 파일이 있으면stage
해당 파일을 덮어씁니다.prod
- 따라서 하위 폴더
files1.json
에 존재folder1
하고 동일한 파일이 어느 파일에도 존재하는dev
경우 패키징할stage
때prod
해당 환경 폴더에 있는 모든 항목을 사용하고 해당 하위 폴더 파일을 덮어써야 합니다. 그렇지 않으면 해당 하위 폴더 내에 존재하는 모든 콘텐츠 폴더를 사용하면 됩니다.
결국, 아래와 같이 3가지 다른 구조를 가지게 됩니다. 하나는 폴더 1(또는 2와 3) 에 대해 dev
하나 는 덮어쓰기되었기 때문에 해당 환경 파일에서 첫 번째 선택 항목으로 갖게 됩니다. , 다른 파일은 덮어쓰지 않았습니다.stage
prod
(이미지 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
다양한 접근 방식이 있을 수 있지만 모든 접근 방식에는 적용 범위 상황을 처리하기 위해 어느 정도의 복잡성이 필요합니다.
비록 약간 길긴 하지만 한 줄로 한 번의 반복으로 이 작업을 수행할 수 있습니다. 즉, 하나의 "환경" 디렉터리입니다.
(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 의 경우 just 를 just로 바꿔야
find
합니다 ( <-마지막 참고 ).-regextype posix-extended
-E
tar
--no-recursion
-n
--transform=s
s
-s
- 데모를 단순화하기 위해 코드 조각은 포함된 디렉터리에서 실행된다고 가정 하고 보관할 "환경" 디렉터리 이름에 대한
Products
사용자 지정 변수와 이름을 포함하는 짧은 이름의 도우미 변수를 사용합니다.$e
$r
Products
- 명령줄에서 실행할
$r
경우 쉘을 오염시키지 않도록 괄호로 묶어 하위 쉘로 만듭니다.$e
- 원본 파일을 복사하거나 링크/참조하지 않으며 유효한 파일 이름을 처리하고 메모리 제한이 없으며 모든 이름을 처리할 수 있습니다. 유일한 가정은 디렉터리 계층 구조의 처음 두 수준에 관한 것입니다. 한 수준 아래의 디렉터리는 "환경" 디렉터리로 간주되므로 무시됩니다( 에 표시된 디렉터리 제외
$e
).
for e in dev prod stage; do ...; done
쉘 루프 에 코드 조각을 포함시키기만 하면 됩니다 . (가장 바깥쪽 브래킷을 제거하고 전체 for
루프를 둘러쌀 수도 있습니다).
좋은 점은 상당히 짧고 비교적 간단하다는 것입니다.
단점은 항상 보관된다는 것입니다.모두이것덮여find
파일(예: 기본 파일)의 경우 이중 명령이 먼저 tar
덮어쓸 파일을 제공하므로 추출 프로세스 중에 해당 파일이 오버레이 파일(예: "환경" 특정 파일)로 덮어쓰여진다는 점입니다 . 이로 인해 더 큰 아카이브를 만들고 추출하는 데 더 많은 시간이 걸리며 이 "오버헤드"가 무시할 수 있는지 여부에 따라 바람직하지 않을 수 있습니다.
에세이에 설명된 파이프라인은 다음과 같습니다.
- (가장 바깥쪽 괄호와 보조변수 제외)
- 첫 번째
find
명령은 비특정 파일 목록(및 업데이트에 따른 부팅 디렉터리)만 생성하는 반면, 두 번째 명령은find
모든 환경 관련 파일 목록만 생성합니다. - 두 명령 자체는 괄호로 묶여 출력이 순서대로
find
파이프에 들어갑니다.tar
tar
이러한 파이프를 읽어 파일 이름을 가져오고 해당 파일을 아카이브에 저장하는 동시에--transform
각 파일의 경로 이름에서 "환경" 구성 요소(있는 경우)를 제거하여 이름을 바꿉니다.- 두
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%'
)
참고할 몇 가지 사항이 있습니다.
- GNU 및 BSD 구문에 대해 이전에 말한 모든 내용이 여기에도 적용됩니다
find
.tar
- 이전 솔루션과 마찬가지로 디렉터리 계층의 처음 두 수준에 대한 가정 외에는 제약 조건이 없습니다.
sed
여기서는 널로 구분된 I/O(옵션)를 처리하기 위해 GNU를 사용하고 있지만 이 두 명령을 쉘 루프(Bash 버전 3 이상 필요) 또는 고유하다고 확신하는 다른 언어로-z
쉽게 대체할 수 있습니다. 권장 사항은 다음과 같습니다. 널로 구분된 I/O를 처리할 수 있는 도구를 사용해야 합니다(예: GNU에서 이를 수행할 수 있음). Bash 루프를 사용한 대체 방법은 아래를 참조하세요.sed
while read ...
gawk
find
나는 암시적인 행동에 의존하지 않기 때문에 여기서는 싱글을 사용하고 있습니다.tar
- 명령의 길을 열어주는 명령
sed
작업 이름 목록sort
- 특히 첫 번째는
sed
"환경" 이름을 경로의 시작 부분으로 이동하고0
그 앞에 보조 번호를 추가합니다. 이는 비환경 파일 앞에 정렬되도록 하기 위한 것입니다. 왜냐하면 제가 후자 앞에 접두사를 추가했기 때문입니다. ,1
목적은 정렬입니다 - 이 준비는 "eyes" 명령의 이름 목록을 정규화하여
sort
모든 이름이 "환경" 이름을 사용하지 않고 모든 이름의 시작 부분에 동일한 수의 슬래시로 구분된 필드를 갖도록 하며 이는sort
키 정의에 중요합니다. - 첫 번째는
sort
먼저 파일 이름을 기준으로 정렬하여 동일한 이름을 나란히 배치한 다음 이전 표시의 숫자 값을 명령하여0
"환경" 특정 파일(존재하는 경우)이 표시되도록 보장하여 적용됩니다. 비특이적 대응물 옆에1
sed
- 파일 이름에 대한 두 번째
sort
병합(옵션 )-u
은 첫 번째 중복 이름만 남깁니다. 이는 이전 재정렬로 인해 존재하는 경우 항상 "환경"별 파일입니다. - 마지막으로 두 번째는
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 {}
)
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)
두 번째 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/
사본 수정
두 디렉토리 트리의 파일은 하드 링크되어 있습니다. 당신이 그들을 수정하는 경우콘텐츠그런 다음 원본 데이터를 변경합니다. 디렉터리에 포함된 정보를 수정하는 작업은 안전하며 다른 트리에서 수행되는 경우 원본 데이터를 변경하지 않습니다. 이것들은 모두:
- 파일 삭제,
- 파일 이름 바꾸기,
- 파일 이동(여기에는 파일을 다른 파일로 이동 사용 포함
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
;그리고이 깊이의 다른 디렉터리.보관된 사본
# 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
디렉토리는 재귀적으로 내려오고 더 깊은 파일은씌우다상위 디렉토리의 파일
그러나 일관된 동작을 위해서는 더 많은 테스트가 필요합니다(확실하지 않음). 올바른 방법은 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