본문 바로가기
빌드노트 검색

빌드노트

Bash printf에서 로그 값의 %s가 사라진다면

Bash에서 printf "$value"는 값 속의 %와 역슬래시를 형식으로 읽는다. percent%s의 조용한 손실과 100%의 부분 출력·오류를 비교하고, 고정 형식의 %s로 원문을 출력하는 방법과 %b로 의도적으로 이스케이프를 해석하는 경우를 구분한다.

Bash에서 로그 값을 printf "$value"로 출력하면 값 속의 %s가 사라지거나 \n이 줄바꿈으로 바뀔 수 있다. 변수를 따옴표로 감싸도 printf는 그 첫 인자를 형식 문자열로 읽는다. 문자열을 그대로 출력하려면 printf '%s\n' "$value"처럼 고정 형식과 데이터 인자를 분리한다. 이 호출은 값 뒤에 개행 하나를 더한다. 개행을 더하지 않으려면 printf '%s' "$value"를 쓴다.

오류 없이 사라지는 %s

다음은 운영 로그를 사용하지 않고 합성 문자열로 비교한 예제다. Linux나 WSL에서 bash --noprofile --norc로 새 Bash를 열고 export LC_ALL=C를 실행한다. type -a printf의 첫 줄이 printf is a shell builtin이면 아래와 같은 내장 명령을 대상으로 비교할 수 있다. 함수나 별칭이 먼저 나오면 호출 대상을 확인하거나 builtin printf로 내장 명령을 지정한다. 직접 실행한 환경은 WSL2의 Ubuntu 24.04.4 LTS, Bash 5.2.21이다.

먼저 값 자체를 첫 인자로 넘겼을 때 내용이 바뀌면서도 성공으로 끝나는지 확인한다.

value='percent%s'
printf "$value"
printf ' (status=%d)\n' "$?"
percent (status=0)

첫 호출이 출력한 것은 개행 없는 percent뿐이다. 뒤의 상태 표시와 개행은 두 번째 호출이 붙였다. printf는 값 속의 %s를 다음 문자열 인자를 출력하라는 지시로 읽었는데, 다음 인자를 주지 않았다. Bash 매뉴얼은 형식이 요구하는 인자가 부족하면 문자열은 빈 문자열로 처리한다고 설명한다. 그래서 %s가 빈 문자열로 대체됐고, 종료 상태 0만 확인해서는 손실을 찾을 수 없었다. GNU Bash 매뉴얼의 printf 설명 (새 탭에서 열림)

따옴표는 변수 확장 결과를 하나의 인자로 전달하는 역할을 한다. 그 인자를 받은 printf의 형식 해석까지 끄지는 않는다. 호출을 고칠 때는 "$value"를 유지하면서 그 앞에 고정 형식을 둔다.

다음은 같은 변수에 세 값을 차례로 넣고, 모두 문자열 인자로 출력하는 코드다.

value='percent%s'
printf '%s\n' "$value"
value='100%'
printf '%s\n' "$value"
value='backslash\nend'
printf '%s\n' "$value"
percent%s
100%
backslash\nend

세 호출 모두 오류 출력 없이 상태 0으로 끝났다. %s는 뒤의 값을 문자열로 출력하며, 그 값 안의 %나 역슬래시를 다시 형식으로 해석하지 않았다. 형식에 쓴 \n만 마지막 개행이 된다. 특히 value='backslash\nend'의 작은따옴표 안에는 실제 개행이 아니라 역슬래시와 문자 n이 들어 있다.

같은 문제 호출을 value='100%'로 바꿔 실행하면 증상이 달라진다. 내장 printf "$value"는 표준 출력에 개행 없는 100을 남기고, 표준 오류에 missing format character가 포함된 메시지를 쓰며 상태 1로 끝났다. 끝의 % 뒤에 변환 문자가 없기 때문이다. 출력이 일부 보이거나 종료 상태가 0이라고 해서 원문이 보존된 것은 아니다. 값에 따라 정상처럼 보이기도 하고 오류가 나기도 한다면, 출력 호출의 첫 인자에 변수가 들어가 있는지 확인한다.

역슬래시를 해석하려는 경우는 %b로 구분한다

역슬래시 표기를 실제 줄바꿈으로 바꾸는 것이 목적일 때는 Bash의 %b를 선택할 수 있다. 아래 두 호출은 같은 값을 두고 데이터 인자의 역슬래시 해석 여부만 비교한다.

value='backslash\nend'
printf '%s\n' "$value"
printf '%b\n' "$value"
backslash\nend
backslash
end

첫 호출은 한 줄, 두 번째 호출은 두 줄을 출력했다. %b는 데이터 인자의 역슬래시 이스케이프를 해석하도록 지정하는 형식이다. 따라서 원문 로그를 출력할 자리에서 %s 대신 %b를 쓰면 \n이 다시 줄바꿈이 된다. 의도한 변환이 있을 때만 사용한다. 반대로 값에 이미 실제 개행이 들어 있다면 %s도 그 개행을 그대로 출력하므로, %s만으로 모든 로그를 한 줄로 만드는 것은 아니다. %b의 계약은 Bash 매뉴얼 (새 탭에서 열림)에서 확인했고, 위 출력은 Bash 5.2.21에서 직접 얻었다.

외부 printf로 실행하면 무엇이 달라질까

이 환경에서 type -a printf는 내장 명령과 /usr/bin/printf를 함께 표시했다. 이름만 쓴 호출은 내장 명령이었고, /usr/bin/printf를 명시하면 GNU Coreutils 9.4 프로그램이 실행됐다. 외부 구현으로 확인할 때도 value='percent%s'를 넣은 뒤 /usr/bin/printf '%s\n' "$value"처럼 형식과 데이터를 분리한다. 외부 프로그램도 첫 인자에서 % 지시자와 역슬래시 이스케이프를 해석한다. GNU Coreutils 9.4 매뉴얼 원문 (새 탭에서 열림)

외부 구현에서도 위 세 값의 문제 호출과 %s 호출은 같은 출력·종료 상태를 보였다. 다만 100%의 오류 문구는 invalid conversion specification이었다. 오류 문자열 전체가 같다고 가정하지 말고 실제 실행한 명령과 종료 상태를 함께 본다. 내장 명령을 확실히 지정하려면 Bash에서 builtin printf '%s\n' "$value"를 사용할 수 있다.

문서 설명은 Bash 유지관리자가 제공하는 5.3 매뉴얼과 Coreutils 9.4 매뉴얼을 확인했다. 실행 관찰은 Bash 5.2.21과 Coreutils 9.4의 합성 텍스트에 한정하며 다른 셸이나 바이너리 데이터의 무손실 출력은 검증하지 않았다. 자기 코드에서는 로그 값을 첫 인자에 넣은 호출을 찾아 고정 형식의 %s 뒤로 옮기고, 줄 끝 개행을 추가할지도 함께 정하면 된다.

← 전체 글 보기