Pythonでos.walkを使ってディレクトリを再帰的に走査する方法

Pythonのos.walkは、ディレクトリとその内容を再帰的に走査する強力なツールです。この関数を活用することで、指定したディレクトリのすべてのサブディレクトリやファイルを効率的に取得できます。本記事では、os.walkの基本的な使い方から、実用的な応用例までを網羅的に解説します。これにより、ディレクトリ操作を伴うタスクをより効率的にこなせるようになるでしょう。

目次

os.walkとは何か


os.walkは、Pythonの標準ライブラリosモジュールに含まれる関数で、指定したディレクトリを再帰的に走査し、そのディレクトリ内のファイルやサブディレクトリのリストを生成します。この関数を利用することで、複雑なディレクトリ構造を簡単に探索でき、ファイルやフォルダの一覧を取得する際に非常に便利です。

os.walkの仕組み


os.walkは、ジェネレーターとして動作し、以下の3つの要素をタプルとして返します。

  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"Current Path: {dirpath}")
    print(f"Directories: {dirnames}")
    print(f"Files: {filenames}")
    print("-" * 40)

出力例


ディレクトリ構造が以下のようになっているとします:

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

この場合、os.walkを実行すると以下のような出力が得られます:

Current Path: /path/to/your/directory
Directories: ['subdir1', 'subdir2']
Files: ['file1.txt', 'file2.txt']
----------------------------------------
Current Path: /path/to/your/directory/subdir1
Directories: []
Files: ['file3.txt']
----------------------------------------
Current Path: /path/to/your/directory/subdir2
Directories: []
Files: ['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"Directory: {subdir_path}")

    # ファイルに対する処理
    for filename in filenames:
        file_path = os.path.join(dirpath, filename)
        print(f"File: {file_path}")

出力例


ディレクトリ構造が以下のようになっている場合:

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

出力は次のようになります:

Directory: /path/to/your/directory/subdir1
Directory: /path/to/your/directory/subdir2
File: /path/to/your/directory/file1.txt
File: /path/to/your/directory/file2.txt
File: /path/to/your/directory/subdir1/file3.txt
File: /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"Found: {file_path}")

出力例


ディレクトリ構造が以下の場合:

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

出力は以下のようになります:

Found: /path/to/your/directory/file1.txt
Found: /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"Found: {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"Depth {current_depth}: {dirpath}")
    print(f"Directories: {dirnames}")
    print(f"Files: {filenames}")
    print("-" * 40)

出力例


ディレクトリ構造が以下の場合:

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

深さ2までの探索では次のような出力が得られます:

Depth 1: /path/to/your/directory
Directories: ['subdir1', 'subdir2']
Files: ['file1.txt']
----------------------------------------
Depth 2: /path/to/your/directory/subdir1
Directories: ['subsubdir1']
Files: ['file2.txt']
----------------------------------------
Depth 2: /path/to/your/directory/subdir2
Directories: []
Files: ['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"Current Path: {dirpath}")
    print(f"Directories: {dirnames}")
    print(f"Files: {visible_files}")
    print("-" * 40)

出力例


ディレクトリ構造が以下の場合:

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

隠しファイルとフォルダを無視すると次のような出力になります:

Current Path: /path/to/your/directory
Directories: ['subdir1', 'subdir2']
Files: ['file1.txt']
----------------------------------------
Current Path: /path/to/your/directory/subdir1
Directories: []
Files: ['file2.txt']
----------------------------------------
Current Path: /path/to/your/directory/subdir2
Directories: []
Files: ['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: {directory_size} bytes")
print(f"Directory Size: {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)

実行結果は次のようになります:

Directory Size: 7000 bytes  
Directory Size: 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 Files Size: {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"Copied: {source_file} -> {backup_file}")
            except Exception as e:
                print(f"Failed to copy {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の柔軟性を活かすことで、ディレクトリ操作を伴うタスクを自動化し、作業効率を大幅に向上させることができます。ぜひ実際のプロジェクトで活用してみてください!

コメント

コメントする

目次