Python 서브프로세스 모듈 완전 해설 | 기본부터 응용까지

1. Python의 subprocess 모듈이란

개요

Python의 subprocess 모듈은 시스템 명령어나 외부 프로그램을 Python에서 실행하기 위한 강력한 도구입니다. 이 모듈을 사용하면 표준 입출력 및 프로세스 관리가 가능해져 Python 프로그램과 외부 프로그램의 연동을 쉽게 할 수 있습니다. 기존의 os.system()이나 commands 모듈을 대체하는 방법으로, 보다 안전하고 유연한 프로세스 제어를 제공합니다.

주요 용도

  • 셸 명령 실행: 간단한 시스템 명령 호출。
  • 프로세스 관리: 외부 프로그램 실행 및 표준 입출력 리다이렉션。
  • 비동기 처리: 장시간 걸리는 작업이나 병렬로 실행되는 작업 관리。

2. 기본적인 사용법:subprocess.run()

기본 사용법

subprocess.run()은 시스템 명령을 Python 내부에서 간단히 실행하기 위한 함수입니다. 예를 들어, 디렉터리 내 파일을 목록화하려면 다음과 같은 코드를 사용합니다。
import subprocess

result = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(result.stdout)
이 코드는 ls -l 명령을 실행하고, 그 출력을 stdout에 저장하여 Python에서 처리합니다. capture_output=True를 통해 표준 출력을 캡처하고, text=True로 결과를 문자열로 다룰 수 있습니다。

에러 핸들링

subprocess.run()을 사용하면, 명령이 실패했을 때 stderr를 이용해 오류 메시지를 얻을 수 있습니다. 또한 returncode로 실행 성공 여부를 확인할 수 있습니다。
result = subprocess.run(['ls', 'nonexistentfile'], capture_output=True, text=True)
if result.returncode != 0:
    print(f"오류: {result.stderr}")
이 예에서는 존재하지 않는 파일을 지정하면 표준 오류에 오류 메시지가 표시됩니다。
年収訴求

3. 비동기 실행: subprocess.Popen()

Popen을 사용한 비동기 처리

subprocess.run()은 동기 처리이므로, 명령이 종료될 때까지 Python 프로그램은 다음 처리로 진행할 수 없지만, subprocess.Popen()을 사용하면 비동기로 프로세스를 실행할 수 있어 다른 작업을 동시에 수행할 수 있습니다。
import subprocess

proc = subprocess.Popen(['sleep', '5'], stdout=subprocess.PIPE)
print("프로세스가 시작되었습니다")
proc.wait()
print("프로세스가 종료되었습니다")
이 코드에서는 sleep 5가 비동기로 실행되어 그 동안 다른 작업을 진행할 수 있습니다。

표준 입출력 제어

Popen에서는 표준 입출력 리다이렉트를 세밀하게 제어할 수 있습니다. 예를 들어, 다음 코드는 파일에서 데이터를 읽어 cat 명령으로 처리하고, 다른 파일에 기록하는 예시입니다。
with open('input.txt', 'r') as infile, open('output.txt', 'w') as outfile:
    proc = subprocess.Popen(['cat'], stdin=infile, stdout=outfile)
    proc.wait()
이를 통해 외부 명령의 표준 입력 및 출력을 파일에 리다이렉트하여 처리할 수 있습니다。

4. 사용 예시: 자동화 스크립트

파일 백업

시스템 관리 및 정기적인 작업 자동화에subprocess는 매우 편리합니다. 예를 들어, 다음 예에서는 파일을 백업 디렉터리로 자동으로 복사합니다。
import subprocess

files_to_backup = ['file1.txt', 'file2.txt', 'file3.txt']
backup_dir = '/backup/directory/'

for file in files_to_backup:
    subprocess.run(['cp', file, backup_dir])
이 코드는 지정된 파일을 백업 폴더에 복사합니다. 이렇게 간단한 스크립트를 작성함으로써 정기적인 백업 작업을 자동화할 수 있습니다。

CI/CD 파이프라인에서의 활용

subprocess는 지속적인 통합(CI) 및 지속적인 배포(CD) 환경에서도 사용되며, 테스트 스크립트의 자동 실행이나 배포 처리의 일부로 통합됩니다. 예를 들어, 테스트 스크립트를 실행하고 성공하면 다음 단계로 진행하는 식의 자동화가 가능합니다。
侍エンジニア塾

5. 보안 및 베스트 프랙티스

shell=True의 위험

shell=True 옵션은 쉘을 통해 명령을 실행할 때 사용하지만 보안상의 위험이 따릅니다. 특히 외부 입력을 그대로 전달하는 경우 쉘 인젝션 공격 위험이 있습니다. shell=False를 사용하면 이 위험을 완화할 수 있습니다。
import subprocess

# 권장되는 사용법(안전)
subprocess.run(['ls', '-l'])

# shell=True(주의 필요)
subprocess.run('ls -l', shell=True)

크로스 플랫폼 지원

시스템 명령은 서로 다른 OS 환경에서 다른 명령을 사용할 수 있습니다. 다음과 같이 Python의 platform 모듈을 사용하여 실행할 명령을 OS에 따라 전환할 수 있습니다。
import platform
import subprocess

if platform.system() == "Windows":
    subprocess.run(['dir'], shell=True)
else:
    subprocess.run(['ls', '-l'])

6. 트러블슈팅 및 디버깅

일반적인 오류와 대책

subprocess를 사용할 때, 파일을 찾을 수 없거나 접근 권한이 없는 등의 오류가 자주 발생합니다. 이러한 오류는 stderr를 사용해 캡처하고, returncode를 확인함으로써 오류 상세 정보를 얻을 수 있습니다。

디버깅을 위한 팁

check=True 옵션을 사용하면, 명령이 실패했을 때 예외를 발생시켜 문제를 조기에 감지할 수 있습니다. 또한, 표준 출력과 오류 메시지를 캡처하여 로그에 남김으로써 디버깅이 쉬워집니다。
import subprocess

try:
    result = subprocess.run(['ls', '-l'], check=True, capture_output=True, text=True)
    print(result.stdout)
except subprocess.CalledProcessError as e:
    print(f"오류가 발생했습니다: {e}")

7. 비동기 처리와 asyncio와의 연동

asyncio를 사용한 비동기 처리

asyncio를 사용하면 subprocess와 연동하여 여러 프로세스를 병렬로 비동기 처리할 수 있습니다. 다음 예시는 asyncio를 사용해 ls 명령을 비동기적으로 실행하고 결과를 캡처합니다。
import asyncio
import subprocess

async def run_command():
    proc = await asyncio.create_subprocess_exec('ls', '-l',
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE)

    stdout, stderr = await proc.communicate()

    if stdout:
        print(f'[stdout]n{stdout.decode()}')
    if stderr:
        print(f'[stderr]n{stderr.decode()}')

asyncio.run(run_command())
이 코드는 비동기로 명령을 실행하고 표준 출력 및 표준 오류의 결과를 처리합니다. asyncio를 사용하면 비동기 태스크를 효율적으로 관리할 수 있습니다.