소개
운영 작업에서는 Python 으로 작업하더라도 기존 시스템 명령을 호출해야 하는 경우가 많습니다. 따라서 Python 의 프로세스 실행 도구를 이해하는 것이 중요합니다.
subprocess 모듈을 사용하면 Python 에서 다른 프로그램을 실행하고, 출력을 캡처하며, 성공 또는 실패 여부를 안전하게 확인할 수 있습니다. Python 코드와 셸 명령을 연결하는 표준 방법입니다.
이 실습에서는 다음 방법을 배웁니다.
subprocess모듈을 가져옵니다.- Python 을 사용해 간단한 Linux 명령 (
ls -l) 을 실행합니다. - 추가 처리를 위해 명령 출력을 캡처합니다.
- 출력을 바이트에서 읽을 수 있는 문자열로 디코딩합니다.
- 명령이 실패했을 때 오류를 적절하게 처리합니다.
Linux 환경의 ~/project 디렉터리에서 작업합니다.
Subprocess 가져오기
이 단계에서는 Python 스크립트를 만들고 시스템 셸과 상호 작용하는 데 필요한 모듈을 가져옵니다.
subprocess 모듈은 Python 표준 라이브러리에 포함되어 있으므로 별도로 설치할 필요가 없습니다. 이 모듈을 사용하면 Python 스크립트가 기본 운영 체제와 통신할 수 있습니다.
이는 Python 코드와 기존 Unix 도구를 연결하는 표준 방법입니다. 운영 작업에서는 모든 셸 명령을 한 번에 Python 으로 대체하는 경우가 드뭅니다. 일반적으로 Python 이 시스템 도구를 호출하고 결과를 캡처한 다음 그 위에 더 높은 수준의 로직을 추가하는 조정자 역할을 합니다.
먼저 올바른 디렉터리에 있는지 확인합니다.
cd ~/project
이제 nano 편집기나 원하는 방법을 사용해 list_files.py라는 새 Python 파일을 만듭니다.
nano list_files.py
모듈을 가져오도록 파일 맨 위에 다음 내용을 추가합니다.
import subprocess
print("Module imported successfully")
파일을 저장하고 편집기를 종료합니다 (nano에서는 Ctrl+X, Y, Enter를 차례로 누릅니다).
이제 스크립트를 실행해 모든 것이 올바르게 작동하는지 확인합니다.
python3 list_files.py
Module imported successfully라는 메시지가 표시되어야 합니다.
명령 실행
이 단계에서는 스크립트를 수정해 실제로 Linux 명령을 실행합니다. 서브프로세스를 호출할 때 권장되는 방법인 subprocess.run() 함수를 사용합니다. 이 함수는 Python 3.5 에서 도입되었습니다.
Python 이 다른 프로그램을 실행하면 별도의 프로세스가 생성됩니다. 이 자식 프로세스에는 셸에서 직접 실행하는 명령과 마찬가지로 자체 종료 코드, 표준 출력, 표준 오류가 있습니다. 이 프로세스 경계를 이해하면 이후의 오류 처리를 훨씬 쉽게 파악할 수 있습니다.
다시 list_files.py를 엽니다.
nano list_files.py
파일의 내용을 다음과 같이 수정합니다. 현재 디렉터리의 파일을 자세히 표시하기 위해 ls -l 명령을 사용합니다.
import subprocess
## Run the 'ls -l' command
## The command and its arguments are passed as a list of strings
subprocess.run(["ls", "-l"])
명령을 리스트로 전달한다는 점에 주의합니다: ["ls", "-l"]. 하나의 문자열로 전달하는 것보다 안전하고 명확합니다. 셸 인젝션과 파일 이름의 공백으로 인해 발생할 수 있는 문제를 피할 수 있기 때문입니다.
파일을 저장하고 종료합니다.
이제 스크립트를 실행합니다.
python3 list_files.py
ls -l 명령의 출력이 터미널에 직접 표시되어야 합니다. 기본적으로 subprocess.run이 명령의 출력을 표준 출력, 즉 화면으로 보내기 때문입니다.
출력 캡처
이전 단계에서는 출력이 화면에 직접 표시되었습니다. 하지만 자동화 스크립트에서는 일반적으로 Python 코드가 출력을 처리할 수 있도록 변수에 저장합니다. 예를 들어 파일 이름을 분석하거나 오류를 확인할 수 있습니다.
명령을 단순히 실행하는 것과 프로그램에 통합하는 것의 차이가 바로 여기에 있습니다. 출력을 데이터로 캡처하면 결과를 사람이 터미널에서 읽기만 하는 대신 스크립트가 결과를 바탕으로 판단할 수 있습니다.
이를 위해 capture_output=True 인수를 사용합니다.
다시 list_files.py를 엽니다.
nano list_files.py
결과를 캡처하도록 코드를 수정합니다.
import subprocess
## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)
## Print the captured result object
print(result)
파일을 저장하고 종료합니다.
스크립트를 실행합니다.
python3 list_files.py
다음과 비슷한 출력이 표시됩니다.
CompletedProcess(args=['ls', '-l'], returncode=0, stdout=b'total 0\n-rw-rw-r-- 1 labex labex 0 ...', stderr=b'')
이는 CompletedProcess 객체입니다. stdout=b'...' 부분을 확인합니다. b는 bytes를 의미합니다. 즉, Python 이 컴퓨터가 보는 그대로의 원시 데이터를 캡처했으며, 사람이 읽을 수 있는 텍스트 (문자열) 로 캡처한 것은 아닙니다. 다음 단계에서 이 데이터를 사람이 읽을 수 있는 텍스트로 변환합니다.
출력 디코딩
출력이 바이트로 캡처되므로, 읽고 사용할 수 있도록 표준 Python 문자열 (UTF-8) 로 디코딩해야 합니다.
바이트와 텍스트의 차이는 Python 에서 매우 중요합니다. 컴퓨터는 궁극적으로 원시 바이트를 주고받지만, 사람은 일반적으로 텍스트를 사용합니다. 자동화에서 발생하는 혼란스러운 오류 중 상당수는 현재 데이터가 어떤 형식인지 잊어버려서 발생합니다.
list_files.py를 다시 엽니다.
nano list_files.py
stdout 특성에 접근한 다음 출력 내용을 디코딩하도록 코드를 수정합니다.
import subprocess
## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)
## Decode the bytes to a string
output_string = result.stdout.decode("utf-8")
## Print the clean string
print("--- Command Output ---")
print(output_string)
파일을 저장하고 종료합니다.
스크립트를 실행합니다.
python3 list_files.py
이제 셸에서 ls -l을 직접 실행했을 때와 같은 형식의 깔끔한 출력이 표시됩니다. 단, 이번에는 Python 이 출력합니다.
참고: subprocess.run()에 text=True 또는 universal_newlines=True를 전달하면 Python 이 디코딩을 자동으로 처리하게 할 수도 있습니다. 하지만 바이너리 데이터를 처리하려면 .decode() 메서드를 이해하는 것이 중요합니다.
오류 처리
명령이 실패하면 어떻게 될까요? 예를 들어 존재하지 않는 디렉터리를 나열하려고 할 수 있습니다. 좋은 스크립트는 이러한 오류를 처리해야 합니다.
Unix 계열 시스템에서는 일반적으로 0 이 아닌 종료 상태로 명령 실패를 알립니다. subprocess.run(..., check=True)는 운영 체제의 이 규칙을 Python 예외로 변환하므로, 스크립트가 일반적인 Python 제어 흐름을 사용해 대응할 수 있습니다.
이를 확인하기 위해 handle_error.py라는 새 스크립트를 만듭니다.
nano handle_error.py
다음 코드를 추가합니다. /nonexistent라는 존재하지 않는 디렉터리를 나열해 봅니다. 또한 check=True를 사용합니다. 이 설정은 명령이 0 이 아닌 종료 코드를 반환하면, 즉 명령이 실패하면 Python 이 CalledProcessError 예외를 발생시키도록 합니다.
import subprocess
try:
## Try to list a directory that does not exist
## check=True raises an exception if the command fails
subprocess.run(["ls", "/nonexistent"], check=True, capture_output=True)
except subprocess.CalledProcessError as e:
print("An error occurred!")
print(f"Return code: {e.returncode}")
## We can also print the error message generated by the command (stderr)
print(f"Error message: {e.stderr.decode('utf-8')}")
파일을 저장하고 종료합니다.
스크립트를 실행합니다.
python3 handle_error.py
오류가 처리되었음을 나타내는 다음과 같은 출력이 표시되어야 합니다.
An error occurred!
Return code: 2
Error message: ls: cannot access '/nonexistent': No such file or directory
try-except 블록을 사용하지 않았다면 프로그램이 복잡한 트레이스백과 함께 중단됩니다. 이 방법을 사용하면 스크립트가 실패를 감지하고 다음에 수행할 작업을 결정할 수 있습니다. 예를 들어 오류를 기록하거나, 다시 시도하거나, 정상적으로 종료할 수 있습니다.
요약
이 실습에서는 Python 의 subprocess 모듈을 사용해 셸 명령을 실행하는 기본 방법을 배웠습니다. 다음 작업을 실습했습니다.
subprocess모듈 가져오기subprocess.run()을 사용한 명령 실행capture_output=True를 사용한 표준 출력 캡처- 바이트 출력을 읽을 수 있는 문자열로 디코딩
check=True와try-except블록을 사용한 명령 실패 처리
이러한 기술은 Python 으로 자동화 스크립트, 배포 도구, 시스템 관리 유틸리티를 구축할 때 필수적입니다.



