- Go 파일을 실행 파일처럼 직접 실행할 수 있는 트릭 소개
- 첫 줄에
//usr/local/go/bin/go run "$0" "$@"; exit를 두고 실행 권한을 주면./script.go로 실행 가능 - 이 방식은 shebang이 아니라 POSIX에서 ENOEXEC 발생 시 셸이 /bin/sh로 폴백하는 동작을 이용함
- 셸은 첫 줄을 명령으로 실행하고, Go 컴파일러는
//주석으로 인식해 무시함 -
"$0"로 자기 자신 경로를 넘겨go run이 스크립트를 빌드·실행하고$@로 인자 전달 - Go의 강력한 표준 라이브러리와 하위 호환성 보장이 스크립팅 용도에 적합하며, Go 1.x 버전을 사용하는 한 스크립트가 수십 년간 동작 가능
- Python의 가상 환경, pip/poetry/uv 등 의존성 관리 복잡성을 피할 수 있음
가짜 shebang의 작동 원리
-
shebang(
#!)은 execve 시스템 콜을 통해 인터프리터를 지정하는 방식이지만, 이 글에서 소개하는 기법은 shebang이 아님 - Go 소스 파일 첫 줄에
//usr/local/go/bin/go run "$0" "$@"; exit를 두고package main이하에 일반 Go 코드를 두는 형태-
chmod +x script.go로 실행 권한을 주면./script.go처럼 실행 가능
-
-
strace로 확인해보면, 셸이./script.go를 execve로 실행 시도할 때 커널이 ENOEXEC(Exec format error)를 반환- ENOEXEC를 받은 셸은 fallback으로
/bin/sh를 사용해 해당 파일을 셸 스크립트로 해석 - 셸에서
//는 주석이 아니라 루트 경로(/)로 해석되므로//usr/local/go/bin/go는 정상적인 경로로 실행됨
- ENOEXEC를 받은 셸은 fallback으로
- 따라서 첫 줄
//usr/local/go/bin/go run "$0" "$@"; exit가 셸에서 명령으로 실행됨-
"$0"는 실행된 파일 경로를 전달하니까, 실행 명령에서"$0"가script.go경로가 되어go run이 자기 자신을 찾아 빌드·실행하는 형태가 됨 -
"$@"는 1번 인자부터의 위치 인자 확장으로./script.go -f flag0 here are some args같은 호출을 가능하게 함 -
; exit가 없으면sh가 Go 파일을 계속 줄 단위로 해석하다가package같은 토큰에서 오류가 남
-
Go가 스크립팅에 적합한 이유
- Go의 하위 호환성 보장이 핵심 기능으로, Go 1.x를 사용하는 한 작성한 스크립트가 장기간 동작함
-
잘 발달된 표준 라이브러리와 내장 도구(포맷터, 린터 등)가 별도 설정 없이 제공되어, 스크립트 공유와 이식성이 극대화됨
- Python처럼 가상 환경이나 다양한 패키지 매니저(pip, poetry, uv)를 배울 필요 없이 코드 실행 가능
- Go 생태계의 내장 도구와 IDE 연동으로
.pyproject나package.json없이도 포맷터·린터를 기본적으로 쓸 수 있음
- 최신 Go만 설치되어 있으면 어떤 OS에서든 수십 년간 실행 가능
다른 컴파일 언어와의 비교
- Rust는 컴파일 속도가 느리고 표준 라이브러리가 약해 의존성 사용이 필수적이며, 완벽함을 요구해 개발 속도가 느림
- Java 및 JVM 언어는 이미 JVM 바이트코드 기반 스크립팅 언어가 존재하며, 경량 Kotlin 스크립팅도 대안이 될 수 있음
- Go는 컴파일 언어 중 스크립팅 용도에 가장 적합한 특성을 보유
gopls 포맷팅 문제와 해결책
-
gopls는 주석 뒤에 공백을 요구(//example→// example)하므로 가짜 shebang 라인이 깨짐 - 공백이 들어가면
// usr/local/go/bin/go가 되어 셸에서 경로로 인식되지 않음 -
해결책: HN 스레드의 제안으로
//대신/**/블록 주석 사용-
/*usr/local/go/bin/go run "$0" "$@"; exit; */형태로 작성 -
exit뒤에 세미콜론(;)이 필수
-