컴퓨터가 인간이 읽을 수 있는 출력을 만들어내는 일은 의외로 만만치 않습니다. 표준 스트림, 그중에서도 표준 출력(stdout)의 등장으로 프로그램들은 일반 텍스트 스트림을 통해 서로 소통할 수 있게 되었지만, 이를 사람이 읽기 좋은 형태로 화면에 표시하는 것은 전혀 다른 차원의 문제였습니다. 비디오 디스플레이에 ASCII 문자를 사용하던 초기 컴퓨팅 시절부터 echo, printf 같은 현대의 셸 명령어에 이르기까지, 컴퓨팅 기술은 줄곧 이 문제를 해결하기 위해 발전해 왔습니다.
하지만 이러한 발전의 길은 순탄하지 않았습니다. 터미널에 출력을 인쇄하는 작업에는 프로그래머가 짚고 넘어가야 할 여러 가지 특수한 동작들이 숨어 있으며, 그 대표적인 예가 바로 이스케이프 시퀀스를 확장해 줄바꿈을 출력하는 일입니다. 겉보기에는 단순해 보이지만 결코 쉽지 않은 이 작업에서 \n 플레이스홀더를 확장하는 방법은 여러 가지가 있고, 각 방식마다 저마다의 역사와 복잡한 사연을 담고 있습니다.
echo 사용하기
Multics 시절부터 등장해 오늘날 유닉스 계열 시스템 곳곳에서 쓰이는 echo는 터미널에게 "Hello world!"를 외치게 하는 가장 익숙한 도구입니다. 그러나 안타깝게도 운영체제마다 구현 방식이 제각각이라 사용이 까다롭습니다. 어떤 시스템의 echo는 이스케이프 시퀀스를 자동으로 확장하는 반면, 다른 시스템에서는 -e 옵션을 붙여야 합니다:
echo "the study of European nerves is \neurology"
# the study of European nerves is \neurology
echo -e "the study of European nerves is \neurology"
# the study of European nerves is
# eurology
이런 구현상의 불일치 때문에 echo는 이식성이 떨어지는 명령어로 간주됩니다. 게다가 사용자 입력과 함께 사용할 경우 명령어 치환(command substitution)을 악용한 셸 인젝션 공격에도 비교적 쉽게 노출됩니다.
현대 시스템에서 echo는 아직 이를 사용하는 수많은 프로그램과의 호환성을 위해 남아 있을 뿐입니다. POSIX 표준은 새로 작성하는 프로그램에서 printf를 사용할 것을 권장합니다.
printf 사용하기
4판 유닉스(4th Edition Unix) 이후, 이식 가능한 printf 명령어는 사실상 더 새롭고 뛰어난 echo의 대체재로 자리 잡았습니다. 형식 지정자(format specifier)를 활용해 입력을 사람이 읽기 좋은 형태로 다듬을 수 있으며, 백슬래시 이스케이프 시퀀스를 해석하려면 %b를 사용하면 됩니다. 문자 시퀀스 \n은 출력이 항상 줄바꿈으로 끝나도록 보장합니다:
printf "%b\n" "Many females in Oble are \noblewomen"
# Many females in Oble are
# oblewomen
printf는 echo를 훨씬 능가하는 강력한 옵션들을 갖추고 있지만, 이 유틸리티 역시 만능은 아닙니다. 포맷 스트링 공격(uncontrolled format string attack)에 취약할 수 있으므로, 프로그래머는 사용자 입력을 반드시 신중하게 처리해야 합니다.
변수에 줄바꿈 담기
컴파일러 간 이식성을 높이기 위해 1983년 ANSI C 표준이 제정되었습니다. $'...' 형태의 ANSI-C 따옴표를 사용하면 이스케이프 시퀀스가 표준 규격에 따라 출력에서 치환됩니다.
이를 활용하면 줄바꿈이 포함된 문자열을 변수에 저장해 두었다가, 출력 시 줄바꿈이 해석된 상태로 인쇄할 수 있습니다. 변수를 설정한 뒤 $와 함께 printf로 호출하면 됩니다:
puns=$'\number\narrow\nether\nice'
printf "%b\n" "These words started with n but don't make $puns"
# These words started with n but don't make
# umber
# arrow
# ether
# ice
확장된 변수는 작은따옴표로 감싸져 있어 printf에 리터럴 그대로 전달됩니다. 늘 그렇듯이 입력값을 올바르게 처리하는 것이 중요합니다.
보너스: 셸 매개변수 확장
Bash와 중괄호를 다룬 이전 글에서 셸 매개변수 확장의 마법을 소개한 적이 있습니다. ${parameter@operator} 확장을 활용하면 이스케이프 시퀀스마저 해석할 수 있습니다. printf의 %s 지정자로 문자열로 출력하면, E 연산자가 변수 안의 이스케이프 시퀀스를 알아서 확장해 줍니다:
printf "%s\n" ${puns@E}
# umber
# arrow
# ether
# ice
인간의 언어로 말하기, 여전히 남은 숙제
문자열 보간(string interpolation)은 오늘날에도 프로그래머를 괴롭히는 까다로운 문제입니다. 언어와 셸이 특정 플레이스홀더의 의미에 대해 서로 합의하도록 만드는 것은 물론, 올바른 이스케이프 시퀀스를 정확히 사용하려면 세밀한 주의가 필요합니다.
잘못된 문자열 보간은 엉뚱하고 우스꽝스러운 출력을 낳을 뿐 아니라, 인젝션 공격 같은 보안 취약점으로 이어질 수도 있습니다. 터미널의 다음 진화가 우리를 이모지로 대화하게 만드는 날이 오기 전까지는, 사람을 위한 출력을 인쇄할 때 세심한 주의를 기울이는 것이 최선입니다.