29. 운영체제와 상호작용하기: sys와 os
Python 프로그램은 고립된 채로 존재하지 않습니다. 파일, 디렉터리, 환경 설정, 그리고 프로그램이 시작되고 통신하는 방식을 관리하는 운영체제 위에서 실행됩니다. sys 모듈과 os 모듈은 이 환경과 상호작용하기 위한 도구를 제공하며, 프로그램이 명령줄 인자(command-line arguments)를 받거나, 환경 변수(environment variables)를 읽거나, 파일 시스템(file system)을 탐색하거나, 디렉터리를 생성/삭제할 수 있게 해줍니다.
이 모듈들을 이해하면 Python 스크립트는 고립된 코드에서 벗어나 더 넓은 시스템과 통합될 수 있는 프로그램으로 변하며, 시작 시점에 사용자 입력에 반응하고, 파일과 폴더를 프로그래밍 방식으로 관리할 수 있게 됩니다.
29.1) 명령줄 인자와 sys.argv 사용하기
터미널이나 명령 프롬프트에서 Python 스크립트를 실행할 때 추가 정보를 전달할 수 있는데, 이를 명령줄 인자(command-line arguments)라고 합니다. 예를 들어:
python greet.py Alice 25여기서 greet.py는 스크립트 이름이고, Alice와 25는 프로그램에 전달된 인자입니다. sys 모듈은 sys.argv를 통해 이러한 인자에 접근할 수 있게 해주는데, sys.argv는 스크립트 이름과 모든 인자를 문자열로 담고 있는 리스트(list)입니다.
29.1.1) sys.argv란?
sys.argv는 다음과 같은 리스트(list)입니다:
sys.argv[0]는 항상 실행 중인 스크립트의 이름입니다sys.argv[1],sys.argv[2]등은 스크립트 이름 뒤에 전달된 인자입니다- 모든 요소는 숫자처럼 보이더라도 문자열입니다
이것이 어떻게 동작하는지 확인하기 위해 간단한 스크립트를 만들어 봅시다:
# show_args.py
import sys
print("Script name:", sys.argv[0])
print("Number of arguments:", len(sys.argv))
print("All arguments:", sys.argv)다음과 같이 이 스크립트를 실행하면:
python show_args.py hello world 123Output:
Script name: show_args.py
Number of arguments: 4
All arguments: ['show_args.py', 'hello', 'world', '123']sys.argv에 4개의 요소가 들어 있는 것을 확인할 수 있습니다. 스크립트 이름에 더해 인자 3개가 포함됩니다. 숫자 123은 정수가 아니라 문자열 '123'으로 저장됩니다.
29.1.2) 프로그램에서 명령줄 인자 사용하기
명령줄 인자는 코드를 수정하지 않고도 사용자가 프로그램 동작을 커스터마이즈할 수 있게 해줍니다. 다음은 첫 번째 인자를 이름으로 사용해 인사하는 프로그램입니다:
# greet.py
import sys
if len(sys.argv) < 2:
print("Usage: python greet.py <name>")
sys.exit(1) # 오류 코드로 종료
name = sys.argv[1]
print(f"Hello, {name}!")다음을 실행하면:
python greet.py AliceOutput:
Hello, Alice!이름을 제공하는 것을 잊으면:
python greet.pyOutput:
Usage: python greet.py <name>프로그램은 충분한 인자가 제공되었는지 확인합니다. 그렇지 않으면 사용법 메시지를 출력하고 sys.exit(1)로 종료합니다. 숫자 1은 종료 코드(exit code)입니다. 관례적으로 0은 성공을 의미하고, 0이 아닌 값은 오류를 나타냅니다. 이를 통해 다른 프로그램이나 스크립트가 여러분의 프로그램이 성공적으로 실행되었는지 감지할 수 있습니다.
29.1.3) 인자를 다른 타입으로 변환하기
모든 인자는 문자열로 들어오기 때문에, 종종 변환이 필요합니다. 다음은 직사각형의 넓이를 계산하는 프로그램입니다:
# area.py
import sys
if len(sys.argv) < 3:
print("Usage: python area.py <width> <height>")
sys.exit(1)
try:
width = float(sys.argv[1])
height = float(sys.argv[2])
except ValueError:
print("Error: Width and height must be numbers")
sys.exit(1)
area = width * height
print(f"Area: {area}")다음을 실행하면:
python area.py 5.5 3.2Output:
Area: 17.6사용자가 잘못된 입력을 제공하면:
python area.py five threeOutput:
Error: Width and height must be numberstry-except 블록(25장)은 변환 오류를 깔끔하게 처리하여, traceback과 함께 크래시가 나기보다는 도움이 되는 피드백을 제공하게 해줍니다.
29.2) sys로 인터프리터 및 런타임 정보 얻기
sys 모듈은 Python 인터프리터 자체와 런타임 환경에 대한 정보를 제공합니다. 이는 디버깅, 로깅, 또는 서로 다른 Python 버전이나 플랫폼에 맞게 동작이 달라지는 코드를 작성할 때 유용합니다.
29.2.1) Python 버전 정보
sys.version과 sys.version_info는 어떤 Python 버전으로 코드가 실행 중인지 알려줍니다:
import sys
print("Python version string:", sys.version)
print("Version info:", sys.version_info)
print(f"Major version: {sys.version_info.major}")
print(f"Minor version: {sys.version_info.minor}")Output (example):
Python version string: 3.11.4 (main, Jul 5 2023, 13:45:01) [GCC 11.2.0]
Version info: sys.version_info(major=3, minor=11, micro=4, releaselevel='final', serial=0)
Major version: 3
Minor version: 11sys.version은 사람이 읽기 쉬운 문자열이고, sys.version_info는 프로그램에서 비교할 수 있는 named tuple입니다:
import sys
if sys.version_info < (3, 8):
print("This program requires Python 3.8 or higher")
sys.exit(1)
print("Python version is compatible")이렇게 하면 프로그램이 지원되는 Python 버전에서만 실행되도록 보장할 수 있습니다.
29.2.2) 플랫폼 정보
sys.platform은 운영체제를 식별합니다:
import sys
print("Platform:", sys.platform)
if sys.platform == "win32":
print("Running on Windows")
elif sys.platform == "darwin":
print("Running on macOS")
elif sys.platform.startswith("linux"):
print("Running on Linux")
else:
print("Running on another platform")Output (on Linux):
Platform: linux
Running on Linux이를 통해 파일 경로나 시스템 명령을 다르게 사용하는 등, 필요할 때 플랫폼별 코드를 작성할 수 있습니다.
29.2.3) 모듈 검색 경로
sys.path는 모듈을 import할 때 Python이 검색하는 디렉터리 목록입니다:
import sys
print("Module search paths:")
for path in sys.path:
print(f" {path}")Output (example):
Module search paths:
/home/user/projects
/usr/lib/python3.11
/usr/lib/python3.11/lib-dynload
/home/user/.local/lib/python3.11/site-packages첫 번째 항목은 보통 스크립트가 들어 있는 디렉터리입니다. import를 사용할 때 Python은 이 경로들을 순서대로 검색합니다. sys.path를 이해하면 import 오류를 디버깅하거나 사용자 정의 모듈 디렉터리를 추가하는 데 도움이 됩니다.
29.2.4) 종료 코드로 프로그램 종료하기
프로그램을 중단하기 위해 sys.exit()를 사용하는 것을 이미 보았습니다. 성공 또는 실패를 나타내기 위해 종료 코드를 전달할 수 있습니다:
import sys
def process_data(filename):
try:
with open(filename) as f:
data = f.read()
# 데이터 처리...
return True
except FileNotFoundError:
print(f"Error: File '{filename}' not found", file=sys.stderr)
return False
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python script.py <filename>", file=sys.stderr)
sys.exit(1)
success = process_data(sys.argv[1])
sys.exit(0 if success else 1)종료 코드는 Unix 관례를 따릅니다:
0은 성공을 의미합니다- 0이 아닌 값은 서로 다른 유형의 오류를 나타냅니다
- 다른 프로그램은 이 코드들을 확인해서 여러분의 프로그램이 성공했는지 판단할 수 있습니다
29.3) os로 환경 변수에 접근하기
환경 변수(environment variables)는 운영체제나 사용자가 설정하는 키-값 쌍이며, 프로그램에서 읽을 수 있습니다. 환경 변수는 설정(configuration), 경로 저장, API 키 등 하드코딩해서는 안 되는 다양한 설정을 저장하는 데 사용됩니다.
29.3.1) 환경 변수 읽기
os 모듈은 모든 환경 변수를 담고 있는 딕셔너리처럼 동작하는 객체인 os.environ을 제공합니다:
import os
# 특정 환경 변수 가져오기
home = os.environ.get('HOME') # Unix/Linux/macOS에서
print(f"Home directory: {home}")
# 존재하지 않는 변수는 None을 반환합니다.
api_key = os.environ.get('MY_API_KEY')
print(f"API key: {api_key}")Output (on Linux):
Home directory: /home/alice
API key: None기본값도 제공할 수 있습니다:
import os
# 기본값과 함께 환경 변수 가져오기
log_level = os.environ.get('LOG_LEVEL', 'INFO')
print(f"Log level: {log_level}")Output (if LOG_LEVEL not set):
Log level: INFO29.3.2) 자주 쓰는 환경 변수
운영체제마다 표준 환경 변수가 제공됩니다:
import os
# 크로스 플랫폼 변수
print("Path:", os.environ.get('PATH'))
# Unix/Linux/macOS
print("User:", os.environ.get('USER'))
print("Home:", os.environ.get('HOME'))
print("Shell:", os.environ.get('SHELL'))
# Windows
print("User", os.environ.get('USERNAME'))
print("User Profile:", os.environ.get('USERPROFILE'))
print("Temp:", os.environ.get('TEMP'))PATH는 특히 중요합니다. 시스템이 실행 파일(executable program)을 찾을 디렉터리 목록을 담고 있습니다. python 같은 명령을 입력하면 시스템은 이 디렉터리들을 검색합니다.
29.3.3) 환경 변수 설정하기
프로그램과 프로그램이 생성하는 모든 서브프로세스(subprocess)에 대해 환경 변수를 수정할 수 있습니다:
import os
# 환경 변수 설정하기
os.environ['MY_CONFIG'] = 'production'
# 다시 읽기
print(os.environ.get('MY_CONFIG')) # Output: production
# 환경 변수 삭제하기
del os.environ['MY_CONFIG']중요: os.environ의 변경 사항은 현재 Python 프로세스와 그 프로세스가 실행하는 프로그램에만 영향을 줍니다. 프로그램이 종료된 뒤에는 유지되지 않으며 다른 프로그램에도 영향을 주지 않습니다.
29.4) 파일 경로와 디렉터리 다루기 (os.path, os.getcwd)
파일을 다루는 프로그램에서는 파일 경로를 올바르게 관리하는 것이 매우 중요합니다. os와 os.path 모듈은 플랫폼 독립적인 방식으로 경로를 구성, 조작, 조회할 수 있는 도구를 제공합니다.
29.4.1) 현재 작업 디렉터리 가져오기
현재 작업 디렉터리(current working directory)(CWD)는 프로그램이 상대 경로(relative path)의 시작점으로 간주하는 폴더입니다:
import os
cwd = os.getcwd()
print(f"Current working directory: {cwd}")Output (example):
Current working directory: /home/alice/projects/myapp'data.txt' 같은 상대 경로로 파일을 열면 Python은 현재 작업 디렉터리에서 파일을 찾습니다. CWD를 이해하면 "file not found" 오류를 디버깅하는 데 도움이 됩니다.
29.4.2) 현재 디렉터리 변경하기
os.chdir()로 작업 디렉터리를 변경할 수 있습니다:
import os
original = os.getcwd()
print("Original directory:", original)
# 다른 디렉터리로 변경
os.chdir('/tmp')
print("New directory:", os.getcwd())
# 다시 되돌리기
os.chdir(original)
print("Back to:", os.getcwd())Output:
Original directory: /home/alice/projects
New directory: /tmp
Back to: /home/alice/projects참고: Chapter 28에서 원래 디렉토리를 자동으로 복원하는 contextlib.chdir()를 배웠습니다. 간단한 디렉토리 변경의 경우 컨텍스트 매니저를 사용하는 것이 좋습니다:
from contextlib import chdir
with chdir('/tmp'):
print("임시 위치:", os.getcwd())
# 자동으로 복원됨이렇게 하면 에러가 발생하더라도 디렉토리가 항상 복원됩니다.
29.4.3) os.path.join()으로 경로 구성하기
운영체제마다 경로 구분자(path separator)가 다릅니다:
- Unix/Linux/macOS:
/(슬래시) - Windows:
\(백슬래시)
os.path.join()은 현재 플랫폼에 맞게 경로를 올바르게 구성해 줍니다:
import os
# 하위 디렉터리에 있는 파일로의 경로 구성
data_dir = 'data'
filename = 'users.txt'
filepath = os.path.join(data_dir, filename)
print(f"File path: {filepath}")Output (on Unix/Linux/macOS):
File path: data/users.txtOutput (on Windows):
File path: data\users.txt여러 구성 요소를 이어 붙일 수도 있습니다:
import os
base = '/home/alice'
project = 'myapp'
subdir = 'data'
file = 'config.json'
full_path = os.path.join(base, project, subdir, file)
print(full_path) # Output: /home/alice/myapp/data/config.jsonos.path.join()을 사용하면 운영체제 전반에 걸쳐 코드를 이식 가능(portable)하게 만들 수 있습니다.
29.4.4) 경로가 존재하는지 확인하기
파일이나 디렉터리로 작업하기 전에 존재 여부를 확인하세요:
import os
path = 'data.txt'
if os.path.exists(path):
print(f"'{path}' exists")
else:
print(f"'{path}' does not exist")파일인지 디렉터리인지도 구체적으로 확인할 수 있습니다:
import os
path = 'mydir'
if os.path.isfile(path):
print(f"'{path}' is a file")
elif os.path.isdir(path):
print(f"'{path}' is a directory")
elif os.path.exists(path):
print(f"'{path}' exists but is neither a file nor directory")
else:
print(f"'{path}' does not exist")이러한 확인은 존재하지 않는 파일을 열거나 존재하지 않는 디렉터리를 나열하려 할 때 발생하는 오류를 방지합니다.
29.4.5) 절대 경로 얻기
os.path.abspath()는 상대 경로를 절대 경로로 변환합니다:
import os
relative_path = 'data/users.txt'
absolute_path = os.path.abspath(relative_path)
print(f"Relative: {relative_path}")
print(f"Absolute: {absolute_path}")Output (example):
Relative: data/users.txt
Absolute: /home/alice/projects/myapp/data/users.txt이는 로깅, 오류 메시지, 또는 파일의 정확한 위치를 알아야 할 때 유용합니다.
29.4.6) 경로를 구성 요소로 분리하기
os.path.split()은 경로를 디렉터리와 파일명으로 분리합니다:
import os
path = '/home/alice/projects/data.txt'
directory, filename = os.path.split(path)
print(f"Directory: {directory}")
print(f"Filename: {filename}")Output:
Directory: /home/alice/projects
Filename: data.txtos.path.basename()은 파일명만 가져오고, os.path.dirname()은 디렉터리만 가져옵니다:
import os
path = '/home/alice/projects/data.txt'
print(f"Basename: {os.path.basename(path)}") # Output: data.txt
print(f"Dirname: {os.path.dirname(path)}") # Output: /home/alice/projects29.4.7) 파일 확장자 분리하기
os.path.splitext()는 파일명과 확장자를 분리합니다:
import os
filename = 'report.pdf'
name, extension = os.path.splitext(filename)
print(f"Name: {name}") # Output: report
print(f"Extension: {extension}") # Output: .pdf이는 파일 유형에 따라 파일을 처리할 때 유용합니다:
import os
files = ['data.csv', 'image.png', 'document.txt', 'script.py']
for file in files:
name, ext = os.path.splitext(file)
if ext == '.csv':
print(f"Process CSV file: {file}")
elif ext == '.png':
print(f"Process image file: {file}")Output:
Process CSV file: data.csv
Process image file: image.png29.5) 파일과 디렉터리 나열, 생성, 삭제하기
os 모듈은 파일 시스템을 조작하는 함수를 제공합니다. 디렉터리 내용 나열, 새 디렉터리 생성, 파일과 폴더 삭제 등이 가능합니다.
29.5.1) 디렉터리 내용 나열하기
os.listdir()는 디렉터리 안의 모든 항목 리스트를 반환합니다:
import os
# 현재 디렉터리 내용 나열
contents = os.listdir('.')
print("Current directory contents:")
for item in contents:
print(f" {item}")Output (example):
Current directory contents:
script.py
data.txt
mydir
README.md이 리스트에는 파일과 디렉터리가 모두 포함됩니다. 둘을 구분하려면:
import os
contents = os.listdir('.')
print("Files:")
for item in contents:
if os.path.isfile(item):
print(f" {item}")
print("\nDirectories:")
for item in contents:
if os.path.isdir(item):
print(f" {item}")Output:
Files:
script.py
data.txt
README.md
Directories:
mydir29.5.2) 디렉터리 생성하기
os.mkdir()는 단일 디렉터리를 생성합니다:
import os
new_dir = 'output'
if not os.path.exists(new_dir):
os.mkdir(new_dir)
print(f"Created directory: {new_dir}")
else:
print(f"Directory already exists: {new_dir}")Output:
Created directory: output중요: os.mkdir()는 상위 디렉터리가 없으면 실패합니다. 예를 들어 'data'가 없는데 'data/output'을 만들려고 하면 오류가 발생합니다.
os.makedirs()는 필요한 모든 상위 디렉터리를 생성합니다:
import os
nested_dir = 'data/processed/2024'
if not os.path.exists(nested_dir):
os.makedirs(nested_dir)
print(f"Created directory structure: {nested_dir}")Output:
Created directory structure: data/processed/2024이는 존재하지 않는 경우 data, data/processed, data/processed/2024를 순서대로 생성합니다.
29.5.3) 파일 삭제하기
os.remove()는 파일을 삭제합니다:
import os
filename = 'temp.txt'
# 임시 파일 생성
with open(filename, 'w') as f:
f.write('Temporary data')
print(f"File exists: {os.path.exists(filename)}") # Output: True
# 파일 삭제
os.remove(filename)
print(f"File exists: {os.path.exists(filename)}") # Output: False경고: os.remove()는 파일을 영구적으로 삭제합니다. 휴지통으로 가지 않습니다.
29.5.4) 디렉터리 삭제하기
os.rmdir()는 비어 있는 디렉터리를 제거합니다:
import os
directory = 'empty_dir'
# 비어 있는 디렉터리를 만들고 제거
os.mkdir(directory)
print(f"Created: {directory}")
os.rmdir(directory)
print(f"Removed: {directory}")os.rmdir()는 디렉터리에 파일이 있으면 실패합니다. 디렉터리와 그 안의 모든 내용을 제거하려면 먼저 파일을 삭제해야 합니다:
import os
def remove_directory_contents(directory):
"""디렉터리 안의 모든 파일을 제거한 뒤, 디렉터리를 제거합니다.
참고: 디렉토리에 서브디렉토리가 포함되어 있으면 실패합니다.
"""
if not os.path.exists(directory):
print(f"Directory does not exist: {directory}")
return
# 디렉터리 안의 모든 파일 제거
for item in os.listdir(directory):
item_path = os.path.join(directory, item)
if os.path.isfile(item_path):
os.remove(item_path)
print(f"Removed file: {item_path}")
# 이제 비어 있는 디렉터리 제거
os.rmdir(directory)
print(f"Removed directory: {directory}")
# 사용 예시
test_dir = 'test_data'
os.makedirs(test_dir, exist_ok=True)
# 테스트 파일 생성
with open(os.path.join(test_dir, 'file1.txt'), 'w') as f:
f.write('test')
with open(os.path.join(test_dir, 'file2.txt'), 'w') as f:
f.write('test')
# 모두 제거
remove_directory_contents(test_dir)Output:
Removed file: test_data/file1.txt
Removed file: test_data/file2.txt
Removed directory: test_data참고: 더 복잡한 디렉터리 삭제(하위 디렉터리 포함)가 필요하다면 Python의 shutil 모듈이 shutil.rmtree()를 제공하지만, 이는 현재 범위를 벗어납니다.
29.5.5) 파일과 디렉터리 이름 변경하기
os.rename()은 파일과 디렉터리의 이름을 바꾸거나 이동합니다:
import os
# 파일 이름 변경
old_name = 'draft.txt'
new_name = 'final.txt'
# 테스트 파일 생성
with open(old_name, 'w') as f:
f.write('content')
os.rename(old_name, new_name)
print(f"Renamed '{old_name}' to '{new_name}'")파일을 다른 디렉터리로 옮길 수도 있습니다:
import os
# 디렉터리와 파일 생성
os.makedirs('source', exist_ok=True)
os.makedirs('destination', exist_ok=True)
with open('source/file.txt', 'w') as f:
f.write('content')
# 파일을 다른 디렉터리로 이동
os.rename('source/file.txt', 'destination/file.txt')
print("Moved file to destination directory")sys 모듈과 os 모듈은 Python 프로그램이 운영체제와 상호작용하고, 명령줄 입력을 받아들이고, 설정을 읽고, 파일과 디렉터리를 관리할 수 있는 능력을 제공합니다. 이러한 기능은 단순한 스크립트를 더 넓은 시스템 환경과 매끄럽게 통합되는 강력한 명령줄 도구로 변환해 줍니다.