Python 대신 Go로 스크립팅하기

1 month ago 11

  • 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는 정상적인 경로로 실행됨
  • 따라서 첫 줄 //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 뒤에 세미콜론(;)이 필수

Read Entire Article