개발 취재 노트

PowerShell Start-Process로 파이썬을 띄우면 unrecognized arguments, bat으로 돌리면 멀쩡할 때

윈도우에서 늘 대기하다가 호출어를 들으면 말을 받아 적고 답을 읽어 주는 로컬 음성 비서(voice-assistant)를 만들고 있었다. 부팅 뒤 비서가 안 떠 있어서 원인을 찾으려고 PowerShell로 pythonw를 직접 띄워 봤다. 그랬더니 error: unrecognized arguments: USB Audio가 나왔다. 그런데 평소 쓰던 bat 파일로 띄우면 같은 인자로도 멀쩡히 떴다.

결론

--device "KT USB Audio"처럼 공백이 들어간 인자를 Start-Process -ArgumentList로 넘기면 따옴표가 사라질 수 있다. 그러면 파이썬 쪽에는 --device KT USB Audio라는 명령줄이 전달된다. argparse는 KT만 --device 값으로 받고, 남은 USB Audio는 모르는 인자라며 종료한다 . 프로그램이나 bat의 버그가 아니고, 테스트하려고 친 명령의 인용 버그다 .

가려내는 법과 고치는 법:

# 1) 가짜 단서인지 가리기 — 실제로 쓰는 실행 경로(bat)를 그대로 돌려 본다
cmd /c path\to\start_assistant.bat

# 2) Start-Process를 꼭 써야 한다면: 공백이 든 값을 따옴표로 한 번 더 감싸 넘긴다
Start-Process pythonw -ArgumentList 'main.py', '--device', '"KT USB Audio"'
#   또는 명령줄 전체를 문자열 하나로 넘긴다
Start-Process pythonw -ArgumentList 'main.py --device "KT USB Audio"'

# 3) 창을 새로 띄우지 않아도 된다면: 호출 연산자(&)를 쓴다. 인자마다 알아서 따옴표를 붙여 준다
& .\venv\Scripts\python.exe main.py --device "KT USB Audio"

(bat 경로와 main.py는 예시다. 세션 출력에는 테스트 명령 원문이 남아 있지 않다 .)

상황

이 음성 비서는 venv의 pythonw.exe로 콘솔 창 없이 상주한다 . 자동 시작 경로는 시작 프로그램 폴더의 VoiceAssistant.lnk → start_assistant_hidden.vbs → bat → pythonw 순서다 . 마이크 장치 이름은 명령줄 인자 --device "KT USB Audio"로 넘긴다 . 장치 이름에 공백이 두 개 들어 있다는 점이 이 사건의 핵심이다.

조사는 PowerShell 셸에서 명령을 하나씩 실행하면서 진행했다 .

증상

마지막 에러는 "pythonw 경로에서만 죽는 이유"로 딱 맞아 보였다. 처음에는 이것을 "결정적 단서"로 받아들였다 .

원인

Start-Process는 -ArgumentList로 받은 배열 원소를 공백으로 이어 붙여 명령줄 문자열 하나를 만든다. 이때 공백이 든 원소에 따옴표를 자동으로 붙여 주지 않는다. 이 동작은 PowerShell 공식 문서 저장소 이슈(MicrosoftDocs/PowerShell-Docs#7701)와 PowerShell 이슈(PowerShell/PowerShell#5576)에서 오래전부터 지적돼 왔다. 윈도우 프로세스는 명령줄을 문자열 하나로 받고, 파이썬이 그 문자열을 공백과 따옴표 기준으로 다시 쪼개 sys.argv를 만든다. 따옴표가 사라지면 KT, USB, Audio가 각각 별개 인자가 된다.

반면 bat 안에는 --device "KT USB Audio"가 따옴표째 적혀 있어서 cmd가 그대로 전달한다. 실제 bat을 cmd /c로 돌리자 명령이 40초 넘게 끝나지 않았다. cmd는 bat이 띄운 pythonw가 끝날 때까지 기다리므로, 이렇게 블록됐다는 것은 pythonw가 죽지 않고 살아 있다는 뜻이었다 . 별도 셸에서 확인하니 bat 경로로 "준비 완료. Hey Jarvis"까지 정상 기동해 있었다 .

즉 argparse 에러는 재현하려고 새로 만든 실행 경로가 원래 경로와 달라서 생긴 새 버그였다 .

해결

프로그램이나 bat은 고칠 것이 없었다. 테스트 방법을 바꿨다.

전(따옴표가 사라짐):

Start-Process pythonw -ArgumentList 'main.py', '--device', 'KT USB Audio'
# 파이썬이 받는 명령줄: main.py --device KT USB Audio
# → error: unrecognized arguments: USB Audio

후:

Start-Process pythonw -ArgumentList 'main.py', '--device', '"KT USB Audio"'
# 파이썬이 받는 명령줄: main.py --device "KT USB Audio"

(두 예시는 문서화된 동작으로 재구성한 것이다. 이 세션에서 실제로 친 명령 원문은 아니다.)

덤 — 이 인용 버그를 걷어 내도 부팅 문제는 남았다

인용 버그를 걷어 내자 코드·bat·장치 인식은 모두 정상으로 확인됐다 . 하지만 "부팅 때 왜 안 떴나"는 이 세션에서 끝내 확정되지 않았다. 로그 마지막 기록이 8월 23일이고 당일 부팅 뒤에는 한 줄도 없어서, 이번 부팅에서 자동 시작이 아예 실행되지 않았다는 관찰까지만 얻었다 . 또 조사하던 셸이 자기가 띄운 자식 프로세스를 호출이 끝날 때 함께 정리해서, "떴다가 죽는다"처럼 보이는 가짜 실패가 한 번 더 나왔다 . 교훈은 같다. 디버깅용 재현 경로가 실제 실행 경로와 다르면 그 차이가 새 증상을 만든다.

취재 후기 — 에러 메시지가 너무 그럴듯했다

박도은
윈도우에서 호출어를 들으면 대답해 주는 음성 비서 있잖아. 재부팅했더니 백그라운드에 안 떠 있어. 시작 프로그램 폴더에 바로가기는 그대로 있는데 pythonw 프로세스가 하나도 없어
정바다
코드가 뜨자마자 죽는 거 아니야? 바로가기가 멀쩡한데 프로세스가 없으면 떴다가 바로 죽었다고 보는 게 제일 자연스럽잖아.
정하늘
그럼 창 없는 pythonw 말고, 에러가 화면에 보이는 콘솔 python.exe로 직접 돌려 보자. 거기서도 죽으면 코드 문제야.
박도은
콘솔로 돌리니까 마이크 장치도 잡고 "준비 완료"까지 멀쩡하게 가. 그런데 평소처럼 bat이랑 vbs 경로로 띄우면 25초가 지나도 안 떠
정하늘
코드는 아니네. 남은 차이는 pythonw라는 실행 방식이야. 그 경로만 따로 떼서 봐야겠다.

PowerShell에서 Start-Process로 pythonw에 장치 인자를 붙여 직접 띄워 본다.

박도은
떴다! 아니, 에러가 떴어. error: unrecognized arguments: USB Audio
정바다
이거다. 장치 이름이 KT USB Audio라 공백이 있잖아. 인자가 공백에서 쪼개져서, 명령줄 인자를 읽는 argparse가 KT만 장치 이름으로 받고 USB Audio는 모르는 인자라며 죽는 거지
박도현
설명은 맞아. 그런데 쪼갠 게 누구야? bat 안에는 따옴표가 제대로 들어가 있을 텐데. 우리가 방금 PowerShell로 새로 조립한 명령이 쪼갰을 수도 있어.
정하늘
그건 금방 가를 수 있어. PowerShell로 다시 조립하지 말고 실제 bat 파일을 cmd /c로 그대로 돌려 보자. 거기서도 같은 에러가 나면 bat 문제고, 안 나면 우리 테스트 명령 문제야.
박도은
cmd /c로 돌렸는데 40초가 넘도록 명령이 안 끝나. 에러도 안 나오고 그냥 멈춰 있어
정바다
멈췄으면 거기서 또 뭔가 걸린 거 아니야?
정하늘
반대야. cmd는 bat이 띄운 pythonw가 끝날 때까지 기다려. 안 끝난다는 건 pythonw가 살아서 돌고 있다는 뜻이야. 다른 창에서 확인해 줘
박도은
확인했어. bat 경로로 "준비 완료. Hey Jarvis"까지 떠서 호출어를 기다리고 있어
정하늘
판정 나왔다. bat은 인자를 따옴표째 잘 넘기고 있었어. unrecognized arguments는 우리 Start-Process 명령의 인용 버그였고, 진짜 원인 후보에서 빠져
박도현
Start-Process -ArgumentList는 인자 배열을 공백으로 이어 붙일 뿐이고, 공백 든 값에 따옴표를 알아서 붙여 주지 않아. 그러니 값을 '"KT USB Audio"'처럼 따옴표로 한 번 더 감싸거나, & 호출 연산자로 실행해야 해.
정바다
에러 메시지가 증상이랑 너무 잘 맞아서 그대로 믿었네. pythonw 경로에서만 안 뜨는 이유로 딱이었거든.
박도현
그럴듯한 단서일수록 그 단서가 어느 경로에서 나왔는지부터 봐야 해. 이번 에러는 원래 실행 경로가 아니라 우리가 재현하려고 새로 만든 경로에서 나왔어.
박도은
그럼 처음에 bat이랑 vbs로 띄웠을 때 25초 동안 안 떴던 건 뭐였어? 그때도 bat을 썼잖아.
정하늘
그것도 우리 쪽 문제였을 가능성이 커. 조사하던 셸이 호출이 끝날 때 자기가 띄운 자식 프로세스를 같이 정리했거든. 셸 밖으로 띄우니 30초 내내 살아 있었어
박도현
결국 가짜 실패가 두 겹이었네. 그래서 진짜 질문인 부팅 때 안 뜬 이유는 풀렸어?
박도은
아니. 인용 문제를 걷어 낸 뒤에도 부팅 때 안 뜬 이유는 끝내 못 잡았어. 이번 부팅 뒤로는 로그가 한 줄도 없어서, 자동 시작이 아예 실행되지 않았다는 데까지만 봤어
정하늘
그래도 코드, bat, 장치 인식이 정상이라는 건 확정했어. 적어도 가짜 단서를 좇아 멀쩡한 bat을 뜯어고치는 일은 막았지
박도현
디버깅하려고 만든 재현 명령이 원래 실행 경로와 한 글자라도 다르면, 그 차이가 새 버그를 만들어. 이상한 에러가 나오면 원래 경로를 그대로 돌려 보는 게 먼저야.