.bat과 .exe, 그리고 Windows가 명령을 실행하는 원리
직접 만든 파이썬 CLI 도구를 .bat 파일로 포장해, 개발자가 아닌 사용자에게 전달했습니다. 그렇게 일을 마무리하고 나니 문득 궁금해졌습니다. 이 .bat이라는 것은 정확히 무엇이고, .exe와는 무엇이 다를까. 평소 당연하게 써오던 것들의 속을 처음으로 들여다보게 되었습니다.
발단 — .bat 한 줄을 만들고 나서
최근 작은 파이썬 CLI 도구를 하나 만들었습니다. 이 도구를 쓸 사람은 개발자가 아닌 Windows 사용자였기 때문에, 명령어를 직접 타이핑하지 않아도 되도록 run.bat 파일 하나를 함께 넣었습니다. 더블클릭만 하면 실행되도록 만든 얇은 래퍼였습니다.
전달까지 마치고 나서야, 정작 저는 .bat이 무엇인지 누군가에게 제대로 설명할 수 없다는 것을 깨달았습니다. 늘 ‘실행되는 파일’ 정도로 뭉뚱그려 알고 있었을 뿐입니다. 그래서 .bat과 .exe, 그리고 컴퓨터에서 ‘실행된다’고 부르는 파일들에는 어떤 종류가 있는지 차근차근 찾아보았습니다.
.bat과 .exe는 무엇이 다른가
같은 ‘실행되는 파일’처럼 보이지만 .bat과 .exe는 본질적으로 다른 존재입니다.
| 항목 | .bat (배치 파일) | .exe (실행 파일) |
|---|---|---|
| 정체 | 명령어를 적어둔 텍스트 파일 | 컴파일된 기계어 바이너리 |
| 메모장으로 열면 | 사람이 읽을 수 있는 글자 | 깨진 문자 (바이너리) |
| 실행 방식 | cmd.exe가 한 줄씩 해석 |
OS가 직접 실행 |
| 만드는 법 | 메모장으로 그냥 타이핑 | 컴파일러로 빌드 |
.bat 파일은 그 자체가 프로그램이 아닙니다. ‘이 명령들을 순서대로 실행하라’고 적어둔 메모에 가깝습니다. 실제로 제가 만든 run.bat을 메모장으로 열면, 파이썬이 설치됐는지 확인하고 프로그램을 실행하라는 명령어들이 글자 그대로 보입니다. 반면 .exe는 이미 기계어로 번역이 끝난 완제품이라, 메모장으로 열면 알아볼 수 없는 문자만 나옵니다.
.bat은 누가 실행하는가 — 인터프리터 이야기
여기서 한 가지 질문이 생깁니다. .bat이 그 자체로 프로그램이 아니라면, 그 안에 적힌 명령어들은 누가 읽고 실행하는 걸까요. 답은 cmd.exe, 즉 Windows의 명령 프롬프트입니다. .bat 파일을 더블클릭하면 Windows는 cmd.exe를 띄우고, 그 안의 텍스트를 한 줄씩 넘겨 해석하게 합니다.
이 구조는 파이썬도 똑같습니다. .py 파일 역시 그 자체로는 텍스트일 뿐이고, 이것을 읽어 실행하는 것은 python.exe라는 인터프리터입니다. .bat에는 cmd.exe가, .py에는 python.exe가 필요한 셈입니다. 오직 .exe만이 해석자 없이 OS가 직접 실행합니다.
그래서 제 run.bat은 사실 아주 얇은 껍데기였습니다. 도구의 진짜 로직은 전부 파이썬 코드 안에 있고, run.bat이 하는 일은 결국 다음 한 줄로 요약됩니다.
python -m mytool
레시피를 적은 종이가 run.bat이라면, 그 레시피를 실제로 수행할 요리사가 python.exe인 셈입니다. 사용자에게 파이썬 설치를 부탁해야 했던 이유가 바로 여기에 있었습니다. 레시피만 있고 요리사가 없으면 아무 일도 일어나지 않습니다.
실행되는 파일에는 어떤 종류가 있나
이렇게 보니 ‘실행된다’는 말 안에는 사실 두 부류가 섞여 있었습니다. 하나는 .exe처럼 그 자체로 실행되는 컴파일된 바이너리이고, 다른 하나는 .bat이나 .py처럼 누군가가 읽어 해석해줘야 하는 스크립트입니다. Windows에서 자주 만나는 것들을 정리하면 다음과 같습니다.
| 확장자 | 종류 | 실행·해석 주체 |
|---|---|---|
| .exe | 컴파일된 실행 파일 | OS가 직접 실행 |
| .com | 옛 형식의 실행 파일 | OS가 직접 실행 |
| .bat / .cmd | 배치 스크립트 | cmd.exe |
| .ps1 | PowerShell 스크립트 | powershell.exe |
| .vbs | VBScript 스크립트 | Windows Script Host |
| .py | Python 스크립트 | python.exe |
| .pyw | Python 스크립트 (콘솔 창 없음) | pythonw.exe |
컴파일된 쪽은 종류가 단출합니다. 반면 스크립트는 언어마다 전용 해석기가 따로 있어서 가짓수가 많습니다. 파이썬만 해도 일반 스크립트인 .py 외에, 콘솔 창 없이 도는 .pyw, 미리 컴파일해둔 바이트코드 .pyc, 코드를 하나로 묶은 .pyz까지 있습니다. .cmd는 .bat의 좀 더 현대적인 형제쯤 되어 거의 같은 역할을 합니다.
Windows는 이름만으로 어떻게 찾는가 — PATH와 PATHEXT
종류를 들여다보다 또 하나 궁금해진 것이 있었습니다. run.bat 안에는 python.exe가 어디에 있는지 경로가 적혀 있지 않습니다. 그냥 python이라고만 쓰여 있습니다. 확장자도, 폴더 경로도 없는 이 단어 하나를 Windows가 실제 파일로 연결하려면 두 가지 정보를 스스로 채워야 합니다.
첫 번째가 PATH입니다. python이라는 이름의 파일을 어느 폴더에서 찾을지 알려주는 폴더 목록입니다. 두 번째는 상대적으로 덜 알려진 PATHEXT입니다. 확장자 없이 이름만 입력했을 때 어떤 확장자를 붙여볼지 정해둔 목록입니다. Windows의 기본 PATHEXT는 대략 이렇습니다.
.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
앞서 표에서 본 확장자들이 거의 그대로 들어 있습니다. 흥미로운 점은, 파이썬을 설치하면 이 목록에 .PY가 더해진다는 것입니다. 덕분에 python뿐 아니라 직접 만든 스크립트도 확장자 없이 이름만으로 실행할 수 있게 됩니다. 정리하면 python이라는 한 단어가 실행되기까지, Windows는 PATH의 폴더들을 차례로 돌면서 PATHEXT의 확장자를 하나씩 붙여 python.exe라는 실제 파일을 찾아냅니다.
| 변수 | 역할 | 답하는 질문 |
|---|---|---|
| PATH | 실행 파일을 찾을 폴더 목록 | ‘어디서’ 찾을까 |
| PATHEXT | 확장자 생략 시 시도할 확장자 목록 | ‘무엇을’ 붙여볼까 |
평소 터미널에 명령어 한 단어를 칠 때마다, 사실은 이 두 목록이 맞물려 조용히 파일을 찾아주고 있었던 셈입니다.
돌이켜보며 — 당연하게 써오던 것의 속
처음에는 .bat 한 줄짜리 래퍼라고 가볍게 생각했습니다. 그런데 그 작은 파일 하나가 궁금증의 실마리가 되어, cmd.exe라는 해석자, 인터프리터의 존재, 컴파일된 바이너리와 스크립트의 구분, PATH와 PATHEXT의 분업까지 — 평소 당연하게 써오던 것들의 속을 비로소 들여다보게 되었습니다.
돌이켜 생각해보면, 무언가를 직접 만들어보는 일의 값어치는 결과물 그 자체에만 있지 않은 것 같습니다. 만들고 난 뒤에 남는 ‘이건 도대체 어떻게 동작하는 걸까’라는 작은 물음표 하나가, 그동안 무심코 지나쳐온 것들을 다시 보게 만듭니다. 다음에 또 무언가를 만들 때도, 그 끝에 따라오는 궁금증을 그냥 흘려보내지 말아야겠다는 생각이 들었습니다.
관련 글
실행 파일은 왜 타깃 OS에서 빌드해야 할까 — PyInstaller·Electron 패키징 원리
PyInstaller, Node SEA, Electron은 코드를 번역하는 컴파일러가 아니라 런타임을 감싸는 포장기입니다. 인터프리터·네이티브 확장·부트로더가 모두 OS에 묶여 있어, 실행 파일을 타깃 OS에서 빌드해야 하는 이유를 정리했습니다.
대화 세션에서 돌리던 slash skill을 배치 자동화로 옮긴 이야기
매일 같은 slash skill을 손으로 돌리던 습관을 LaunchAgent 배치로 옮기면서 느낀 것은 기술 장벽보다 역할 재정의가 본질이라는 점이었습니다. 집행자에서 큐레이터로의 전환에 대한 기록입니다.
맥에서 윈도우 PC SSH 자동화, PowerShell 함정 10가지 정리
맥북을 메인으로 쓰면서 윈도우 PC를 SSH로 자동화하다 보면, 공식 문서대로 따라가도 PowerShell 명령이 어딘가에서 자꾸 깨집니다. UAC 토큰 분리, ReadConsoleOutput, 4단 quote escape, cmd.exe와 PowerShell의 동작 차이 등 하루 동안 누적된 10가지 함정과 우회 패턴을 정리합니다.