iBetter Books
수정

클린업 패턴

스크립트가 실행 중에 임시 파일을 만들었다고 가정합시다. 작업이 정상적으로 끝나면 삭제하겠지만, 도중에 오류가 생기거나 Ctrl+C로 중단되면 어떻게 될까요? 임시 파일이 남습니다. 디스크 공간이 서서히 채워지고, 다음 실행에서 이전 임시 파일과 충돌이 생길 수 있습니다.

클린업 패턴은 이 문제를 원천 차단합니다. 스크립트 시작 시 cleanup 함수를 정의하고, EXIT trap에 등록해두면 어떤 상황에서 종료되든 뒷정리가 보장됩니다.

cleanup 함수 패턴

가장 기본적인 클린업 패턴입니다.

#!/usr/bin/env bash# 파일: basic_cleanup.shset -euo pipefailTEMP_FILE=""cleanup() {    echo "정리 중..."    [[ -n "$TEMP_FILE" && -f "$TEMP_FILE" ]] && rm -f "$TEMP_FILE"    echo "완료"}trap cleanup EXIT# 이제부터 임시 파일 생성TEMP_FILE=$(mktemp)echo "임시 파일: $TEMP_FILE"# 작업 수행echo "데이터 처리 중..." > "$TEMP_FILE"sleep 2cat "$TEMP_FILE"# 명시적 삭제 없이 종료해도 cleanup이 처리함

TEMP_FILE을 빈 문자열로 초기화한 뒤 mktemp 결과를 담는 이유가 있습니다. trap이 등록된 순간부터 EXIT가 발생하면 cleanup이 실행되는데, mktemp 실행 전에 오류가 나면 $TEMP_FILE이 비어있게 됩니다. 그래서 cleanup 함수에서 변수가 비어있지 않고 파일이 존재할 때만 삭제하도록 방어 코드를 넣어야 합니다.

mktemp로 임시 파일 안전하게 생성

직접 /tmp/myfile.tmp처럼 고정된 이름으로 임시 파일을 만들면 문제가 생깁니다. 같은 스크립트를 두 번 실행하면 파일이 충돌하고, 다른 사용자가 같은 이름의 파일을 이미 만들어 놓을 수도 있습니다. mktemp는 중복 없는 고유한 임시 파일 이름을 생성합니다.

# 임시 파일 생성 (기본: /tmp/tmp.XXXXXXXX)TEMP_FILE=$(mktemp)echo "생성된 파일: $TEMP_FILE"# 출력: /tmp/tmp.Ab3xYz12# 접미사 지정TEMP_FILE=$(mktemp --suffix=.csv)# 출력: /tmp/tmp.Ab3xYz12.csv# 이름 패턴 지정TEMP_FILE=$(mktemp /tmp/myapp.XXXXXX)# 출력: /tmp/myapp.Ab3xYz# 임시 디렉토리 생성TEMP_DIR=$(mktemp -d)echo "생성된 디렉토리: $TEMP_DIR"# 출력: /tmp/tmp.Ab3xYz12

XXXXXX는 mktemp가 무작위 문자로 채워넣는 자리입니다. 최소 3개 이상의 X가 필요합니다.

임시 디렉토리 패턴

파일이 여러 개 필요하다면 임시 디렉토리를 만들고 그 안에서 작업하는 것이 편합니다.

#!/usr/bin/env bash# 파일: temp_dir_pattern.shset -euo pipefailTEMP_DIR=""cleanup() {    if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then        rm -rf "$TEMP_DIR"        echo "임시 디렉토리 삭제: $TEMP_DIR"    fi}trap cleanup EXITTEMP_DIR=$(mktemp -d)echo "작업 디렉토리: $TEMP_DIR"# 임시 디렉토리 안에서 모든 중간 파일 생성echo "원본 데이터" > "${TEMP_DIR}/input.txt"sort "${TEMP_DIR}/input.txt" > "${TEMP_DIR}/sorted.txt"uniq "${TEMP_DIR}/sorted.txt" > "${TEMP_DIR}/unique.txt"echo "처리 결과:"cat "${TEMP_DIR}/unique.txt"# cleanup이 디렉토리 전체를 한 번에 삭제

PID 파일(lockfile) 관리 패턴

같은 스크립트가 동시에 두 번 이상 실행되면 안 되는 경우가 있습니다. 데이터베이스 백업, 재고 계산, 정산 처리 같은 작업입니다. PID 파일로 이를 방지합니다.

#!/usr/bin/env bash# 파일: pid_lock.shset -euo pipefailPID_FILE="/var/run/myapp.pid"cleanup() {    rm -f "$PID_FILE"}# 이미 실행 중인지 확인if [[ -f "$PID_FILE" ]]; then    old_pid=$(cat "$PID_FILE")    if kill -0 "$old_pid" 2>/dev/null; then        echo "오류: 이미 실행 중입니다. PID: $old_pid"        exit 1    else        echo "경고: 이전 PID 파일이 남아있습니다. 제거 후 계속합니다."        rm -f "$PID_FILE"    fifi# PID 파일 생성echo $$ > "$PID_FILE"trap cleanup EXITecho "작업 시작. PID: $$"echo "PID 파일: $PID_FILE"# ... 실제 작업 ...sleep 10echo "작업 완료"

PID 파일을 확인할 때 파일만 있다고 실행 중이라고 판단하면 안 됩니다. 이전 실행이 비정상 종료되면 PID 파일이 남을 수 있습니다. kill -0 PID로 해당 PID의 프로세스가 실제로 살아있는지 확인하는 것이 중요합니다.

다중 클린업 작업 관리

여러 리소스를 정리해야 할 때는 cleanup 함수 안에서 순서를 정해 처리합니다.

#!/usr/bin/env bash# 파일: multi_cleanup.shset -euo pipefailTEMP_DIR=""PID_FILE="/tmp/myapp.pid"DB_CONNECTED=falsecleanup() {    local exit_code=$?    # 1. 데이터베이스 연결 해제    if $DB_CONNECTED; then        echo "DB 연결 해제..."        # db_disconnect 명령어 실행        DB_CONNECTED=false    fi    # 2. 임시 파일 정리    if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then        echo "임시 파일 정리..."        rm -rf "$TEMP_DIR"    fi    # 3. PID 파일 삭제    rm -f "$PID_FILE"    # 4. 종료 로그    if [[ $exit_code -ne 0 ]]; then        echo "비정상 종료. 종료 코드: $exit_code" >&2    fi    exit "$exit_code"}trap cleanup EXIT# PID 파일 생성echo $$ > "$PID_FILE"# 임시 디렉토리 생성TEMP_DIR=$(mktemp -d)# 작업 수행echo "작업 시작..."DB_CONNECTED=true# ... 실제 작업 ...echo "완료"

cleanup 함수 안에서 종료 코드를 저장해두고, 마지막에 그 코드로 exit하는 것이 중요합니다. EXIT trap 안에서 exit를 호출하면 trap이 다시 실행되지 않으므로 안전합니다.

실용적인 cleanup 템플릿

다음은 실무에서 바로 사용할 수 있는 클린업 패턴 템플릿입니다.

#!/usr/bin/env bash# 파일: cleanup_template.shset -euo pipefail# --- 전역 변수 ---TEMP_DIR=""PID_FILE="/tmp/$(basename "$0" .sh).pid"SCRIPT_NAME=$(basename "$0")LOG_FILE="/tmp/${SCRIPT_NAME%.sh}.log"# --- 로그 함수 ---log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }# --- 클린업 함수 ---cleanup() {    local exit_code=$?    log "정리 시작 (종료 코드: $exit_code)"    [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] && rm -rf "$TEMP_DIR"    rm -f "$PID_FILE"    log "정리 완료"    exit "$exit_code"}trap cleanup EXIT# --- 중복 실행 방지 ---if [[ -f "$PID_FILE" ]] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then    log "오류: 이미 실행 중입니다."    exit 1fiecho $$ > "$PID_FILE"# --- 메인 작업 ---TEMP_DIR=$(mktemp -d)log "작업 시작. 임시 디렉토리: $TEMP_DIR"# 여기에 실제 작업 코드 작성log "작업 완료"

이 템플릿을 복사해 실제 작업 코드만 채워 넣으면 안전한 클린업이 보장된 스크립트를 빠르게 만들 수 있습니다. 다음 절에서는 이 패턴들을 종합한 실전 스크립트를 작성합니다.