Python으로 os.walk를 사용하여 디렉토리를 재귀적으로 탐색하는 방법

Python의os.walk는 디렉토리와 그 내용을 재귀적으로 탐색하는 강력한 도구입니다. 이 함수를 활용하면 지정한 디렉토리의 모든 하위 디렉토리와 파일을 효율적으로 가져올 수 있습니다. 본 기사에서는 os.walk의 기본적인 사용법부터 실용적인 응용 예제까지 포괄적으로 설명합니다. 이를 통해 디렉토리 작업을 포함하는 작업을 더 효율적으로 처리할 수 있을 것입니다.

os.walk란 무엇인가


os.walk는 Python의 표준 라이브러리os 모듈에 포함된 함수로, 지정한 디렉토리를 재귀적으로 탐색하고 그 디렉토리 내의 파일 및 하위 디렉토리 목록을 생성합니다. 이 함수를 사용하면 복잡한 디렉토리 구조를 쉽게 탐색할 수 있으며, 파일 및 폴더 목록을 가져오는 데 매우 유용합니다.

os.walk의 동작 원리


os.walk는 제너레이터로 동작하며, 다음 세 가지 요소를 튜플로 반환합니다.

  1. 디렉토리 경로 (dirpath)
    현재 탐색 중인 디렉토리의 경로를 나타냅니다.
  2. 하위 디렉토리 목록 (dirnames)
    현재 디렉토리 내의 하위 디렉토리 이름 목록입니다.
  3. 파일 목록 (filenames)
    현재 디렉토리 내의 파일 이름 목록입니다.

특징

  • 재귀적 탐색: 지정된 디렉토리의 하위 디렉토리를 자동으로 탐색합니다.
  • 순서: 디렉토리 계층을 상위에서 하위로 또는 하위에서 상위로 처리하는 설정이 가능합니다(topdown=True/False).
  • 효율성: 필요한 정보를 즉시 생성하여 메모리 효율이 좋습니다.

용도

  • 파일 이름 검색
  • 특정 확장자를 가진 파일 목록 작성
  • 하위 디렉토리 크기 계산
  • 백업 또는 이동 작업 자동화

기본적인 사용법

os.walk를 사용하면 지정된 디렉토리 아래의 파일 및 폴더를 쉽게 가져올 수 있습니다. 아래는 기본적인 코드 예제입니다.

코드 예제

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

# os.walk 사용하여 디렉토리 탐색
for dirpath, dirnames, filenames in os.walk(target_directory):
    print(f"현재 경로: {dirpath}")
    print(f"디렉토리: {dirnames}")
    print(f"파일: {filenames}")
    print("-" * 40)

출력 예제


디렉토리 구조가 아래와 같다고 가정합니다:

/path/to/your/directory
├── file1.txt
├── file2.txt
├── subdir1
│   └── file3.txt
└── subdir2
    └── file4.txt

이 경우, os.walk를 실행하면 다음과 같은 출력이 나옵니다:

현재 경로: /path/to/your/directory
디렉토리: ['subdir1', 'subdir2']
파일: ['file1.txt', 'file2.txt']
----------------------------------------
현재 경로: /path/to/your/directory/subdir1
디렉토리: []
파일: ['file3.txt']
----------------------------------------
현재 경로: /path/to/your/directory/subdir2
디렉토리: []
파일: ['file4.txt']
----------------------------------------

설명

  • dirpath: 현재 탐색 중인 디렉토리의 경로입니다.
  • dirnames: 현재 디렉토리 내에 존재하는 하위 디렉토리 이름 목록입니다.
  • filenames: 현재 디렉토리 내에 존재하는 파일 이름 목록입니다.

주의 사항


os.walk는 지정한 디렉토리가 존재하지 않으면 에러를 발생시키므로, 사전에 디렉토리 존재 여부를 확인하는 것이 안전합니다.

파일과 디렉토리 분기 처리

os.walk를 사용하면 디렉토리 내의 파일과 폴더를 효율적으로 분류할 수 있습니다. 각 항목에 대해 다른 처리를 적용하고 싶을 때, 분기 처리를 추가하기만 하면 쉽게 구현할 수 있습니다.

코드 예제


다음은 파일과 디렉토리를 분기하여 각각 다른 처리를 수행하는 예제입니다:

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

# 디렉토리 탐색 및 분기 처리
for dirpath, dirnames, filenames in os.walk(target_directory):
    # 하위 디렉토리에 대한 처리
    for dirname in dirnames:
        subdir_path = os.path.join(dirpath, dirname)
        print(f"디렉토리: {subdir_path}")

    # 파일에 대한 처리
    for filename in filenames:
        file_path = os.path.join(dirpath, filename)
        print(f"파일: {file_path}")

출력 예제


디렉토리 구조가 아래와 같은 경우:

/path/to/your/directory
├── file1.txt
├── file2.txt
├── subdir1
│   └── file3.txt
└── subdir2
    └── file4.txt

출력은 다음과 같습니다:

디렉토리: /path/to/your/directory/subdir1
디렉토리: /path/to/your/directory/subdir2
파일: /path/to/your/directory/file1.txt
파일: /path/to/your/directory/file2.txt
파일: /path/to/your/directory/subdir1/file3.txt
파일: /path/to/your/directory/subdir2/file4.txt

코드 설명

  • os.path.join: dirpathdirname 또는 filename을 결합하여 절대 경로를 생성합니다.
  • 디렉토리 처리 (for dirname in dirnames): 디렉토리에 대해 특정 작업(예: 디렉토리 생성 일시 가져오기)을 수행할 수 있습니다.
  • 파일 처리 (for filename in filenames): 각 파일에 대해 특정 작업(예: 파일 크기 가져오기)을 수행할 수 있습니다.

응용 예제

  • 하위 디렉토리 이름 목록을 작성하여 관리하기
  • 특정 이름 규칙을 따른 파일을 추출하여 처리하기
  • 파일 크기나 생성 일시를 기준으로 필터링 작업 수행하기

특정 확장자를 가진 파일을 검색하는 방법

os.walk를 사용하면 특정 확장자를 가진 파일을 쉽게 검색할 수 있습니다. 예를 들어 .txt.jpg와 같은 특정 형식의 파일을 효율적으로 추출할 수 있습니다.

코드 예제


다음은 .txt 확장자를 가진 파일을 검색하여 그 경로를 출력하는 코드 예제입니다:

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

# 검색할 확장자 지정
target_extension = ".txt"

# 특정 확장자를 가진 파일 검색
for dirpath, dirnames, filenames in os.walk(target_directory):
    for filename in filenames:
        if filename.endswith(target_extension):
            file_path = os.path.join(dirpath, filename)
            print(f"찾음: {file_path}")

출력 예제


디렉토리 구조가 아래와 같은 경우:

/path/to/your/directory
├── file1.txt
├── file2.doc
├── subdir1
│   └── notes.txt
└── subdir2
    └── image.png

출력은 다음과 같습니다:

찾음: /path/to/your/directory/file1.txt
찾음: /path/to/your/directory/subdir1/notes.txt

코드 설명

  • filename.endswith(target_extension): 파일 이름이 지정된 확장자로 끝날 경우 True를 반환합니다. 이를 이용해 특정 파일 형식을 필터링합니다.
  • os.path.join: 전체 경로를 생성하기 위해 사용합니다.

여러 확장자 검색하는 방법


여러 확장자를 검색하고자 할 때는 조건을 변경합니다.

# 검색할 확장자를 리스트로 지정
target_extensions = [" .txt", ".doc"]

for dirpath, dirnames, filenames in os.walk(target_directory):
    for filename in filenames:
        if filename.endswith(tuple(target_extensions)):
            file_path = os.path.join(dirpath, filename)
            print(f"찾음: {file_path}")

응용 예제

  • 프로젝트 내의 소스 코드(예: .py 파일) 목록을 가져오기
  • 특정 이미지 형식(예: .jpg.png)을 검색하여 일괄 처리하기
  • 확장자별 파일 통계 작성하기

디렉토리 깊이 제한을 둔 탐색

os.walk는 기본적으로 지정된 디렉토리 이하의 모든 계층을 재귀적으로 탐색하지만, 경우에 따라 특정 깊이까지만 탐색을 제한하고 싶을 수 있습니다. 이 경우 현재 깊이를 추적하고 처리 대상을 제한하여 해결할 수 있습니다.

코드 예제


다음은 디렉토리 깊이를 2로 제한하여 탐색하는 예제입니다:

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

# 최대 탐색 깊이 지정
max_depth = 2

# 깊이 제한을 둔 탐색
for dirpath, dirnames, filenames in os.walk(target_directory):
    # 현재 깊이 계산
    current_depth = dirpath.count(os.sep) - target_directory.count(os.sep) + 1

    if current_depth > max_depth:
        # 깊이를 초과하면 하위 디렉토리 탐색을 건너뜀
        del dirnames[:]  # dirnames를 비워 하위 디렉토리 무시
        continue

    print(f"깊이 {current_depth}: {dirpath}")
    print(f"디렉토리: {dirnames}")
    print(f"파일: {filenames}")
    print("-" * 40)

출력 예제


디렉토리 구조가 아래와 같은 경우:

/path/to/your/directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── subsubdir1
│       └── file3.txt
└── subdir2
    └── file4.txt

깊이 2까지 탐색하면 다음과 같은 출력이 나옵니다:

깊이 1: /path/to/your/directory
디렉토리: ['subdir1', 'subdir2']
파일: ['file1.txt']
----------------------------------------
깊이 2: /path/to/your/directory/subdir1
디렉토리: ['subsubdir1']
파일: ['file2.txt']
----------------------------------------
깊이 2: /path/to/your/directory/subdir2
디렉토리: []
파일: ['file4.txt']
----------------------------------------

코드 설명

  • os.sep: OS 의존적인 경로 구분 기호를 가져옵니다(Windows에서는\\, Unix에서는/).
  • dirpath.count(os.sep): 현재 디렉토리 경로에서 구분 기호의 개수를 세어 이를 기반으로 깊이를 계산합니다.
  • del dirnames[:]: 하위 디렉토리 목록을 비워서 그 이하의 탐색을 중단합니다.

응용 예제

  • 대규모 프로젝트에서 상위 디렉토리만 탐색하고자 할 때
  • 디렉토리 트리의 일부를 제한된 깊이로 표시할 때
  • 디스크 작업 부담을 줄이기 위해 제한적으로 탐색할 때

숨김 파일 및 폴더 무시 방법

디렉토리 탐색을 할 때, 숨김 파일이나 폴더(보통 이름이 점.으로 시작하는)들을 무시하고 싶을 수 있습니다. os.walk를 사용하여 숨김 요소를 필터링함으로써 효율적인 처리가 가능합니다.

코드 예제


다음은 숨김 파일과 폴더를 무시하고 탐색하는 코드 예제입니다:

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

# 숨김 파일 및 폴더를 무시하고 탐색
for dirpath, dirnames, filenames in os.walk(target_directory):
    # 숨김 폴더 무시
    dirnames[:] = [d for d in dirnames if not d.startswith(".")]

    # 숨김 파일 무시
    visible_files = [f for f in filenames if not f.startswith(".")]

    print(f"현재 경로: {dirpath}")
    print(f"디렉토리: {dirnames}")
    print(f"파일: {visible_files}")
    print("-" * 40)

출력 예제


디렉토리 구조가 다음과 같을 경우:

/path/to/your/directory
├── file1.txt
├── .hidden_file.txt
├── subdir1
│   ├── file2.txt
│   └── .hidden_folder
│       └── file3.txt
└── subdir2
    └── file4.txt

숨김 파일과 폴더를 무시하면 다음과 같은 출력이 나타납니다:

현재 경로: /path/to/your/directory
디렉토리: ['subdir1', 'subdir2']
파일: ['file1.txt']
----------------------------------------
현재 경로: /path/to/your/directory/subdir1
디렉토리: []
파일: ['file2.txt']
----------------------------------------
현재 경로: /path/to/your/directory/subdir2
디렉토리: []
파일: ['file4.txt']
----------------------------------------

코드 설명

  • 숨김 폴더 무시 (dirnames[:] = ...): dirnames를 덮어 씌워서 이름이.으로 시작하는 폴더를 제외합니다. 이 작업으로 그 이하 계층도 탐색되지 않습니다.
  • 숨김 파일 무시 ([f for f in filenames ...]): 리스트 컴프리헨션을 사용하여 이름이.으로 시작하는 파일을 제외합니다.

주의 사항

  • 파일이나 폴더가 시스템 속성으로 “숨김”으로 설정된 경우(특히 Windows), 이 코드에서는 감지되지 않을 수 있습니다. 그럴 경우 추가 모듈(예: ctypes)을 사용해야 할 수도 있습니다.

응용 예제

  • 숨김 설정이 있는 구성 파일(예: .gitignore.env)을 제외하고 처리하고자 할 때
  • 사용자에게 표시할 디렉토리 목록을 생성할 때
  • 대규모 데이터 세트 정리 시 숨김 요소를 제외하고 싶을 때

응용 예제: 디렉토리 크기 계산

os.walk를 사용하여 지정된 디렉토리 내의 모든 파일 크기를 합산하고 해당 디렉토리의 총 크기를 계산할 수 있습니다. 이 방법은 특정 폴더가 얼마나 많은 저장 공간을 차지하는지 확인하려 할 때 유용합니다.

코드 예제


다음은 디렉토리 크기를 계산하는 코드 예제입니다:

import os

# 대상 디렉토리 지정
target_directory = "/path/to/your/directory"

def calculate_directory_size(directory):
    total_size = 0
    # 디렉토리 탐색
    for dirpath, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            file_path = os.path.join(dirpath, filename)
            try:
                # 파일 크기를 가져와서 합산
                total_size += os.path.getsize(file_path)
            except FileNotFoundError:
                # 파일이 삭제된 경우 등 예외 처리
                pass
    return total_size

# 디렉토리 크기 계산
directory_size = calculate_directory_size(target_directory)

# 결과 출력 (바이트 단위, MB 단위로 표시)
print(f"디렉토리 크기: {directory_size} bytes")
print(f"디렉토리 크기: {directory_size / (1024 ** 2):.2f} MB")

출력 예제


디렉토리 구조가 아래와 같은 경우:

/path/to/your/directory
├── file1.txt (500 bytes)
├── subdir1
│   ├── file2.txt (1500 bytes)
│   └── file3.txt (3000 bytes)
└── subdir2
    └── file4.txt (2000 bytes)

실행 결과는 다음과 같습니다:

디렉토리 크기: 7000 bytes  
디렉토리 크기: 0.01 MB  

코드 설명

  • os.path.getsize(file_path): 파일 크기를 바이트 단위로 가져옵니다.
  • 예외 처리: 파일이 중간에 삭제된 경우나 접근 권한이 없는 경우를 대비해 FileNotFoundError를 처리합니다.
  • 단위 변환: 바이트 단위 크기를 사람이 읽을 수 있는 형식(KB, MB 등)으로 변환합니다.

응용: 특정 확장자의 파일 크기 계산


특정 확장자를 가진 파일의 총합 크기를 계산하려면 아래와 같이 코드를 변경합니다:

def calculate_size_by_extension(directory, extension):
    total_size = 0
    for dirpath, dirnames, filenames in os.walk(directory):
        for filename in filenames:
            if filename.endswith(extension):
                file_path = os.path.join(dirpath, filename)
                try:
                    total_size += os.path.getsize(file_path)
                except FileNotFoundError:
                    pass
    return total_size

# 예: .txt 파일의 총합 크기 계산
txt_size = calculate_size_by_extension(target_directory, ".txt")
print(f".txt 파일 크기: {txt_size} bytes")

실용성

  • 서버나 클라우드 스토리지에서 디스크 사용 상황 모니터링
  • 특정 프로젝트 폴더의 리소스 소비 파악
  • 디스크 공간 부족 시 청소 작업 보조

응용 예제: 전체 파일 백업 복사

os.walk를 사용하여 디렉토리 내의 모든 파일을 재귀적으로 탐색하고 지정한 백업 위치로 복사하여 디렉토리 전체를 백업할 수 있습니다. 이 방법은 파일 구조를 유지하면서 데이터를 안전하게 복사할 때 유용합니다.

코드 예제


다음은 파일을 백업 위치로 복사하는 코드 예제입니다:

import os
import shutil

# 원본 디렉토리와 백업 위치 지정
source_directory = "/path/to/your/source_directory"
backup_directory = "/path/to/your/backup_directory"

def backup_files(source, backup):
    for dirpath, dirnames, filenames in os.walk(source):
        # 백업 위치 경로 계산
        relative_path = os.path.relpath(dirpath, source)
        backup_path = os.path.join(backup, relative_path)

        # 백업 위치 디렉토리 생성
        os.makedirs(backup_path, exist_ok=True)

        for filename in filenames:
            source_file = os.path.join(dirpath, filename)
            backup_file = os.path.join(backup_path, filename)

            try:
                # 파일 복사
                shutil.copy2(source_file, backup_file)
                print(f"복사 완료: {source_file} -> {backup_file}")
            except Exception as e:
                print(f"복사 실패 {source_file}: {e}")

# 백업 실행
backup_files(source_directory, backup_directory)

출력 예제


디렉토리 구조가 아래와 같은 경우:

/path/to/your/source_directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── file3.txt
└── subdir2
    └── file4.txt

백업 위치 디렉토리에 다음 구조가 생성됩니다:

/path/to/your/backup_directory
├── file1.txt
├── subdir1
│   ├── file2.txt
│   └── file3.txt
└── subdir2
    └── file4.txt

코드 설명

  • os.makedirs(backup_path, exist_ok=True): 백업 위치 디렉토리를 재귀적으로 생성합니다. exist_ok=True로 설정하여 디렉토리가 이미 있더라도 오류가 발생하지 않도록 합니다.
  • os.path.relpath(dirpath, source): 원본 디렉토리로부터 상대 경로를 가져와 백업 위치 디렉토리를 구축합니다.
  • shutil.copy2(source_file, backup_file): 파일의 내용뿐만 아니라 타임스탬프와 같은 메타데이터도 복사합니다.

주의 사항

  1. 심볼릭 링크: shutil.copy2는 심볼릭 링크를 일반 파일로 복사합니다. 링크를 유지하려면 별도의 처리가 필요합니다.
  2. 디스크 용량: 백업 위치 디렉토리에 충분한 용량이 있는지 확인하세요.
  3. 접근 권한: 복사할 파일에 대한 접근 권한이 필요합니다.

응용 예제

  • 특정 확장자만 백업: if filename.endswith(".txt") 조건 추가.
  • 백업 로그 기록: 복사된 파일을 로그 파일에 기록.
  • 차분 백업: 파일의 타임스탬프나 해시 값을 비교하여 변경된 파일만 복사.

요약

본 기사에서는 Python의os.walk를 사용하여 디렉토리를 재귀적으로 탐색하는 방법과 그 응용 예제에 대해 설명했습니다. os.walk는 파일 및 폴더를 효율적으로 탐색하고 다양한 작업을 수행하는 데 유용한 강력한 도구입니다.

기본적인 사용법부터 특정 조건에 따른 필터링, 깊이 제한, 숨김 파일 제외, 디렉토리 크기 계산 및 백업 복사 구현까지 다양한 활용 예제를 소개했습니다.

os.walk의 유연성을 활용하면 디렉토리 작업을 자동화하고 작업 효율을 크게 향상시킬 수 있습니다. 실제 프로젝트에서 활용해 보세요!