파이썬 subprocess 명령어가 안 될 때, shell=True를 선택하는 기준

터미널에서 되던 명령이 Python의 subprocess에서 실패한다면 먼저 shell=True를 켜기보다 명령과 인자를 분리해 전달해야 합니다. 핵심 기준은 셸 문법이 필요한지이며, 일반적인 외부 프로그램 실행은 리스트 인자와 shell=False로 시작하는 편이 안전합니다.

먼저 결론: 명령은 리스트로 전달합니다

subprocess.run()의 args에는 명령 문자열이나 프로그램과 인자의 시퀀스를 넘길 수 있습니다. 자동화 코드에서는 실행 파일과 각 인자를 리스트의 별도 항목으로 적는 방식이 공백과 따옴표 문제를 줄이기 쉽습니다. Python 공식 문서도 일반적으로 시퀀스 형태를 권장합니다(subprocess 공식 문서).

import subprocess

result = subprocess.run(
    ["python", "script with spaces.py", "--mode", "daily report"],
    check=True,
    capture_output=True,
    text=True,
)

여기서 script with spaces.py와 daily report는 각각 하나의 인자입니다. 문자열 하나로 합쳐서 따옴표를 다시 계산하는 대신, Python 자료구조에서 인자 경계를 명확히 표현하는 것이 운영 중 수정에도 유리합니다.

문자열 명령을 받았다면 shlex.split()을 검토합니다

설정 파일이나 사용자가 입력한 명령이 이미 문자열로 들어오는 경우에는 shlex.split()으로 셸과 유사한 규칙에 따라 토큰을 나눌 수 있습니다. 인용된 공백은 한 인자로 유지되므로 단순히 command.split()을 호출하는 것보다 의도한 경계를 보존할 수 있습니다(shlex 공식 문서).

import shlex
import subprocess

command_text = 'python "script with spaces.py" --mode "daily report"'
args = shlex.split(command_text)

subprocess.run(args, check=True)

다만 shlex는 Unix 셸을 기준으로 설계된 도구입니다. Windows를 포함한 다른 운영체제의 셸 인용 규칙까지 동일하게 보장한다고 가정하면 안 됩니다. 실행 환경이 여러 운영체제로 나뉘거나 명령을 직접 설계할 수 있다면 처음부터 리스트 인자를 저장하는 편이 낫습니다.

shell=False와 shell=True를 나누는 기준

shell의 기본값은 False입니다. 외부 실행 파일에 인자를 전달하는 일반적인 경우에는 이 기본값을 유지하고 리스트 인자를 사용합니다(subprocess 공식 문서).

다음처럼 파이프, 리디렉션, 와일드카드, 환경 변수 확장처럼 셸이 해석하는 문법 자체가 필요할 때만 shell=True를 후보로 봅니다.

import subprocess

subprocess.run(
    "python report.py | sort > report.sorted.txt",
    shell=True,
    check=True,
)

하지만 셸 문법이 필요하다는 이유만으로 항상 shell=True가 최선은 아닙니다. 파이프 연결은 여러 Popen 프로세스나 Python 코드로, 파일 목록 처리는 glob·fnmatch로, 경로와 환경 변수 처리는 관련 Python 기능으로 바꿀 수 있는지 먼저 확인하는 편이 좋습니다. 셸을 제거하면 명령의 각 단계와 실패 지점을 코드에서 더 분명하게 다룰 수 있습니다.

외부 입력을 셸 명령에 붙이지 않습니다

shell=True를 사용하면서 파일명, 검색어, 요청값처럼 외부에서 들어온 문자열을 명령 문자열에 직접 이어 붙이면 셸 메타문자 해석으로 명령이 의도와 다르게 실행될 수 있습니다. 공식 문서도 shell=True일 때 메타문자와 공백을 안전하게 인용할 책임이 애플리케이션에 있으며, 부적절한 결합은 셸 인젝션 위험을 만들 수 있다고 설명합니다(subprocess 공식 문서).

따라서 선택 순서는 다음과 같이 잡는 것이 실무적입니다.

  1. 실행 파일과 인자를 리스트로 구성하고 shell=False로 실행합니다.
  2. 셸 문법이 필요한지 확인하고, 가능하면 Python 또는 subprocess 조합으로 대체합니다.
  3. 대체할 수 없어 shell=True가 필요하다면 외부 입력을 명령 문자열에 직접 결합하지 않고, 허용할 값과 인용 방식을 별도로 설계합니다.

오류가 날 때 확인할 항목

명령이 안 될 때는 다음 순서로 보면 원인을 빠르게 좁힐 수 있습니다.

  • 실행 파일, 옵션, 값이 리스트의 별도 항목으로 나뉘어 있는가?
  • 공백이 포함된 경로를 문자열 분할로 잘못 쪼개지 않았는가?
  • 파이프나 리디렉션 같은 셸 문법을 실제로 요구하는가?
  • 셸 문법이 필요하다면 Python 기능이나 여러 프로세스 연결로 대체할 수 있는가?
  • shell=True 명령에 외부 입력이 직접 들어가지는 않는가?
  • Unix용 shlex.split() 결과를 다른 운영체제 셸에도 그대로 적용하고 있지는 않은가?

이 기준으로도 실행 시간이 길어지는 문제까지 이어진다면, 이 글의 범위인 인자와 셸 선택과는 별개로 subprocess timeout 처리 순서와 프로세스 정리를 확인할 수 있습니다.

마무리

subprocess에서 명령어 문자열이 안 되는 문제는 대개 shell=True의 유무보다 인자 경계와 셸 문법의 필요성을 구분하지 않은 데서 시작합니다. 리스트 인자와 shell=False를 기본으로 두고, 문자열 명령은 입력 출처와 운영체제를 확인한 뒤 shlex.split()을 제한적으로 사용하십시오. 셸 기능이 정말 필요한 경우에만 shell=True를 선택하고 외부 입력 결합을 차단하는 것이 안전한 기준입니다.

Leave a Comment