셸의 널 명령과 매개변수 확장을 활용해 필수 인수를 확인하고 기본값을 설정하는 방법을 살펴봅니다.
셸 스크립트를 셀 수 없을 만큼 많이 썼지만, 인정하기 싫을 정도로 자주 진심으로 머리를 날려 버리는 요령을 여전히 발견한다. 최근 내 머리를 완전히 날려 버린 것? 셸 콜론이다.
2026년 7월 23일 03:53 협정 세계시 게시 · 2026년 7월 26일 05:33 협정 세계시 수정 · 작성자 Filip Roséen · 태그
... 갓 내린 너무 뜨거운 커피 옆에 이미 너무 차가워진 커피 한 잔이 있었다. 한 시간 전에 세 개는 닫을 수 있었던 터미널 네 개, 그리고 정말로 (정말로) 쓰고 싶지 않았던 셸 스크립트 하나도 있었다.
단 하나의 콜론이 밤을 구할 줄 누가 알았겠는가?day
참고: 당장 머리가 날아갈 준비가 되었나? 주목받는 더 많은 콜론을 보라.
익숙한 춤이다. 이쯤 되면 거의 근육 기억이다. 스크립트가 있고, 몇 개의 인수를 받으며, 그중 일부는 필수다. 좋다, 이전에도 수없이 썼던 이런 if문이다.
if [ -z "$1" ]; then
echo "missing argument, aborting." 1>&2
exit 1
fi
echo "Hello $1!"
그런데.. 위 네 줄을 단 한 줄로 바꿀 수 있다고 하면 어떨까?
: "${1:?missing argument, aborting.}"
echo "Hello $1!"
$ bash example.sh
example.sh: line 1: 1: missing argument, aborting.
$ bash example.sh refp
Hello refp!
그리고 제대로 된 이름의 변수를 참조하면 어떤 일이 일어나는지 보라. 이전과 같은 동작이지만 더 쉽게 알아볼 수 있다. 진단 메시지에 변수 이름이 포함된다!
: "${GREET_NAME:?missing argument, aborting.}"
echo "Hello $GREET_NAME!"
$ bash greet.sh
greet.sh: line 1: GREET_NAME: missing argument, aborting.
:?의 이야기앞선 조각에는 두 가지 일이 일어나며, 그중 한 부분이 매개변수 확장을 사용한다는 판단은 정확하다.
문법 ${name:?diagnostic}은 $name이 설정되지 않았거나 비어 있는지 확인한다. 그렇다면 진단 메시지가 _표준 오류_로 출력되고 셸은 0이 아닌 상태로 종료한다. 그렇지 않으면;
변수가 설정되어 있다면 $name과 동등하다.
이제 콜론 하나는 알았다. 하지만 줄 맨 앞에 홀로 앉아 있는 다른 콜론은 어떨까?
:는 널 명령이다. 인수를 평가하고 결과를 버리는 일 외에는 아무것도 하지 않는 내장 명령이다.
:는 오래되었다. 1971년 Thompson 셸까지 거슬러 올라가며, այնտեղ에서는 레이블과 유닉스 최초의 주석 표시를 겸했다.
: 어둠 속에서 사랑을 담아 당신을 바라보는 두 눈.
어쩌면 우리는 이미 :가 보기보다 훨씬 많은 것을 담고 있다는 사실을 확인했을지도 모른다. 하지만 널 명령의 진짜 마법을 증명하기 위해, 내 머리를 날려 버린 몇 가지 사용법을 소개한다.
: "${DATA_DIR:=/var/data}" # set defaults, : swallows the result
: "${RETRIES:=3}" # instead of running it as a command
: > error.log # truncate error.log
: > error.log > access.log # truncate both error.log and access.log
( : < dataset.json ) && echo YES # is dataset.json readable?
( : >> result.json ) && echo YES # is result.json writable?
trap : INT # trap requires a command
sleep 60 # sleep is interruptible
set -u # error on unset variables
: "$DEPLOY_ENV" "$HOST" # check DEPLOY_ENV and HOST
if some-command; then
: # command required
else
echo "command failed"
fi
그러니 나처럼 타이핑을 적게 하는 쪽을 선호한다면(빨리 가야 하니까), 커피가 식기 전에 널 명령과 매개변수 확장은 공부할 만한 한 쌍이다.
그리고 또.. 이거 마법 아닌가?
set : : : : : : : : : : : : : : : : : : : : :
while : colons are more than "${1:?magic}"; do
echo "$*" && shift
done
참고: 위 예제는 로컬에서 안전하게 실행할 수 있으니, 시도해 보라!
온라인에서 몇몇 댓글을 읽고 나니, 설명할 만한 몇 가지를 건너뛴 듯하다. 질문이 나오면 이 섹션을 계속 업데이트하겠다.
매개변수 확장은 어쨌든 일어난다. 하지만 널 명령이나 이와 비슷한 용법이 없으면 셸은 결과 문자열을 실행할 명령으로 취급한다.
% ${HELLO:=123}
zsh: command not found: 123
매개변수 확장 앞에 널 명령을 붙이면 결과는 버려지지만, 식은 여전히 평가되어 HELLO가 123으로 설정된다.
% : ${HELLO:=123}
% echo $HELLO
123
VAR=${VAR:-default-value}를 쓸 수 있는데 왜 널 명령을 사용하는가?핵심적으로는 개인 취향의 문제지만, 사랑하는 콜론을 사용하면 잠재적인 오타의 수를 하나로 줄일 수 있다(둘이 아니라).
: "${DATA_DIR:=/var/data}" # <- DATA_DIR mentioned once (1)
DATA_DIR="${DATA_DRI:-/var/data}" # <- oops (2)