고쳐 쓰다

고쳐 쓰다

나는 필요하다

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개 이상의 디렉터리를 삭제하는 것입니다. 삭제할 특정 디렉터리(있는 경우)를 결정하려면 다음을 수행합니다.

  • DD01날짜 부분이 있거나 필드에 숫자가 아닌 문자가 예상되는 모든 디렉터리를 제외합니다.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_prefxbase_suffxbasestdout

그런 다음 stdout이를 전달하여 grep숫자가 아닌 문자가 포함된 모든 항목을 제거합니다.또는로 끝나는 항목 은 매월 1일의 백업이 무기한 01보관되도록 삭제하는 것이 01중요합니다 .

grep그런 다음 출력을 sort입력하십시오 .감소따라서 최신 항목(모든 ??????01항목 제외)이 출력 상단에 있고 최신 항목이 출력 상단에 표시됩니다.

이제 모든 ??????01백업 디렉터리 날짜를 제외하고 날짜를 가장 최근 날짜부터 내림차순으로 정렬했으므로 남은 작업은 첫 번째 항목을 건너뛰고 그 이상의 N모든 항목을 삭제하는 것입니다 .N+1

코드 는 변수를 사용하여 retain표현됩니다 N. ed 출력을 tail읽고 sortline 에서 시작하는 라인 출력을 시작 retain+1하고 스트림이 루프 stdout에 전달됩니다 .while

루프는 각 행을 변수로 읽어서 참조 base, 그 뒤에 가 오는 rm -rf명령 을 재구성한 다음 해당 명령을 작성합니다 .base_pathbase_prefxbasebase_suffxstdout

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_regexfind ... -regex전체 줄(디렉터리 이름)과 일치하는 정규식이 필요 하므로 전체 줄 정규식이어야 합니다 . 정규식 문자는 .문자가 나타날 때마다 이스케이프됩니다.
  • printf_specprintf문자열을 YYYYMMDD-HHMMSSZ유효한 디렉터리 이름으로 형식화하는 호환 사양이 필요합니다 .

이제 우리는 이름이 find -E확장된 정규식(ala)과 $base_path전체 줄 일치를 형성 하는 이 디렉터리 아래 정확히 한 수준 아래의 디렉터리를 찾도록 지시하고 지시할 수 있습니다.grep -Ex$base_regex

일치시키려는 정규식 부분은 YYYYMMDD-HHMMSSZ괄호로 묶여 있습니다. 이렇게 하면 sed다음 단계에서 유용한 "역참조"가 생성됩니다 . findto의 전체 출력을 전달 하고 입력의 각 줄을 시간순으로 정렬해야 하는 sed정규식의 괄호 부분과 일치하는 해당 줄의 부분으로 바꾸도록 지시합니다 . YYYYMMDD-HHMMSSZ이전 스크립트는 타임스탬프를 구문 분석하기 위해 bash-ism을 사용했지만 bash-ism은 glob에 의존하므로 정규식 기반 솔루션을 구현하기 위해 sed.

스크립트의 나머지 부분은 기본적으로 동일합니다. 즉, 매월 1일에 모든 백업 작업을 삭제하도록 sed출력이 전달됩니다 . grep출력은 역순으로 진행된 sort다음 목록 상단의 최대값을 tail건너뛰고 그 이후의 각 행을 각 행을 전달하는 while 루프로 출력합니다 .$retainprintf

지침:

숙련된 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)

관련 정보