ssh 너머로 넘긴 정규식이나 awk, heredoc 패치가 오류 없이 "0건", "적게", 엉뚱한 숫자를 냅니다. 로컬 셸, 원격 셸, 파이썬 문자열이 저마다 역슬래시와 따옴표를 해석해서, 친 것과 다른 글자가 원격에 도착합니다. 스크립트를 로컬 파일로 써서 올린 뒤 원격에서 실행하고, 원격에서는 날것의 값만 받아 계산은 이쪽에서 합니다.
ssh 한 줄에 파이썬이나 awk 를 끼워 넣으면 편합니다. 하지만 따옴표와 역슬래시가 많아지는 순간부터 결과를 믿을 수 없게 됩니다. 이 문제가 무서운 이유는 오류가 나지 않는다는 점입니다.
네 번 모두 오류 없이 틀렸다
| 상황 | 보인 결과 | 실제 |
|---|---|---|
| 따옴표 heredoc 으로 원격 파이썬 패처 작성 | unterminated f-string, 앵커 0번 일치 |
\\n 이 \n 으로 도착(2회 발생) |
| ssh 안 python -c 안의 PHP 정규식으로 개수 세기 | 5개 | 같은 검사를 파일로 올려 다시 세니, 대상의 절반만 훑었는데도 이미 8개 |
| ssh 안에서 awk 로 메모리 총량 읽기 | 그럴듯한 숫자 | PID 같은 엉뚱한 값, "메모리 0%" 지표가 될 뻔함 |
로컬 치환 스크립트의 삼중 따옴표 안 "\\n" |
깨진 파일 생성 | 진짜 줄바꿈으로 들어감 |
첫 번째는 원격 파일에 패치 코드를 쓰려던 경우입니다. 내가 \\n 으로 친 것이 원격 파일에는 \n 으로 들어가, 파이썬 문자열 안에서 실제 줄바꿈이 되며 f-string 이 깨졌습니다. 고칠 자리를 찾는 앵커도 src.count(old)==0 으로 하나도 맞지 않았습니다.
두 번째와 세 번째는 더 나빴습니다. 숫자가 나오긴 나왔고 그럴듯했기 때문에 그대로 사실로 받아들일 뻔했습니다.
셸을 지날 때마다 한 겹씩 벗겨진다
ssh 로 넘긴 명령은 로컬 셸이 한 번 해석하고, 원격 셸이 또 한 번 해석합니다. 그 안에 python -c 가 있으면 파이썬 문자열 규칙이 한 번 더 붙습니다. 겹마다 역슬래시와 따옴표가 소비되므로, [^\"]* 같은 문자 클래스는 원격에 도착할 때쯤 다른 패턴이 되어 아무것도 맞추지 못합니다.
awk 의 $2 는 셸이 두 번 지나가는 곳에서 가장 흔하게 당합니다. 제 경우는 로컬 셸이 풀지 못하게 \$2 로 막아 두었는데, 원격에 도착한 명령이 다시 큰따옴표 안에 들어 있어서 이번에는 원격 셸이 $2 를 풀어 버렸습니다. 막는 겹이 하나 모자라면 무슨 일이 나는지는 셸 한 겹으로도 볼 수 있어서, 이 글을 쓰며 로컬에서 다시 돌려 봤습니다.
$ bash -c "awk '{print $2}' meminfo.txt"
MemTotal: 16314284 kB
MemFree: 1234567 kB
$ bash -c "awk '{print \$2}' meminfo.txt"
16314284
1234567
첫 줄은 숫자만 뽑으려던 명령인데, $2 가 빈 글자로 풀려 awk '{print }' 가 되는 바람에 줄 전체가 나왔습니다. 오류는 없습니다. 이 출력을 다시 다른 명령에 넘기면 엉뚱한 칸을 숫자로 읽게 되고, 제 경우에는 그렇게 읽은 엉뚱한 값이 "메모리 0%" 지표로 쓰일 뻔했습니다.
첫 번째 경우인 heredoc 은 따로 봐야 합니다. bash 규칙대로라면 따옴표를 친 heredoc(<<'EOF')은 역슬래시를 건드리지 않고 그대로 넘기고, ssh 도 표준 입력을 바이트 그대로 보냅니다. 그런데도 두 번 모두 한 단계 깎여 도착했습니다. 그때 명령은 제가 터미널에 직접 친 것이 아니라 다른 도구를 거쳐 실행된 것이라, 어느 단계가 깎았는지는 가르지 못했습니다. 그래서 이 경우는 셸 겹침의 예로 세지 않고, 원인을 모르는 관측으로만 남겨 둡니다. 어느 쪽이든 처방은 아래의 "파일로 올리기" 하나로 같습니다.
스크립트는 파일로 올리고, 계산은 이쪽에서
1. 정규식이 든 조사와 패치는 파일로
# 로컬에서 scan.py 를 편집기로 그대로 저장한 뒤
ssh [email protected] "cat > /tmp/scan.py" < ./scan.py
ssh [email protected] "python3 /tmp/scan.py"
편집기로 저장한 파일은 바이트 그대로 올라가므로 이스케이프가 깎일 일이 없습니다. 셸 한 줄로 끝내도 되는 것은 정규식 없는 단순 조회뿐이라고 정했습니다.
2. ssh 너머로는 awk, sed 를 쓰지 않는다
ssh [email protected] "cat /proc/meminfo" > meminfo.txt
python3 -c "import re;t=open('meminfo.txt').read();print(re.search(r'MemTotal:\s+(\d+)',t).group(1))"
원격에서는 /proc 이나 df 결과를 가공 없이 받아 오고, 숫자 계산은 로컬에서 합니다.
3. 파이썬 안에서 줄바꿈 글자가 필요하면
둘은 용도가 반대입니다. 코드 안에 역슬래시와 n 두 글자를 그대로 남겨야 할 때는 원시 문자열(r"\n")을 쓰고, 실제 줄바꿈 한 글자가 필요할 때는 chr(10) 을 씁니다. 네 번째 경우는 두 글자를 남기려던 자리에 줄바꿈 한 글자가 들어가서 생겼습니다. 그리고 고친 결과를 먼저 컴파일해 본 뒤에만 다음 단계로 넘어갑니다.
python3 -m py_compile patched.py || exit 1
확인 방법
- 값이 0 이거나 이상하면
bash -x로 돌려 변수에 무엇이 들어왔는지부터 봅니다. - 결과가 예상보다 적으면 도구를 먼저 의심하고, 다른 방법으로 한 번 더 셉니다. 5개 대 8개 차이도 이렇게 찾았습니다.
- 패치 뒤에는 앵커가 몇 번 맞았는지 숫자로 찍어 0 이면 멈추게 합니다.
테스트한 환경
| 항목 | 내용 |
|---|---|
| 로컬 | 윈도우 PC 의 Git Bash |
| 원격 | 리눅스 서버, python3 |
| 넘긴 것 | 파이썬 패처, PHP 정규식, awk |
| awk 재현 | 2026년 10월 5일, Git Bash |
같이 읽을 글: while read 루프가 첫 줄만 처리하고 끝날 때, 원격 서버 장시간 작업을 띄우고 끝났는지 확인하는 법
이 글의 내용은 2026년 10월 5일에 마지막으로 확인했습니다. 틀린 곳을 발견하시면 연락처로 알려 주세요.