Step 51. 표준 라이브러리 — 파이썬에 이미 들어 있는 도구 상자

Step 51. 표준 라이브러리 — 파이썬에 이미 들어 있는 도구 상자

Level 1 — 프로그래밍과 컴퓨터 내부 | 난이도 ★★☆☆☆ | 예상 소요 시간 3시간

전제: Step 41~50 완료. 파이썬 기본 문법과 모듈 import를 압니다. 파이썬 스크립트(.py)를 만들고 터미널에서 python 파일명.py로 실행할 수 있어야 합니다.

  • 준비물: 파이썬이 설치된 PC, 텍스트 에디터, 터미널(파워쉘 또는 Git Bash).
  • 주의: 오늘 실습은 100% 안전합니다. 하는 일은 전부 "조회"와 "출력 받아 오기"뿐입니다. 마지막에 만드는 도구는 운영체제 명령을 실행하지만, 예제로 쓰는 ipconfig 같은 명령은 보기 전용입니다.

파이썬을 설치한 순간, 여러분의 컴퓨터에는 이미 수백 개의 도구가 함께 깔렸습니다. 설치 없이 import 한 줄이면 바로 쓸 수 있는 이 기본 도구들을 표준 라이브러리(standard library)라고 부릅니다. 지금까지 쓴 random, re, datetime도 그중 하나였습니다.

오늘 만날 세 도구는 성격이 다릅니다. os, sys, subprocess는 "파이썬과 운영체제 사이의 전화선"입니다. os는 파일과 폴더를 보는 전화, sys는 스크립트 자신의 정보를 보는 전화, subprocess는 운영체제에게 명령을 시키는 전화입니다. 이 전화선이 열리면 파이썬이 텍스트 놀이터를 벗어나 컴퓨터 전체를 부리기 시작합니다. 보안 도구들이 하는 일의 절반이 바로 이 전화선 위에서 일어납니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • 표준 라이브러리가 무엇인지 설명하고, 필요한 모듈을 찾아 import한다
  • os 모듈로 현재 위치, 파일 목록, 파일 정보를 조회한다
  • sys.argv로 명령줄 인자(실행 시 뒤에 적는 값)를 받아 처리한다
  • subprocess로 운영체제 명령을 파이썬 안에서 실행하고 출력을 받아 온다
  • 인자로 명령을 건네면 실행해 주는 작은 도구를 직접 만든다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 파이썬 3.12, 터미널(파워쉘). 모두 표준 라이브러리라 새로 설치하는 것 없음
오늘의 문법 os.getcwd() / os.listdir() / os.path.join() / os.environ, sys.argv / sys.exit(), subprocess.run(), .decode("cp949")
필요한 개념 표준 라이브러리, 명령줄 인자, 환경 변수(PATH), 반환 코드(returncode)

2-1. 표준 라이브러리 — 기본 탑재 도구 상자

파이썬 공식 배포판에 포함된 모듈 모음이 표준 라이브러리입니다. 수학 계산, 날짜, 파일, 네트워크 기초까지 — "파이썬으로 되나?" 싶은 일의 절반 이상은 여기에 이미 있습니다. 전체 목록은 공식 문서(docs.python.org/ko/3/library)에 있습니다. 오늘은 그중 운영체제와 연결되는 세 개를 뽑습니다.

2-2. os — 운영체제와의 창구

운영체제는 컴퓨터의 총괄 관리자이고, os 모듈은 그 관리자에게 묻고 시키는 창구입니다. "지금 어느 폴더에 있지?"(getcwd), "이 폴더에 뭐가 있지?"(listdir), "이 경로를 운영체제 방식으로 붙여 줘"(path.join) 같은 질문을 던집니다.

2-3. sys.argv — 스크립트에 말 거는 법

sys는 실행 중인 파이썬 자체에 관한 모듈입니다. 오늘의 주인공은 sys.argv로, 명령줄 인자(command-line arguments)를 담은 리스트입니다. python mytool.py hello처럼 스크립트 이름 뒤에 띄어쓰기로 적는 추가 값들입니다. 0번 칸에는 언제나 스크립트 자신의 이름이, 1번 칸부터 우리가 적은 인자가 들어옵니다.

2-4. subprocess — 명령을 시키는 전화

터미널에서 치던 명령(ipconfig, dir 같은)을 파이썬 코드 안에서 실행하고, 그 출력까지 받아 오는 모듈입니다. 보안 도구가 외부 프로그램을 불러 쓸 때 이 방식을 씁니다. 실행 결과에는 반환 코드(returncode)가 따라오는데, 0이면 성공이라는 약속입니다.

2-5. 환경 변수 — 운영체제의 메모판

환경 변수(environment variable)는 운영체제가 모든 프로그램에게 공개해 두는 설정 메모입니다. 대표적인 것이 PATH — 명령어를 쳤을 때 실행 파일을 어디서 찾을지 적어 둔 폴더 목록입니다. 터미널에서 python을 쳤을 때 python.exe가 실행되는 이유가 이 목록에 파이썬 폴더가 있기 때문입니다(Step 41에서 건드렸던 그것입니다).


3. 따라 하기

3-1. os — 지금 여기는 어디?

작업 폴더(앞으로 실습할 security-study 폴더)에서 os_lab.py를 만듭니다:

import os

print("현재 폴더:", os.getcwd())
print("파일 목록:", os.listdir())
현재 폴더: C:\Users\dlqht\Documents\...\tmp_test
파일 목록: ['os_lab.py', 's51_1_os.py', 's51_2_greet.py', ...]

(2026-09-09 실측. 폴더 경로와 파일 목록은 여러분의 것과 다릅니다.)

출력 읽는 법: getcwd()는 "get current working directory(현재 작업 폴더 얻기)"의 준말입니다. 스크립트는 이 폴더를 기준으로 상대 경로를 해석합니다. listdir()는 그 폴더 안의 이름들을 리스트로 줍니다.

왜 하는가: "내 프로그램이 지금 어디 서서 무엇을 보고 있는가"는 모든 파일 작업의 출발점입니다.

3-2. 경로 붙이기 — os.path.join

import os

print(os.path.join("reports", "result.txt"))
print(os.path.join("a", "b", "c.txt"))
reports\result.txt
a\b\c.txt

(2026-09-09 실측, 윈도우. 맥·리눅스에서는 /로 붙습니다.)

출력 읽는 법: 경로를 문자열 더하기로 붙이면 운영체제마다 구분자가 달라(\ vs /) 문제가 생깁니다. os.path.join은 현재 운영체제에 맞는 구분자를 알아서 골라 줍니다. 같은 코드가 어느 운영체제에서도 작동하는 길입니다.

예측해 보기: os.path.exists("os_lab.py")는 무엇을 줄까요? 예측하고 실행해 보세요.

3-3. 환경 변수 엿보기

import os

print("사용자:", os.environ.get("USERNAME"))
print("PATH 앞부분:", os.environ.get("PATH")[:60], "...")
사용자: dlqht
PATH 앞부분: C:\Users\dlqht\AppData\Roaming\kimi-desktop\daimon-sha ...

(2026-09-09 실측. 여러분의 사용자 이름과 PATH가 나옵니다.)

출력 읽는 법: os.environ은 환경 변수 전체를 담은 사전처럼 동작하고, .get("이름")으로 하나씩 꺼냅니다. PATH는 세미콜론으로 구분된 폴더들의 긴 나열입니다. 뒤에서 subprocess가 명령을 찾을 때 바로 이 목록을 뒤집니다.

3-4. sys.argv — 스크립트에 말 걸기

greet.py를 만듭니다:

import sys

print("인자 전체:", sys.argv)
print("스크립트 이름:", sys.argv[0])
if len(sys.argv) > 1:
    print("첫 번째 인자:", sys.argv[1])

터미널에서 인자를 붙여 실행합니다:

python greet.py 안녕 보안
인자 전체: ['greet.py', '안녕', '보안']
스크립트 이름: greet.py
첫 번째 인자: 안녕

인자 없이 실행하면:

python greet.py
인자 전체: ['greet.py']
스크립트 이름: greet.py

(2026-09-09 실측.)

출력 읽는 법: 0번 칸은 언제나 스크립트 이름, 1번 칸부터 우리가 적은 인자입니다. if len(sys.argv) > 1: 검사는 "인자가 안 들어오면 1번 칸 자체가 없어서 죽는다"를 막는 관례입니다 — 인자 없이 실행한 두 번째 실측에서 마지막 줄이 안 나온 것을 확인하세요.

왜 하는가: nmap -p 80 대상 같은 도구들의 사용법이 전부 이 구조입니다. 우리 도구도 같은 방식으로 인자를 받는 첫걸음입니다.

3-5. subprocess — 운영체제에 심부름시키기

runner.py를 만듭니다:

import subprocess

result = subprocess.run(
    ["ipconfig"],
    capture_output=True,
    text=False
)
output = result.stdout.decode("cp949", errors="replace")
print(output[:200])
print("...")
print("반환 코드:", result.returncode)
Windows IP 구성


알 수 없는 어댑터 Tailscale:

   연결별 DNS 접미사. . . . : tail88dfb9.ts.net
...
반환 코드: 0

(2026-09-09 실측, 한글 윈도우. 네트워크 구성은 컴퓨터마다 다릅니다.)

출력 읽는 법: subprocess.run()에 명령을 리스트로 건네 실행합니다. capture_output=True는 "출력을 화면에 뿌리지 말고 나에게 줘"라는 뜻이고, 그 결과가 result.stdout에 바이트로 담깁니다. 윈도우 명령의 출력은 한글이 cp949 코드표로 되어 있어서 Step 50의 지식으로 디코딩합니다. 깨진 글자는 errors="replace"가 “로 대신합니다. 반환 코드 0은 "명령 성공"입니다.

왜 하는가: 네트워크 명령들(Step 27~34)을 파이썬이 직접 실행하고 결과를 분석할 수 있게 되는 순간입니다. 자동화의 시작입니다.

3-6. 명령 실행 스크립트 — 종합

cmdtool.py를 만듭니다:

import sys
import subprocess

if len(sys.argv) < 2:
    print("사용법: python cmdtool.py <명령> [옵션...]")
    sys.exit(1)

result = subprocess.run(sys.argv[1:], capture_output=True)
print(result.stdout.decode("cp949", errors="replace"))
if result.returncode != 0:
    print("명령이 실패했습니다. 코드:", result.returncode)

실행합니다:

python cmdtool.py ipconfig /all
Windows IP 구성

   호스트 이름 . . . . . . . : XI3492
   주 DNS 접미사 . . . . . . :
   노드 유형 . . . . . . . . : 혼성
   IP 라우팅 사용. . . . . . : 아니요
   ...

(2026-09-09 실측. 호스트 이름 등은 컴퓨터마다 다릅니다.)

출력 읽는 법: sys.argv[1:]는 1번 칸부터 끝까지 — 우리가 적은 명령 전체를 리스트로 자릅니다. 이제 이 스크립트는 어떤 명령이든 인자로 받아 실행하는 작은 껍데기가 되었습니다. sys.exit(1)은 "여기서 끝낸다, 단 0이 아닌 코드로"라는 뜻으로, 실패로 끝났음을 운영체제에 알리는 관례입니다.

3-7. os.path — 파일의 정보 묻기

경로 붙이기 말고도 os.path는 파일에 관한 질문에 답합니다. fileinfo.py를 만들어 같은 폴더에서 실행합니다:

import os

name = "fileinfo.py"
print("존재하나?", os.path.exists(name))
print("크기(바이트):", os.path.getsize(name))
print("확장자:", os.path.splitext(name))
print("없는 파일:", os.path.exists("없는파일.xyz"))
존재하나? True
크기(바이트): 486
확장자: ('fileinfo', '.py')
없는 파일: False

(2026-09-09 실측. 크기는 파일을 저장할 때마다 달라집니다.)

출력 읽는 법: exists는 있느냐, getsize는 크기가 얼마냐, splitext는 이름과 확장자를 둘로 나눠 줍니다. splitext의 결과는 튜플(순서가 정해진 묶음)로, [0]이 이름, [1]이 확장자입니다.

주의할 점: 이 파일들은 스크립트를 실행한 폴더를 기준으로 찾습니다. 실측에서도 다른 폴더에서 실행했다가 FileNotFoundError: [WinError 2] 지정된 파일을 찾을 수 없습니다를 만났습니다. 상대 경로의 기준은 "파일이 있는 곳"이 아니라 "명령을 친 곳"입니다.

왜 하는가: "파일이 있는지 확인하고, 없으면 만들고, 확장자별로 분류하는" — 자동화 스크립트의 가장 흔한 세 동작이 여기 다 들어 있습니다.


4. 미션과 연습문제

미션 — 시스템 점검 도구 만들기

syscheck.py를 만들어 다음을 구현하세요:

  1. 인자로 기능을 골라 실행합니다:
    • python syscheck.py files → 현재 폴더의 파일 목록과 총 개수 출력
    • python syscheck.py net → ipconfig 실행 결과 출력
    • python syscheck.py env → 사용자 이름과 PATH 출력
    • 인자 없음 → 위 사용법 안내 출력
  2. files 기능에서는 확장자별 개수도 함께 세어 보여 줍니다 (딕셔너리 + splitext 활용)
  3. 결과를 점검결과.txt에도 함께 저장합니다 (Step 45 복습)
  4. 각 기능을 함수로 나누고, 실패해도 프로그램이 죽지 않게 예외처리합니다 (Step 46 복습)

연습문제

문제 1. 표준 라이브러리와 외부 패키지(Step 53에서 배울)의 차이는 무엇인가요? 사용하기 전에 필요한 준비가 각각 어떻게 다른가요?

문제 2. sys.argv에서 0번 칸에는 무엇이 들어 있고, 왜 sys.argv[1]을 읽기 전에 길이 검사를 해야 하나요?

문제 3. subprocess에 명령을 건넬 때 ["ipconfig", "/all"]처럼 리스트로 나누는 이유는 무엇인가요? ["ipconfig /all"]처럼 한 칸에 넣으면 어떻게 되나요?

문제 4. 윈도우 명령의 출력을 받았는데 한글이 깨져 보입니다. 원인과 해결법을 코드로 말해 보세요.


5. 모범 답안과 완료 기준

미션 모범 답안

import sys
import os
import subprocess

def show_files(lines):
    names = os.listdir()
    lines.append(f"파일 {len(names)}개:")
    ext_count = {}
    for n in names:
        lines.append("  " + n)
        ext = os.path.splitext(n)[1] or "(확장자 없음)"
        ext_count[ext] = ext_count.get(ext, 0) + 1
    lines.append("--- 확장자별 ---")
    for ext, c in ext_count.items():
        lines.append(f"  {ext}: {c}개")

def show_net(lines):
    try:
        r = subprocess.run(["ipconfig"], capture_output=True)
        lines.append(r.stdout.decode("cp949", errors="replace"))
    except FileNotFoundError:
        lines.append("ipconfig를 찾을 수 없습니다.")

def show_env(lines):
    lines.append("사용자: " + str(os.environ.get("USERNAME")))
    lines.append("PATH: " + str(os.environ.get("PATH")))

lines = ["=== 시스템 점검 결과 ==="]
if len(sys.argv) < 2:
    print("사용법: python syscheck.py [files|net|env]")
    sys.exit(1)

if sys.argv[1] == "files":
    show_files(lines)
elif sys.argv[1] == "net":
    show_net(lines)
elif sys.argv[1] == "env":
    show_env(lines)
else:
    lines.append("알 수 없는 기능: " + sys.argv[1])

print("\n".join(lines))
with open("점검결과.txt", "w", encoding="utf-8") as f:
    f.write("\n".join(lines))

검증하는 법: ① 세 가지 인자(files, net, env)가 각각 동작하는가. ② 인자 없이 실행하면 사용법이 나오고 조용히 끝나는가. ③ 실행 후 점검결과.txt가 생겼고 내용이 화면 출력과 같은가. ④ ext_count.get(ext, 0)는 "없으면 0으로 치고"라는 딕셔너리 집계 관례입니다 (Step 42 복습). 화면 출력과 파일 저장에 같은 lines 리스트를 쓴 것이 포인트입니다 — 출력할 내용을 한 번만 만들어 두 곳에 보냅니다.

연습문제 해답

문제 1 해답. 표준 라이브러리는 파이썬 설치 시 함께 들어오는 기본 모듈로, import만 하면 바로 쓸 수 있습니다. 외부 패키지는 pip으로 따로 설치해야 합니다. 준비의 차이는 "설치가 필요한가" 한 줄로 요약됩니다.

문제 2 해답. 0번 칸에는 스크립트 자신의 이름이 들어 있습니다. 인자 없이 실행하면 리스트에 0번 칸만 있어서 sys.argv[1]은 존재하지 않는 칸을 가리키게 되고, 실측에서 본 IndexError: list index out of range가 납니다. 그래서 if len(sys.argv) < 2: 검사를 먼저 하고, 없으면 사용법을 보여 주고 끝내는 것이 관례입니다.

문제 3 해답. 리스트의 칸 하나가 "프로그램 이름", 그 다음 칸들이 "옵션"이라는 약속이기 때문입니다. ["ipconfig /all"]처럼 한 칸에 넣으면 파이썬은 "ipconfig /all"이라는 이름의 프로그램을 찾고, 그런 프로그램은 없으니 실패합니다 (2026-09-09 실측: FileNotFoundError: [WinError 2] 지정된 파일을 찾을 수 없습니다). 리스트 방식은 명령 주입(악의적 명령 끼워 넣기) 위험도 줄여 주는 안전한 습관입니다.

문제 4 해답. 윈도우 명령의 출력은 utf-8이 아니라 cp949 코드표로 되어 있기 때문입니다. result.stdout.decode("cp949", errors="replace")로 디코딩합니다. "출력이 깨지면 코드표를 의심하라"는 Step 50에서 배운 상식입니다.

완료 기준 체크리스트

  • [ ] 표준 라이브러리가 무엇인지 설명할 수 있다
  • [ ] os.getcwd와 os.listdir로 현재 폴더를 조사할 수 있다
  • [ ] os.path.join/exists/getsize/splitext를 쓸 수 있다
  • [ ] sys.argv로 인자를 받아 길이 검사와 함께 처리할 수 있다
  • [ ] subprocess.run으로 명령을 실행하고 cp949로 출력을 디코딩할 수 있다
  • [ ] 상대 경로가 "실행한 폴더" 기준임을 설명할 수 있다
  • [ ] 미션: syscheck.py를 완성하고 점검결과.txt까지 저장했다

6. 흔한 실수와 해결

벽 1. IndexError: list index out of range

증상: sys.argv[1]을 읽다가 죽습니다 (2026-09-09 실측):

IndexError: list index out of range

원인: 인자 없이 실행해서 1번 칸이 없습니다.
해결: if len(sys.argv) < 2: 검사를 먼저 하고, 없으면 사용법을 보여 주고 sys.exit(1)로 끝냅니다. 친절한 도구는 사용법부터 알려 줍니다.

벽 2. 한글이 깨져 나온다

증상: subprocess 출력에서 한글이 깨진 글자로 보입니다.
원인: 윈도우 명령 출력은 cp949 코드표인데, 그대로 찍거나 utf-8로 디코딩했기 때문입니다.
해결: .decode("cp949", errors="replace"). 실측 환경(한글 윈도우)에서도 디코딩해야 "알 수 없는 어댑터" 같은 한글이 제대로 읽혔습니다.

벽 3. FileNotFoundError — 터미널에서는 되는 명령이 안 된다

증상 (2026-09-09 실측, ver 명령 실행 시):

FileNotFoundError: [WinError 2] 지정된 파일을 찾을 수 없습니다

원인: 두 가지 경우가 있습니다. ① 명령 이름을 오타냈거나 그 프로그램이 PATH에 없는 경우. ② ver처럼 cmd.exe의 내장 명령인 경우 — 내장 명령은 독립 실행 파일이 없어서 subprocess가 찾을 수 없습니다.
해결: 그 명령이 실행 파일로 존재하는지 먼저 확인하세요 (ipconfig는 파일로 존재, ver·dir은 cmd의 내장 기능). 내장 명령이 꼭 필요하면 ["cmd", "/c", "ver"]처럼 cmd를 거쳐 실행합니다.

벽 4. 명령을 한 칸에 통째로 넣는다

증상: subprocess.run(["ipconfig", "/all"])은 되는데 ["ipconfig /all"]은 실패합니다 (2026-09-09 실측, 둘 다 확인).
원인: 리스트의 한 칸은 통째로 "프로그램 이름"입니다. "ipconfig /all"이라는 이름의 프로그램은 없습니다.
해결: 명령과 옵션을 리스트의 칸으로 나누세요. 참고로 윈도우에서는 subprocess.run("ipconfig /all")처럼 문자열 하나가 우연히 동작하기도 합니다(실측으로 확인) — 하지만 경로에 공백이 끼는 순간 꼬이므로, 어느 운영체제에서도 안전한 리스트 방식을 습관으로 하세요.

벽 5. 파일이 분명히 있는데 없다고 나온다

증상: os.path.exists("mylog.txt")가 False이거나 getsize에서 FileNotFoundError.
원인: 상대 경로의 기준은 파일이 있는 폴더가 아니라 명령을 실행한 폴더(getcwd의 값)입니다. 다른 폴더에서 실행하면 못 찾습니다 (2026-09-09 실측으로 재현 확인).
해결: 실행 전 print(os.getcwd())로 내가 어디 서 있는지 확인하는 습관을 들이세요. 이 한 줄이 경로 문제의 90%를 잡아 줍니다.


7. 정리

오늘의 개념

개념 한 줄 설명
표준 라이브러리 파이썬에 기본 탑재된 모듈 모음 — import만 하면 됨
명령줄 인자 python tool.py 값에서 뒤에 붙는 값들, sys.argv로 받음
환경 변수 운영체제가 공개해 둔 설정 메모 (PATH 등)
반환 코드 명령의 성적표 — 0이면 성공
cp949 한글 윈도우 명령 출력의 문자 코드표

오늘의 문법

문법 하는 일
os.getcwd() 현재 작업 폴더
os.listdir() 폴더 안의 이름 목록
os.path.join(a, b) 운영체제에 맞게 경로 붙이기
os.path.exists(p) / getsize(p) / splitext(p) 존재 / 크기 / 확장자 분리
os.environ.get("PATH") 환경 변수 읽기
sys.argv 명령줄 인자 리스트 (0번은 스크립트 이름)
sys.exit(1) 실패 코드로 종료
subprocess.run([...], capture_output=True) 명령 실행 + 출력 받기
.decode("cp949", errors="replace") 윈도우 명령 출력 디코딩

명령어보다 중요한 감각

오늘 파이썬이 텍스트 놀이터의 담을 넘었습니다. 운영체제에게 묻고(os), 시키고(subprocess), 받아 오는 — 이제 여러분의 코드는 컴퓨터 전체를 움직일 수 있습니다. 반복되는 심부름은 코드에게 맡기는 것, 그것이 스크립트 사고방식입니다.

두 가지를 더 기억해 두세요. 첫째, subprocess로 무언가를 시키기 전에 세 가지를 먼저 적는 습관: (가) 이 명령을 터미널에서 직접 치면 되는가, (나) 출력을 받아 무엇을 할 것인가, (다) 실패하면(returncode ≠ 0) 어떻게 할 것인가. 이 설계 메모가 있으면 코드는 저절로 나옵니다. 둘째, "어떤 명령이든 실행하는 프로그램"은 강력한 만큼 공격의 표적이 됩니다. 이상한 인자를 건네받으면 명령 주입 공격의 통로가 됩니다 — 훗날 배울 그 공격의 씨앗을 오늘 여러분이 직접 만들어 본 겁니다. 이런 도구는 내 컴퓨터와 내 랩에서만 돌리세요.


전부 체크되면 Step 51 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.