Writing a Robust Script
한국어 원문으로 표시합니다.
목표
운영 서버에 올려도 되는 스크립트의 조건 - 실패를 숨기지 않고, 뒷정리를 보장하고, 두 번 돌아도 안전한 - 을 하나씩 구현합니다.
왜 중요한가
셸은 기본적으로 관대합니다. 없는 변수는 빈 문자열로 넘어가고, 파이프 중간의 실패는 마지막 명령이 성공하면 묻히고, 스크립트가 죽어도 임시 파일은 남습니다. 이 관대함이 "매일 성공으로 보고되던 백업이 사실 0바이트였다" 같은 사고를 만듭니다. set -euo pipefail 은 문제를 발견 지점에서 멈춰 세우는 장치이고, trap ... EXIT 는 어떤 경로로 끝나든 뒷정리가 돌게 하는 장치입니다. 두 개는 짝으로 써야 의미가 있습니다.
단계
/root/robust/strict.sh를 만드세요. 맨 앞에set -euo pipefail을 넣고, 인자를 하나 받아 그대로 출력합니다.strict.sh hello는hello를 출력하고 0 으로, 인자 없이 실행하면 0이 아닌 코드로 끝나야 합니다./root/robust/pipe.sh를 만드세요. 인자가ok면 성공하는 파이프라인을,broken이면 앞 단계가 실패하는 파이프라인을 실행합니다.broken일 때 종료 코드가 0이 아니어야 합니다./root/robust/tmpwork.sh를 만드세요.mktemp로 임시 파일을 만들어 그 경로를 표준출력에 한 줄 출력하고, 종료할 때trap으로 반드시 지웁니다. 인자로fail을 주면 실패로 끝나되 그때도 임시 파일이 남으면 안 됩니다./root/robust/once.sh를 만드세요. 실행하면 약 3초간 작업하고 0 으로 끝납니다. 이미 실행 중일 때 다시 실행하면already running을 알리고 종료 코드 9 로 끝나야 합니다. 앞 실행이 끝난 뒤에는 다시 정상 실행돼야 합니다./root/robust/count.sh <디렉터리>를 만드세요. 그 디렉터리 바로 아래의 일반 파일 개수만 출력합니다. 디렉터리 경로와 파일 이름 모두에 공백이 있어도 정확해야 하고, 인자가 없으면 0이 아닌 코드로 끝나야 합니다./root/robust/backup.sh <원본디렉터리> <결과파일>을 만드세요.- 인자가 없으면 표준에러에
usage를 포함한 안내를 내고 종료 코드64 - 원본 경로가 없으면 종료 코드
66 - 정상이면 하위 디렉터리까지 포함해 tar.gz 를 만들고 종료 코드
0
- 인자가 없으면 표준에러에
/root/robust/retry.sh <횟수> <명령...>을 만드세요. 명령이 실패하면 최대 그 횟수만큼 재시도하되, 시도 사이에 최소 1초는 쉬고, 성공하면 즉시 멈춥니다. 매 시도마다attempt라는 단어가 들어간 로그를 남깁니다./root/robust/audit.sh <스크립트경로>를 만드세요. 대상 스크립트에 셔뱅과 엄격 모드 선언이 있는지 검사해strict=yes|no와shebang=yes|no두 줄을 출력합니다. 둘 다 yes 면 종료 코드 0, 아니면 0이 아닌 코드. 파일이 없어도 0이 아닌 코드여야 합니다.
참고
trap핸들러 맨 앞에서local rc=$?로 종료 코드를 붙잡고 마지막에exit "$rc"로 되돌려주지 않으면 트랩이 종료 코드를 삼킵니다.find "$d" -maxdepth 1 -type f로 최상위 일반 파일만 셀 수 있습니다.- 흔한 실수 1: 4번에서 잠금 파일만 만들고 지우지 않으면 다음 실행이 영원히 막힙니다.
- 흔한 실수 2: 7번에서
$*로 명령을 다시 실행하면 공백이 든 인자가 깨집니다.
엄격 모드 켜기
/root/robust/strict.sh 를 만드세요. 맨 앞에 set -euo pipefail 을 넣고, 인자를 하나 받아 그대로 출력합니다. strict.sh hello 는 hello 를 출력하고 0 으로, 인자 없이 실행하면 0이 아닌 코드로 끝나야 합니다.
set -euo pipefail 한 줄이면 됩니다. -u 가 걸려 있으면 없는 $1 을 읽는 순간 죽어야 합니다.
파이프라인 실패 전파
/root/robust/pipe.sh 를 만드세요. 인자가 ok 면 성공하는 파이프라인을, broken 이면 앞 단계가 실패하는 파이프라인을 실행합니다. broken 일 때 종료 코드가 0이 아니어야 합니다.
기본 셸은 마지막 명령의 상태만 봅니다. 앞 단계가 실패했을 때도 실패로 끝나게 만드세요.
임시 파일을 반드시 지우기
/root/robust/tmpwork.sh 를 만드세요. mktemp 로 임시 파일을 만들어 그 경로를 표준출력에 한 줄 출력하고, 종료할 때 trap 으로 반드시 지웁니다. 인자로 fail 을 주면 실패로 끝나되 그때도 임시 파일이 남으면 안 됩니다.
trap ... EXIT 는 정상·오류 종료 모두에서 돕니다. 임시 파일은 mktemp 로 만드세요.
중복 실행 막기
/root/robust/once.sh 를 만드세요. 실행하면 약 3초간 작업하고 0 으로 끝납니다. 이미 실행 중일 때 다시 실행하면 already running 을 알리고 종료 코드 9 로 끝나야 합니다. 앞 실행이 끝난 뒤에는 다시 정상 실행돼야 합니다.
flock 이나 mkdir 기반 잠금을 씁니다. 두 번째 실행은 즉시 종료 코드 9 로 끝나야 하고, 앞 실행이 끝나면 잠금이 풀려야 합니다.
공백이 든 경로 안전하게 다루기
/root/robust/count.sh <디렉터리> 를 만드세요. 그 디렉터리 바로 아래의 일반 파일 개수만 출력합니다. 디렉터리 경로와 파일 이름 모두에 공백이 있어도 정확해야 하고, 인자가 없으면 0이 아닌 코드로 끝나야 합니다.
경로에도, 파일 이름에도 공백이 있습니다. 변수를 감싸지 않으면 개수가 틀어집니다.
사용법과 종료 코드 규약
/root/robust/backup.sh <원본디렉터리> <결과파일> 을 만드세요.
- 인자가 없으면 표준에러에
usage를 포함한 안내를 내고 종료 코드64 - 원본 경로가 없으면 종료 코드
66 - 정상이면 하위 디렉터리까지 포함해 tar.gz 를 만들고 종료 코드
0
사용법 안내는 표준에러로, 상황별로 다른 종료 코드로. 64 는 사용법 오류, 66 은 입력 없음입니다.
재시도 헬퍼
/root/robust/retry.sh <횟수> <명령...> 을 만드세요. 명령이 실패하면 최대 그 횟수만큼 재시도하되, 시도 사이에 최소 1초는 쉬고, 성공하면 즉시 멈춥니다. 매 시도마다 attempt 라는 단어가 들어간 로그를 남깁니다.
명령을 그대로 다시 실행하려면 "$@" 가 필요합니다. 성공하면 즉시 멈추고, 시도 사이에는 쉬어야 합니다.
스크립트 감사 도구
/root/robust/audit.sh <스크립트경로> 를 만드세요. 대상 스크립트에 셔뱅과 엄격 모드 선언이 있는지 검사해 strict=yes|no 와 shebang=yes|no 두 줄을 출력합니다. 둘 다 yes 면 종료 코드 0, 아니면 0이 아닌 코드. 파일이 없어도 0이 아닌 코드여야 합니다.
다른 스크립트 파일을 읽어 셔뱅과 엄격 모드 여부를 판정합니다. 없는 파일도 처리해야 합니다.