빌드노트
Bash 래퍼에서 인자 세 개가 하나로 합쳐진다
Bash 래퍼에서 "$*"는 공백 있는 값과 빈 문자열까지 한 인자로 합친다. 실제 자식의 인자 수와 값을 비교해 "$@"가 경계를 보존함을 확인하고, 개수가 같아도 값이 달라지는 인용 누락과 빈 인자·무인자의 차이를 설명한다.
Bash 래퍼에 공백 있는 값과 빈 문자열을 넘겼는데 자식 명령이 다른 인자를 받는다면, 래퍼의 마지막 호출 줄을 확인한다. "$*"는 받은 인자를 한 문자열로 합친다. $@도 따옴표 없이 쓰면 공백에서 다시 나뉘고 빈 인자가 사라질 수 있다. 받은 인자를 그대로 전달할 때는 "$@"를 독립된 인자 자리에 쓴다.
예를 들어 bash ./argv.sh "$*"로 전달하던 줄은 bash ./argv.sh "$@"로 바꾼다. 처음 호출할 때 붙인 따옴표가 래퍼 안의 확장까지 보호해 주지는 않는다. 각 호출 단계에서 인자 경계를 유지해야 한다.
세 인자가 넘어가는 모양을 직접 본다
운영 장애 기록 대신 합성 입력으로 비교했다. 실행 환경은 WSL2의 Ubuntu 24.04.4, Bash 5.2.21이며 기본 IFS인 공백·탭·개행을 사용했다. Bash가 있는 빈 실습 폴더에서 다음 두 파일을 준비하면 같은 입력을 확인할 수 있다. 파일은 LF 줄바꿈으로 저장하고, 두 파일이 있는 폴더에서 명령을 실행한다.
먼저 자식 명령이 받은 개수와 값을 확인하도록 아래를 argv.sh에 저장한다. 출력 후 종료하는 스크립트다. 여기서 argc는 스크립트 이름을 제외한 인자 수인 $#이고, 출력의 argv[0]은 첫 번째 입력 인자에 붙인 표시다.
#!/usr/bin/env bash
printf 'argc=%d\n' "$#"
i=0
for arg in "$@"; do
printf 'argv[%d]=<%s>\n' "$i" "$arg"
i=$((i + 1))
done
래퍼의 전달 표현만 비교하기 위해 다음을 같은 폴더의 wrapper.sh에 저장한다. 마지막 호출은 인용하지 않았을 때의 문제를 확인하려고 일부러 $@로 썼다.
#!/usr/bin/env bash
printf '%s\n' 'quoted star'
bash ./argv.sh "$*"
printf '%s\n' 'quoted at'
bash ./argv.sh "$@"
printf '%s\n' 'unquoted at'
bash ./argv.sh $@
공백을 포함한 값 하나, 빈 문자열 하나, 일반 문자열 하나를 아래처럼 전달한다. ''도 인자 하나다. bash로 파일을 읽어 실행하므로 별도의 실행 권한 부여는 필요 없다.
bash wrapper.sh 'hello world' '' plain
실제 출력은 다음과 같았다. 명령은 정상 종료했지만 세 호출이 받은 값은 달랐다.
quoted star
argc=1
argv[0]=<hello world plain>
quoted at
argc=3
argv[0]=<hello world>
argv[1]=<>
argv[2]=<plain>
unquoted at
argc=3
argv[0]=<hello>
argv[1]=<world>
argv[2]=<plain>
개수가 같아도 같은 인자가 아니다
"$*"를 쓴 첫 호출에서는 세 값이 한 인자가 됐다. world와 plain 사이의 공백 두 개는 원래 값 사이에 들어간 구분자다. 빈 문자열의 자리가 공백으로 남아 보여도, 자식에게 별도의 빈 인자가 전달된 것은 아니다.
GNU Bash 매뉴얼은 큰따옴표 안의 $*가 IFS의 첫 문자로 인자들을 이어 붙인다고 설명한다. 반면 명령의 인자 자리에서 "$@"는 각각을 "$1" "$2" …처럼 확장한다. 둘째 호출의 빈 argv[1]=<>도 그래서 유지됐다. Bash의 Special Parameters (새 탭에서 열림)
셋째 호출은 개수만 보면 정상처럼 보인다. 인용하지 않은 $@가 hello world를 두 인자로 나누는 동시에 빈 문자열을 없앴기 때문에 결과도 세 개다. 원하는 hello world, 빈 문자열, plain과는 다른 입력이다. 별도로 실행한 인용 없는 $*도 이 입력과 기본 IFS에서는 같은 결과였다. 인용하지 않은 확장의 단어 분리와 빈 값 제거는 Word Splitting 규칙 (새 탭에서 열림)에 따른다.
일반 문자열만 넣은 추가 비교에서는 alpha beta plain이 "$@"와 인용 없는 두 표현 모두에서 그대로 세 인자로 전달됐다. 공백과 빈 문자열이 없는 입력으로만 시험하면 인용 누락을 놓칠 수 있다. "$*"는 이때도 alpha beta plain이라는 한 인자를 만들었다.
인용 없는 확장은 파일명 패턴에도 영향을 받는다. 매뉴얼에 따르면 단어 분리 뒤에 *, ?, [를 포함한 값이 파일명 확장의 대상이 될 수 있다. 이 글의 세 입력에는 그런 패턴이 없으므로 위 출력에 파일명 확장은 관여하지 않았다. Filename Expansion 규칙 (새 탭에서 열림)
수정한 뒤에는 빈 인자와 무인자를 구별한다
자기 래퍼에서는 실제 자식 명령을 호출하는 줄의 전달 부분을 "$@"로 바꾸고, 같은 입력으로 각 값까지 대조한다. 자식 명령에 앞서 잠시 위 출력기를 연결하면 공백 있는 값이 하나로 남았는지, 빈 인자가 제자리에 있는지 확인할 수 있다. 호출한 쪽에서 이미 hello world를 두 인자로 넘겼다면 "$@"도 그 두 인자를 유지한다. 따라서 래퍼에 들어오는 입력부터 잘못됐는지, 래퍼를 지난 뒤 달라졌는지도 나눠 본다.
입력이 없는 경우도 다르다. 같은 폴더에서 bash wrapper.sh로 실행하자 "$*" 호출은 argc=1과 argv[0]=<>를 출력했다. "$@" 호출은 argc=0만 출력했다. 빈 문자열 한 개와 인자 0개는 다르다. 출력기가 인자마다 반복하도록 작성된 이유도 이 차이를 보려는 것이다.
구분자 설정으로 원래 경계를 복원하려고 할 필요는 없다. 별도 자식 Bash에서 IFS=:로 비교하자 "$*"는 hello world::plain이라는 한 인자가 됐고, IFS를 빈 값으로 두면 hello worldplain이 됐다. 두 조건 모두 "$@"는 처음의 세 인자를 유지했다. 인자 목록을 보존하려는 래퍼에서는 합쳐진 문자열을 다시 나누는 단계를 만들지 않는다.
참고한 유지관리자 사이트의 GNU Bash 매뉴얼은 5.3판이며, 위 출력은 Bash 5.2.21에서 직접 확인한 것이다. 다른 셸과 Bash 5.3 실행, 개행·비ASCII 문자·파일명 패턴이 든 인자는 이번에 비교하지 않았다. 실제 프로그램의 옵션 해석도 이 출력기가 검증하지는 않는다. 전달된 개수와 값이 모두 기대와 같은데 동작이 다르면, 다음으로 자식 프로그램의 옵션 처리와 그 뒤의 호출 단계를 확인한다.