개발 취재 노트
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.exe프로세스가 하나도 없었다 - 콘솔
python.exe로 직접 돌리면 장치를 인식하고 "준비 완료"까지 정상 진행됐다 - bat/vbs 경로로는 25초가 지나도 프로세스가 안 뜨고 로그에 새 줄도 없었다 (뒤에서 보듯, 조사하던 셸이 자식 프로세스를 정리해서 생긴 가짜 실패였을 가능성이 크다 )
- 그래서
pythonw로 직접 띄워 보니error: unrecognized arguments: USB Audio가 나왔다
마지막 에러는 "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은 고칠 것이 없었다. 테스트 방법을 바꿨다.
- 가짜 단서를 의심한 순간 PowerShell로 다시 조립한 명령을 버리고, 실제 bat 파일을 그대로 실행했다
- PowerShell에서 공백 있는 인자를 넘겨야 할 때는 위
## 결론처럼 값을 따옴표로 한 번 더 감싸거나, 명령줄 전체를 문자열 하나로 넘기거나,&호출 연산자를 쓴다
전(따옴표가 사라짐):
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로 직접 돌려 보자. 거기서도 죽으면 코드 문제야.pythonw라는 실행 방식이야. 그 경로만 따로 떼서 봐야겠다.PowerShell에서 Start-Process로 pythonw에 장치 인자를 붙여 직접 띄워 본다.
error: unrecognized arguments: USB Audio KT USB Audio라 공백이 있잖아. 인자가 공백에서 쪼개져서, 명령줄 인자를 읽는 argparse가 KT만 장치 이름으로 받고 USB Audio는 모르는 인자라며 죽는 거지 cmd /c로 그대로 돌려 보자. 거기서도 같은 에러가 나면 bat 문제고, 안 나면 우리 테스트 명령 문제야.cmd /c로 돌렸는데 40초가 넘도록 명령이 안 끝나. 에러도 안 나오고 그냥 멈춰 있어 pythonw가 끝날 때까지 기다려. 안 끝난다는 건 pythonw가 살아서 돌고 있다는 뜻이야. 다른 창에서 확인해 줘 unrecognized arguments는 우리 Start-Process 명령의 인용 버그였고, 진짜 원인 후보에서 빠져 Start-Process -ArgumentList는 인자 배열을 공백으로 이어 붙일 뿐이고, 공백 든 값에 따옴표를 알아서 붙여 주지 않아. 그러니 값을 '"KT USB Audio"'처럼 따옴표로 한 번 더 감싸거나, & 호출 연산자로 실행해야 해.pythonw 경로에서만 안 뜨는 이유로 딱이었거든.