로그를 정리하는 스크립트를 평소와 다른 디렉터리에서 돌렸다. 그랬더니 정리 대상이 아닌 파일이 지워졌고 스크립트는 아무 오류 없이 정상 종료했다.
Table of contents
Open Table of contents
원인 — 상대 경로와 실행 위치
스크립트를 열어 보니 대상 디렉터리를 상대 경로로 적고 있었다.
find ./logs -name '*.log' -mtime +30 -delete
./logs 는 스크립트가 놓인 자리가 아니라 명령을 친 시점의 pwd 를 기준으로 풀린다.
/app 에서 돌리면 /app/logs 를 지우고 홈 디렉터리에서 돌리면 거기 있는 logs 를 지운다. 그 자리에 마침 logs 가 있어서 find 가 아무 오류 없이 지우고 끝난 것이었다.
스크립트 위치 기준의 절대 경로
먼저 dirname "$0" 으로 스크립트가 자기 위치를 알아내고 그것을 기준으로 절대 경로를 만들게 고쳤다.
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
APP_ROOT=${APP_ROOT:-$(cd "$SCRIPT_DIR/.." && pwd)}
find "$APP_ROOT/logs" -name '*.log' -mtime +30 -delete
$0 에서 dirname 으로 스크립트가 놓인 자리를 얻고 cd 뒤 pwd 로 절대 경로를 확정하므로 어디서 돌려도 $APP_ROOT/logs 한 곳을 가리킨다.
APP_ROOT 를 환경 변수로도 받게 둔 것은 시험 장비에서 다른 자리를 가리키게 하기 위해서였고 안 주면 SCRIPT_DIR 의 부모가 된다. 기준을 바꾼 뒤에는 확정된 $APP_ROOT 를 실행 전에 화면에 찍게 했다.
경로가 맞아도 자리는 확인한다
$APP_ROOT 가 맞게 만들어졌어도 그 디렉터리가 정말 우리 것인지는 별개 문제였다. 다른 프로젝트에도 logs 는 있기 때문이다.
그래서 그 디렉터리에 .app-root 표시 파일을 두고 그것이 있을 때만 진행하게 했다.
[ -f "$APP_ROOT/.app-root" ] || {
echo "여기는 애플리케이션 루트가 아닙니다: $APP_ROOT"
exit 1
}
.app-root 가 없으면 exit 1 로 멈춘다. 디렉터리 이름만 보고 지우는 것보다 표시 파일 하나를 test -f 로 확인하는 쪽이 안전했다.
삭제 전 출력과 건수 상한
지우기 전에 무엇을 지울지 목록으로 출력하고 DRY_RUN=1 이면 실제로는 안 지우게 했다.
targets=$(find "$APP_ROOT/logs" -name '*.log' -mtime +30)
count=$(echo "$targets" | grep -c . || true)
echo "$count 개를 지웁니다. ($APP_ROOT/logs)"
echo "$targets" | head -5
if [ "${DRY_RUN:-0}" = "1" ]; then exit 0; fi
echo "$targets" | xargs -r rm -f
find 결과를 targets 에 담아 두고 head -5 로 앞부분을 보인 뒤 DRY_RUN 이 1이면 xargs rm 까지 안 가고 exit 0 한다. 처음 돌릴 때와 조건을 바꾼 뒤에는 DRY_RUN=1 로 먼저 본다.
여기에 건수 상한을 하나 뒀는데 평소보다 훨씬 많이 잡히면 조건이 잘못된 것으로 보고 멈춘다.
if [ "$count" -gt 10000 ]; then
echo "지울 것이 $count 개입니다. 예상보다 많아 멈춥니다."
exit 1
fi
count 가 상한을 넘으면 exit 1 이다. 실제로 그 뒤에 한 번 걸렸고 확인해 보니 -mtime 값에 오타가 있어 find 가 전체를 잡은 것이었다.
전제의 문서화와 실행 기록
같은 모양의 스크립트가 더 있는지 grep 으로 찾아봤다.
$ grep -rn "^\s*\(find\|rm\|cp\|mv\)\s\+\./" scripts/
scripts/clean-cache.sh:8: rm -rf ./cache/*
scripts/backup.sh:14: cp -r ./config /backup/
find · rm · cp · mv 뒤에 ./ 가 오는 줄을 찾는 식이고 넷이 더 나왔다. 그중 rm -rf ./cache/* 가 가장 위험해서 먼저 고쳤다. 하나만 고치면 다음에 다른 것에서 같은 일이 난다.
마지막으로 각 스크립트 첫머리에 어디서 돌리는 것을 전제하는지를 적고 실행할 때마다 한 줄을 기록에 남겼다.
echo "$(date -Is) $(whoami)@$(hostname) pwd=$(pwd) root=$APP_ROOT count=$count" >> "$LOG"
date -Is 와 whoami 와 hostname 과 pwd 를 한 줄에 남기므로 언제 누가 어느 자리에서 돌렸는지가 드러난다. 전제를 안 적으면 다음 사람이 다른 자리에서 돌리고 이 줄이 없으면 무엇이 지워졌는지 알 수 없다.
정리
- 상대 경로는
pwd기준이지 스크립트 위치가 아니다 dirname "$0"과pwd로 절대 경로를 만든다- 만든 경로를 실행 전에 출력해 사람이 보게 한다
- 경로가 맞아도
.app-root를test -f로 확인한다 - 지우기 전에
dry-run으로 무엇을 지울지 보인다 count상한을 두고 넘으면exit 1한다grep -rn으로 상대 경로를 쓰는 스크립트를 전부 찾는다- 전제를 적고
date -Is·whoami·pwd를 실행 기록에 남긴다