빌드노트
빈 변수인데 Bash의 [ -n $value ]가 참이 된다
빈 변수를 인용하지 않으면 [ -n $value ]에 -n 하나만 남아 참이 된다. 실제 인자 수와 종료 상태를 비교해 빈 값·공백·파일 이름 확장의 차이를 확인하고, 큰따옴표나 Bash [[ ]]로 문자열 경계를 유지한다.
Bash에서 value=''로 비워 두었는데도 if [ -n $value ]가 참 분기로 갈 수 있다. 인용하지 않은 빈 변수의 확장이 인자에서 빠지면서, 실제로는 [ -n ]이 실행되기 때문이다. 문자열 길이를 검사하려면 [ -n "$value" ]처럼 값의 자리를 보존한다. Bash로 실행하는 코드라면 [[ -n $value ]]도 사용할 수 있다.
먼저 같은 빈 값으로 세 표현의 종료 상태를 비교해 보자. 아래 명령은 Bash에서 실행한다. 새 Bash 5.2.21에서 직접 확인했으며, 상태 0은 참, 1은 거짓이다.
value=''
[ -n $value ]; printf 'unquoted=%d\n' "$?"
[ -n "$value" ]; printf 'quoted=%d\n' "$?"
[[ -n $value ]]; printf 'double_bracket=%d\n' "$?"
unquoted=0
quoted=1
double_bracket=1
-n이 빈 문자열을 참으로 판정한 것처럼 보이지만, 첫 번째 명령에는 검사할 빈 문자열 인자가 도착하지 않았다. 따옴표를 붙이면 그 자리가 유지되어 기대한 거짓이 된다. 이미 실행한 명령의 상태는 바로 뒤에서 읽어야 한다. 다른 명령을 실행하면 $?도 바뀐다.
-n 하나만 남으면 문자열 하나를 검사한다
Bash의 [는 test와 같은 조건 검사 내장 명령이며 끝에 ]가 필요하다. 닫는 ]를 제외한 조건식의 인자 수에 따라 해석이 달라진다. 공식 test 설명 (새 탭에서 열림)에 따르면, 인자 하나일 때는 그 문자열이 비어 있지 않으면 참이다. 두 인자가 -n과 문자열이면 두 번째 문자열의 길이를 검사한다.
따라서 [ -n ]의 조건식은 비어 있지 않은 문자열 -n 하나다. [ -n "" ]은 -n과 빈 문자열이라는 두 인자로 구성된다. 같은 글자 -n이 있어도 두 명령의 검사 대상은 다르다.
이 차이는 값만 출력해서는 잘 보이지 않는다. 다음 함수는 받은 인자 수와 각 인자의 경계를 출력한다. [ 대신 같은 인자를 함수에 전달해, 확장 뒤 어떤 목록이 만들어지는지 확인한다.
show_args() {
printf 'argc=%d' "$#"
for arg do printf ' <%s>' "$arg"; done
printf '\n'
}
value=''
show_args -n $value
show_args -n "$value"
value='alpha beta'
show_args -n $value
show_args -n "$value"
argc=1 <-n>
argc=2 <-n> <>
argc=3 <-n> <alpha> <beta>
argc=2 <-n> <alpha beta>
빈 값의 인용 전에는 -n 하나만 남고, 인용 후에는 <>로 표시한 빈 인자가 있다. alpha beta는 인용하지 않으면 두 단어로 나뉘어 조건식 인자가 세 개가 된다. 이 입력의 [ -n $value ]는 실제로 binary operator expected 진단과 상태 2를 냈다. 상태 2도 if에서는 참 분기로 가지 않으므로, 이를 단순히 “값이 비어서 거짓”이라고 읽으면 오류를 놓친다.
이 관찰은 Bash의 단어 분리 규칙 (새 탭에서 열림)과 일치한다. 큰따옴표 밖에서 확장한 값은 IFS에 따라 나뉘고, 인용하지 않은 빈 확장은 인자에서 제거된다. 큰따옴표 안의 빈 값은 빈 문자열 인자로 유지된다. 위 비교는 IFS가 기본 공백·탭·개행인 조건이다.
공백과 별표에서는 성공처럼 보여도 값이 달라진다
같은 비교에 hello를 넣으면 세 표현 모두 상태 0이었다. 일반 문자열 하나만으로 검사하면 인용 누락을 발견하기 어렵다. 공백 세 개를 넣었을 때도 모두 상태 0이었지만, 이유는 달랐다. 인용 전에는 공백이 사라져 -n만 남았고, 인용 후와 [[ ]]에서는 길이 3인 문자열을 검사했다. -n은 공백만 있는 값도 비어 있지 않은 값으로 취급한다. 공백뿐인 입력을 거절해야 한다면 별도의 입력 규칙이 필요하다.
value='*'도 확인했다. 파일 이름 확장 규칙 (새 탭에서 열림)에 따라, 인용하지 않은 별표는 현재 폴더의 파일 목록으로 바뀔 수 있다. 새 빈 폴더에서 시작해 합성 파일만 하나씩 추가한 관찰은 다음과 같았다. nullglob과 failglob는 꺼진 상태였다.
- 파일이 없을 때:
show_args -n $value는argc=2 <-n> <*>를 출력했고 조건은 상태 0이었다. a.txt만 있을 때:argc=2 <-n> <a.txt>로 바뀌었지만 조건은 여전히 상태 0이었다.a.txt,b.txt가 있을 때:argc=3 <-n> <a.txt> <b.txt>가 되었고 조건은 상태 2의 오류를 냈다.
세 경우 모두 인용한 쪽은 argc=2 <-n> <*>를 유지했고 상태 0이었다. 파일 하나일 때는 결과만 보면 정상처럼 보여도 검사 대상은 이미 별표에서 파일 이름으로 바뀌었다. 다른 입력이나 폴더에서 나온 인용 전 결과를 그대로 예상하지 말고, 먼저 show_args로 값의 경계가 유지되는지 확인한다.
값 하나를 유지하는 조건으로 고친다
기존 if에서는 변수 확장에 큰따옴표를 붙이면 된다. 다음 코드는 빈 값에서 value empty를 출력한다. value='alpha beta'로 바꾸면 value present를 출력한다.
value=''
if [ -n "$value" ]; then
printf '%s\n' 'value present'
else
printf '%s\n' 'value empty'
fi
Bash로 실행할 스크립트라면 조건 부분을 [[ -n $value ]]로 바꿀 수도 있다. 이 조건에서는 값이 단어로 분리되거나 파일 이름 목록으로 확장되지 않는다. Bash 조건 구문 설명 (새 탭에서 열림)에 해당하며, 이번 비교에서도 빈 값만 상태 1이고 나머지 입력은 상태 0이었다. [[ ]]는 Bash 구문이므로 일반적인 POSIX sh용 스크립트에는 인용한 [ -n "$value" ]를 사용한다. 이 선택이 다른 모든 연산자의 인용 규칙까지 같다는 뜻은 아니다.
직접 실행한 범위는 WSL Ubuntu 24.04의 Bash 5.2.21, 기본 IFS, 파일 이름 확장이 켜진 새 프로세스다. 공식 온라인 매뉴얼은 Edition 5.3을 참고했다. 다른 셸이나 변경한 IFS·글롭 옵션의 전체 조합은 실행하지 않았다. 예제에서는 변수에 값을 먼저 할당했으며, set -u 상태의 미설정 변수를 읽는 문제는 이 비교에 포함하지 않았다.