나는 필요하다
cron에서 사용하고 야간에 무인으로 실행하여 모든 첫 달 백업과 최신 14개 백업을 제외한 모든 백업 디렉터리를 삭제하는 스크립트(bash?)를 만드는 데 문제가 있습니다. bash가 작동하지 않으면 이식성이 필요하므로 shell과 POSIX를 사용할 수 있습니다.
스크립트는 안전하고 우아해야 하며,나 어디 붙어있어?: 5월 이후 백업이 발생하지 않았음을 인식하고, 스크립트가 11월에 실행되었음에도 불구하고 가장 최근(5월) 14개 백업이 최신이므로 계속 유지합니다. 모든 경우에 스크립트는 이름에 날짜 부분이 01인 모든 백업(-YYYYMMDD-)을 유지해야 합니다.
내가 소유한 것
- 백업이 포함된 DIRS가 있습니다.
- 백업 날짜는 DIR 이름에 있습니다.
- 스크립트는 콘텐츠를 읽고
/path/to/backups/example.com/
콘텐츠에서 삭제할 DIRS를 결정해야 합니다. - DIRS는 비어 있지 않습니다. 여기에는 오늘의 백업이 포함됩니다.
/path/to/backups/example.com/example.com-20210101-backup/ // Keep (first of month)
/path/to/backups/example.com/example.com-20210201-backup/ // Keep (first of month)
/path/to/backups/example.com/example.com-20210301-backup/ // Keep (first of month)
/path/to/backups/example.com/example.com-20210401-backup/ // Keep (first of month)
/path/to/backups/example.com/example.com-20210501-backup/ // Keep (first of month)
/path/to/backups/example.com/example.com-20210502-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210503-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210504-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210505-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210506-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210507-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210508-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210509-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210510-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210511-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210512-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210513-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210514-backup/ // <-- Script to remove
/path/to/backups/example.com/example.com-20210515-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210516-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210517-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210518-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210519-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210520-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210521-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210522-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210523-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210524-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210525-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210526-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210527-backup/ // Keep (Most recent 14 days even if old)
/path/to/backups/example.com/example.com-20210528-backup/ // Keep (Most recent 14 days even if old)
왜 이것을 스크립트로 작성합니까?
언젠가는 백업이 복원될 수 있고 자동으로 삭제해야 하는 새 DIRS가 있을 수 있기 때문입니다.
내가 찾은 것
내가 찾은 모든 것은 가장 최근의 14개를 제외한 모든 항목을 제거하거나 첫 달만 유지하고 둘 다는 유지하지 않았습니다.
예를 들어 다음과 같습니다.https://unix.stackexchange.com/a/379041
find /path/to/backups/example.com/ -type d -mtime +14 -exec rm -rf {} +
스크립트가 실행된 날짜로부터 가장 최근의 14개 스크립트만 표시되며, 11월에 실행되면 아무 것도 표시되거나 삭제되지 않습니다.
내 문제
모든 첫 달 백업과 최신 14개 백업, 심지어 오래된 백업까지 유지하는 보안 스크립트를 생성할 수 있는 방법은 없습니다.
올바른 방향으로 안내해 주셔서 감사합니다.
- 감사합니다!
답변1
가정:
- 백업 디렉터리(적어도 최상위 수준)는 공백/줄 바꿈/정규 표현식 또는 쉘 glob 메타 문자 등 없이 잘 구성되어 있습니다.
- 경로에 디렉터리 모음이 있습니다.
base_path
- 모든 디렉토리는 접두사로 시작됩니다.
base_prefx
- 각 디렉토리는 접미사로 끝납니다.
base_suffx
- 경로, 접두사 및 접미사가 제거되면 각 디렉터리 이름은 날짜입니다.
YYYYMMDD
- 이러한 기준을 충족하지 않는 디렉터리는 무시됩니다.
주어진 정보를 바탕으로 그에 따라 전략을 계획할 수 있습니다.
현재 작업의 핵심은 YYYYMMDD
이름의 일부를 기준으로 0개 이상의 디렉터리를 삭제하는 것입니다. 삭제할 특정 디렉터리(있는 경우)를 결정하려면 다음을 수행합니다.
DD
01
날짜 부분이 있거나 필드에 숫자가 아닌 문자가 예상되는 모든 디렉터리를 제외합니다.YYYYMMDD
- 나머지 디렉토리에서는
N
가장 최근 날짜를 제외합니다. - 나머지 모든 디렉터리(있는 경우)가 삭제됩니다.
당신은 을 선택했습니다 N=14
.
#!/usr/bin/env bash
retain=14
base_path='./path/to/backups/example.com/'
base_prefx='example.com-'
base_suffx='-backup'
find "$base_path" -maxdepth 1 -mindepth 1 \
-type d \
-name "${base_prefx}????????${base_suffx}" |
while IFS= read dir
do
base="$(basename "$dir" "$base_suffx")"
printf '%s\n' "${base#$base_prefx}"
done |
grep -Ev '([^[:digit:]]|01$)' |
sort -r |
tail +$(($retain+1)) |
while IFS= read base
do
printf 'rm -rf "%q%q%q%q"\n' \
"$base_path" "$base_prefx" "$base" "$base_suffx"
done
이 find
명령은 base_path
가상 디렉터리 구조 템플릿과 일치하고 해당 디렉터리보다 정확히 한 수준 아래에 있는 디렉터리에서 하위 디렉터리 이름을 찾습니다 base_path
.
find
의 출력은 입력의 각 줄을 읽고 제거한 다음 디렉터리 base_path
이름 (표면적으로는 날짜) 부분을 쓰는 while 루프에 공급됩니다 .base_prefx
base_suffx
base
stdout
그런 다음 stdout
이를 전달하여 grep
숫자가 아닌 문자가 포함된 모든 항목을 제거합니다.또는로 끝나는 항목 은 매월 1일의 백업이 무기한 01
보관되도록 삭제하는 것이 01
중요합니다 .
grep
그런 다음 출력을 sort
입력하십시오 .감소따라서 최신 항목(모든 ??????01
항목 제외)이 출력 상단에 있고 최신 항목이 출력 상단에 표시됩니다.
이제 모든 ??????01
백업 디렉터리 날짜를 제외하고 날짜를 가장 최근 날짜부터 내림차순으로 정렬했으므로 남은 작업은 첫 번째 항목을 건너뛰고 그 이상의 N
모든 항목을 삭제하는 것입니다 .N+1
코드 는 변수를 사용하여 retain
표현됩니다 N
. ed 출력을 tail
읽고 sort
line 에서 시작하는 라인 출력을 시작 retain+1
하고 스트림이 루프 stdout
에 전달됩니다 .while
루프는 각 행을 변수로 읽어서 참조 base
, 그 뒤에 가 오는 rm -rf
명령 을 재구성한 다음 해당 명령을 작성합니다 .base_path
base_prefx
base
base_suffx
stdout
rm
명령은 쓰기만 하기 때문에 stdout
스크립트는 아무것도 삭제하지 않습니다. 출력을 작동하기 전에 출력의 정확성을 확인해야 합니다. 명령이 올바르게 표시되면 출력을 파이프 sh
하고 rm
명령을 실행할 수 있습니다. 스크립트를 만족스럽게 테스트한 후에 printf
는 실제로 올바른 rm -rf
명령을 호출하여 통과하도록 행을 수정할 수 있습니다 cron
.
테스트할 디렉터리를 만들어 보겠습니다.
mkdir -p path/to/backups/example.com/example.com-20210101-backup
mkdir -p path/to/backups/example.com/example.com-20210201-backup
mkdir -p path/to/backups/example.com/example.com-20210301-backup
mkdir -p path/to/backups/example.com/example.com-20210401-backup
mkdir -p path/to/backups/example.com/example.com-20210501-backup
mkdir -p path/to/backups/example.com/example.com-20210502-backup
mkdir -p path/to/backups/example.com/example.com-20210503-backup
mkdir -p path/to/backups/example.com/example.com-20210504-backup
mkdir -p path/to/backups/example.com/example.com-20210505-backup
mkdir -p path/to/backups/example.com/example.com-20210506-backup
mkdir -p path/to/backups/example.com/example.com-20210507-backup
mkdir -p path/to/backups/example.com/example.com-20210508-backup
mkdir -p path/to/backups/example.com/example.com-20210509-backup
mkdir -p path/to/backups/example.com/example.com-20210510-backup
mkdir -p path/to/backups/example.com/example.com-20210511-backup
mkdir -p path/to/backups/example.com/example.com-20210512-backup
mkdir -p path/to/backups/example.com/example.com-20210513-backup
mkdir -p path/to/backups/example.com/example.com-20210514-backup
mkdir -p path/to/backups/example.com/example.com-20210515-backup
mkdir -p path/to/backups/example.com/example.com-20210516-backup
mkdir -p path/to/backups/example.com/example.com-20210517-backup
mkdir -p path/to/backups/example.com/example.com-20210518-backup
mkdir -p path/to/backups/example.com/example.com-20210519-backup
mkdir -p path/to/backups/example.com/example.com-20210520-backup
mkdir -p path/to/backups/example.com/example.com-20210521-backup
mkdir -p path/to/backups/example.com/example.com-20210522-backup
mkdir -p path/to/backups/example.com/example.com-20210523-backup
mkdir -p path/to/backups/example.com/example.com-20210524-backup
mkdir -p path/to/backups/example.com/example.com-20210525-backup
mkdir -p path/to/backups/example.com/example.com-20210526-backup
mkdir -p path/to/backups/example.com/example.com-20210527-backup
mkdir -p path/to/backups/example.com/example.com-20210528-backup
mkdir -p path/to/backups/example.com/example.com-20210228-backup/example.com-20210101-backup
mkdir -p path/to/backups/example.com/example.com-messedup-backup/example.com-20210227-backup
mkdir -p path/to/backups/example.com/example.com-20210428-backup/example.com-20210601-backup
그런 다음 스크립트를 실행합니다.
$ ./test.sh
rm -rf "./path/to/backups/example.com/example.com-20210514-backup"
rm -rf "./path/to/backups/example.com/example.com-20210513-backup"
rm -rf "./path/to/backups/example.com/example.com-20210512-backup"
rm -rf "./path/to/backups/example.com/example.com-20210511-backup"
rm -rf "./path/to/backups/example.com/example.com-20210510-backup"
rm -rf "./path/to/backups/example.com/example.com-20210509-backup"
rm -rf "./path/to/backups/example.com/example.com-20210508-backup"
rm -rf "./path/to/backups/example.com/example.com-20210507-backup"
rm -rf "./path/to/backups/example.com/example.com-20210506-backup"
rm -rf "./path/to/backups/example.com/example.com-20210505-backup"
rm -rf "./path/to/backups/example.com/example.com-20210504-backup"
rm -rf "./path/to/backups/example.com/example.com-20210503-backup"
rm -rf "./path/to/backups/example.com/example.com-20210502-backup"
rm -rf "./path/to/backups/example.com/example.com-20210428-backup"
rm -rf "./path/to/backups/example.com/example.com-20210228-backup"
좋아 보입니다. 실행해 보겠습니다.
$ ./test.sh | sh
고쳐 쓰다
파일 이름에 셸 전역 변수(예: ????????
)와 정규식(예: )을 혼합하면 [0-9]{6}Z
다루기 어려울 수 있습니다. 물론 스크립트를 전체적으로 정규식을 사용하도록 조정할 수 있지만 그렇게 하면 약간의 복잡성이 추가됩니다.
#!/usr/bin/env bash
retain=15
# This is a shell glob (with no wildcards); must end in slash
base_path='./path/to/backups/example.com/'
# This is an extended regex pattern:
base_regex='\./path/to/backups/example\.com/example\.com-([0-9]{8}-[0-9]{6}Z)-backup'
# This is a printf spec to printf a base_path and a date-time to a full directory name:
printf_spec='%qexample.com-%q-backup'
find -E "$base_path" -maxdepth 1 -mindepth 1 \
-type d \
-regex "${base_regex}" |
sed -Ee "s~^${base_regex}$~\1~" |
grep -Ev '^[0-9]{6}01-' |
sort -r |
tail -n +$(($retain+1)) |
while IFS= read line
do
printf "rm -rf ${printf_spec}\n" "${base_path}" "$line"
done
어떤 변수가 쉘 전역 변수인지, 정규 표현식인지, 사양인지를 명확히 하기 위해 상단에 주석이 추가되었습니다 printf
. 이는 다음과 같은 이유로 필요합니다.
base_path
필요한 것은find
어디를 봐야 할지 알려주는 쉘 글로브입니다.base_regex
find ... -regex
전체 줄(디렉터리 이름)과 일치하는 정규식이 필요 하므로 전체 줄 정규식이어야 합니다 . 정규식 문자는.
문자가 나타날 때마다 이스케이프됩니다.printf_spec
printf
문자열을YYYYMMDD-HHMMSSZ
유효한 디렉터리 이름으로 형식화하는 호환 사양이 필요합니다 .
이제 우리는 이름이 find -E
확장된 정규식(ala)과 $base_path
전체 줄 일치를 형성 하는 이 디렉터리 아래 정확히 한 수준 아래의 디렉터리를 찾도록 지시하고 지시할 수 있습니다.grep -Ex
$base_regex
일치시키려는 정규식 부분은 YYYYMMDD-HHMMSSZ
괄호로 묶여 있습니다. 이렇게 하면 sed
다음 단계에서 유용한 "역참조"가 생성됩니다 . find
to의 전체 출력을 전달 하고 입력의 각 줄을 시간순으로 정렬해야 하는 sed
정규식의 괄호 부분과 일치하는 해당 줄의 부분으로 바꾸도록 지시합니다 . YYYYMMDD-HHMMSSZ
이전 스크립트는 타임스탬프를 구문 분석하기 위해 bash-ism을 사용했지만 bash-ism은 glob에 의존하므로 정규식 기반 솔루션을 구현하기 위해 sed
.
스크립트의 나머지 부분은 기본적으로 동일합니다. 즉, 매월 1일에 모든 백업 작업을 삭제하도록 sed
출력이 전달됩니다 . grep
출력은 역순으로 진행된 sort
다음 목록 상단의 최대값을 tail
건너뛰고 그 이후의 각 행을 각 행을 전달하는 while 루프로 출력합니다 .$retain
printf
지침:
숙련된 U&L 사용자는 다른 사항을 지적할 수도 있지만 다음은 참고할 몇 가지 사항입니다.
base_regex
디렉터리 이름 리터럴과 일치할 것으로 예상되는 사용하는 정규식 문자를 이스케이프 처리하세요.- 이
sed
명령은~
검색 및 바꾸기 구분 기호로 a를 사용합니다. 따라서 디렉토리 이름에 물결표를 사용하지 않아야 합니다.base_regex
문자열에 물결표를 추가 하지 않는 한find
이러한 디렉토리는 파일 시스템에 실제로 생성된 경우에도 제거되어야 합니다. - 이 알고리즘은 각 날짜/시간 조합을 고유한 백업으로 처리하므로 어제 14개의 백업 작업이 실행된 경우 마지막 14개 백업 유지는 어제의 백업만 보관할 수 있습니다.
답변2
@짐님 감사합니다. 이것은 디렉토리 이름에 Zulu 시간을 추가해야 할 때까지 작동하여 다음과 같이 만들었습니다.
./path/to/backups/example.com/example.com-20210101-040538Z-backup
노트: 줄루어 시간은 매일 변경됩니다.
이것은 그것을 깨뜨린다.
그래서 나는 이와 같은 시간 문자열을 해결하기 위해 정규식을 추가하는 스크립트를 따라 보았습니다. 하지만 내 정규식 형식에 문제가 있다고 확신하기 때문에 다음은 작동하지 않습니다.
#!/usr/bin/env bash
retain=15
base_path='./path/to/backups/example.com/'
base_prefx='example.com-'
base_time="-040538Z" # <--- Works but is not Regex.
# base_time="-[[:digit:]]{6}Z" # <--- Regex (I think) but not working.
base_suffx="-backup"
find "$base_path" -maxdepth 1 -mindepth 1 \
-type d \
-name "${base_prefx}????????${base_time}${base_suffx}" |
while IFS= read dir
do
base="$(basename "$dir" "$base_time$base_suffx")"
printf '%s\n' "${base#$base_prefx}"
done |
grep -Ev '([^[:digit:]]|01$)' |
sort -r |
tail -n +$(($retain+1)) |
while IFS= read base
do
printf 'success-safety-rm -rf "%q%q%q%q%q"\n' \
"$base_path" "$base_prefx" "$base" "$base_time" "$base_suffx"
done
어쩌면 내가 하고 있는 일이 잘못된 건 아닐까?
이 스크립트를 어떻게 더 잘 구성할 수 있나요?
답변3
GNU 배포 솔루션
후손을 위한 참고 사항: @Jim L 위의 허용된 답변의 업데이트된 부분은 솔루션이자 기초입니다.
왜 혼자 대답하나요?
find -E
위의 "Accepted Answer Update" 섹션이 GNU 환경에서 오류를 일으키기 때문입니다 find: unknown predicate '-E'
.
다음은 GNU 기반 Linux용 작업 스크립트입니다.
#!/usr/bin/env bash
retain=15
# CHANGEME This is a shell glob (with no wildcards); must end in slash; example:
base_path='./path/to/backups/example.com/'
# CHANGEME This is an extended regex pattern example:
base_regex='\./path/to/backups/example\.com/example\.com-([0-9]{8}-[0-9]{6}Z)-backup'
# CHANGEME This is a printf spec to printf a base_path and a date-time to a full directory name example:
printf_spec='%qexample.com-%q-backup'
find "$base_path" -maxdepth 1 -mindepth 1 \
-type d \
-regextype posix-extended \
-regex "${base_regex}" |
sed -Ee "s~^${base_regex}$~\1~" |
grep -Ev '^[0-9]{6}01-' |
sort -r |
tail -n +$(($retain+1)) |
while IFS= read line
do
printf "REMOVEME-SAFETY-rm -rf ${printf_spec}\n" "$base_path" "$line"
done
달리다:
./test.sh
결과 :
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20211105-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20211104-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20211103-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20211102-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210303-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210302-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210203-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210202-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210110-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210109-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210108-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210107-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210106-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210105-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210104-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210103-040538Z-backup
REMOVEME-SAFETY-rm -rf /path/to/backups/example.com/example.com-20210102-040538Z-backup
example.com 테스트 설정에서 제거할 백업 디렉터리는 무엇입니까?
이제 printf "REMOVEME-SAFETY-rm...
무인으로 실행되고 실제로 디렉토리를 삭제하도록 라인을 조정할 수 있습니다.
노트:짐의지침:위에서 허용된 답변도 이 버전을 나타냅니다.
@Jim L에게 다시 한 번 감사드립니다.
답변4
다른 파일 이름 지정 패턴을 사용하여 다양한 백업을 삭제하려고 했기 때문에 다른 솔루션은 내 시나리오에서 안정적으로 작동하지 않았습니다. 그래서 파일 이름이 아닌 파일 생성(또는 수정) 날짜를 기준으로 다음 스크립트를 작성했습니다.
#!/usr/bin/env bash
dryRun=true # Set to `false` or remove this line to move from logging to deletion
rootFolder="/var/lib/psa/dumps/" # Files within this folder will be checked recursively
fileGlob="*.*" # Limit to specific file type if required
fileAgeLimit="30 days ago" # All files up to this age (date only and inclusive) will be kept
regularExpressionForDatesToBeKept='-[0-9][0-9]-01$' # This default regular expression will keep all files from the 1st of each month
checkForDeletion() {
filePath=$1
fileName=$(basename "$filePath")
fileDateTimeString=$(stat -c '%w' "$filePath") # Creation date; only available on newer file systems
if [[ "$fileDateTimeString" = "-" ]]; then
fileDateTimeString=$(stat -c '%y' "$filePath") # Use modification date instead
fi
fileDateString="$(date +"%Y-%m-%d" -d "$fileDateTimeString")"
fileAgeLimitDateString="$(date +"%Y-%m-%d" -d "$fileAgeLimit")"
if [[ "$fileDateString" < "$fileAgeLimitDateString" ]]; then
if [[ "$fileDateString" =~ $regularExpressionForDatesToBeKept ]]; then
[[ $dryRun = true ]] && echo -e "To be kept\tFile date: $fileDateString (matches '$regularExpressionForDatesToBeKept')\t$fileName"
else
if [[ $dryRun = true ]]; then
echo -e "To be DELETED\tFile date: $fileDateString\t\t\t\t$fileName"
else
rm -f "$filePath"
fi
fi
else
[[ $dryRun = true ]] && echo -e "To be kept\tFile date: $fileDateString ($fileAgeLimit or younger)\t$fileName"
fi
}
# Safely loop through all find matches
while read -r -d ''; do
checkForDeletion "$REPLY"
done < <(find "$rootFolder" -type f -name "$fileGlob" -print0)