원격 서버의 컨테이너 안에서 스크립트를 돌려야 했는데 따옴표가 계속 깨졌다. 명령이 로컬 zsh 와 ssh 와 docker exec 와 컨테이너 안 bash -c 를 거쳐 들어가는 구조였다.
Table of contents
Open Table of contents
원인 — 층마다 벗겨지는 따옴표
각 계층이 문자열을 자기 규칙으로 해석하면서 따옴표를 한 겹씩 벗기기 때문이다. 그래서 역슬래시를 몇 개나 붙여야 하는지를 명령마다 손으로 세어야 했다.
틀리면 문법 오류가 나는데 어느 계층에서 깨졌는지가 메시지에 안 나온다. 계층이 늘어날수록 한 번에 맞히는 일이 아니라 시행착오를 반복하는 일이 된다.
대안 — 파일로 보내는 방법의 대가
스크립트를 파일로 만들어 scp 로 원격에 복사하고 docker cp 로 컨테이너에 옮겨 실행하니 따옴표가 안 깨졌다. 대신 단계가 늘어나고 양쪽에 남은 임시 파일을 지우는 일까지 추가됐다.
한 번 돌리자고 파일을 만들고 옮기고 지우는 과정이 통째로 따라붙는다. 따옴표 문제는 해결되지만 실행 비용이 그만큼 늘어난 상태가 됐다.
해결 — 인코딩해서 넘기기
두 가지 방법이 있었는데 파일로 옮겨 돌리는 것과 스크립트 전체를 base64 로 인코딩해 한 덩어리로 넘기는 것이다. 뒤쪽을 골랐다. 이유는 인코딩된 문자열에는 따옴표도 공백도 줄바꿈도 없어 어느 계층을 지나도 셸이 해석할 문자가 없고 만들고 옮기고 지울 파일도 없어서다. 대신 명령줄만 봐서는 무엇을 돌리는지 안 보이는 것을 감수했다.
컨테이너 안에서는 echo "$b" | base64 -d | bash 로 풀어 돌린다. 결과가 한글을 담아 깨지는 것도 같은 방식으로 base64 로 받아 로컬에서 푸는 것으로 해결했다.
주의 — 확인과 길이 제한
넘기기 전에 인코딩한 문자열을 base64 -d 로 다시 풀어서 원본과 diff 로 같은지를 먼저 확인했다. GNU coreutils 매뉴얼대로 base64 는 기본 76자마다 줄을 바꾸므로 -w0 으로 없애야 했고 BSD 판은 -b0 이라 둘 다 되게 썼다.
execve(2) 매뉴얼이 적듯 명령줄 인자 전체 길이에 ARG_MAX 한도가 있고 인코딩하면 원본보다 길어지므로 그것도 감안했다. 상한을 여유 있게 잡고 넘으면 파일 방식으로 가게 했으며 무엇을 돌렸는지 알 수 있게 원본을 로그에 남겼다.
판단 기준 — 임시와 반복의 경계
그때그때 쓰기에 편한 방식이었지만 계속 쓸 만한 방식은 아니었다. 같은 것을 세 번 돌리고 나서 .sh 로 만들어 저장소에 넣고 Dockerfile 의 COPY 로 이미지에 포함시켜 뒀다.
한 번 돌리는 임시 실행에는 인코딩 전달이 맞고 반복되는 것은 형상 관리에 들어가야 무엇을 돌리는지가 코드로 남는다. 그 경계를 세 번째 실행에서 넘었다.
정리
- 여러 계층을 거치면 따옴표와 이스케이프가 깨진다
- 어느 계층에서 깨졌는지가 메시지에 안 나온다
- 파일로 보내면 안 깨지지만 단계와 정리 비용이 붙는다
base64로 넘기면 해석될 문자가 없어 안 깨진다- 넘기기 전에 다시 풀어 원본과 같은지 확인한다
ARG_MAX를 감안하고 넘으면 파일 방식으로 간다- 인코딩하면 무엇을 돌렸는지 안 보이므로 원본을 로그에 남긴다
- 반복되는 실행은 스크립트로 만들어 형상 관리에 넣는다