Pythonでファイル階層をツリー表示するプログラムを作成したので解説

プログラミング

はじめに

ファイル管理やディレクトリ構造の把握は、プログラミングやシステム管理において重要な作業である。
そこで私は、Pythonで、指定したディレクトリ内のファイルやフォルダの階層を特定のボックス描画文字を用いて一覧表示し、その結果をファイルに出力するプログラムを作成した。

本記事では、その作成手順を詳しく解説する。

スポンサーリンク
スポンサーリンク

プログラムの要件

以下の要件を満たすPythonプログラムを作成する。

  1. ディレクトリの探索:

– 指定したディレクトリ内のファイルとフォルダを再帰的に探索する。
– フォルダはソートされ、ファイルとフォルダが分けて表示される。

  1. ツリー構造の描画:

– 指定されたボックス描画文字を使用して、階層を視覚的に表現する。
– 再帰的な関数を用いて各階層を処理し、適切なインデントと接続線を描画する。

  1. 出力のファイル保存:

– スクリプト実行ディレクトリ内の data フォルダに fileDirList.txt という名称で結果をファイル出力する。
data フォルダが存在しない場合は自動的に作成する。

  1. コマンドライン引数の利用:

– ディレクトリパスをコマンドライン引数として受け取り、柔軟に指定できるようにする。

ステップバイステップの実装

1. 基本的なディレクトリツリー表示の作成

まず、指定したディレクトリのファイル・フォルダの階層を表示する基本的なスクリプトを作成する。
ここでは、以下のボックス描画文字を使用してツリー構造を表現する。

┣ ┠ ┝ ├
┫ ┨ ┥ ┤
│ ┃
─ ━
┌ ┏ ┓ ┐
└ ┗ ┛ ┘

コード例

import os
import sys

# 使用するボックス描画文字の定義
BRANCH_CHAR = '┣'
LAST_BRANCH_CHAR = '┗'
VERTICAL_LINE = '┃'
SPACE = ' '
BRANCH_LINE = ' ┣━ '
LAST_BRANCH_LINE = ' ┗━ '

def print_tree(dir_path, prefix=''):
# ディレクトリ内のエントリを取得し、ソートする
try:
entries = sorted(os.listdir(dir_path))
except PermissionError:
print(prefix + "アクセス拒否")
return

# フォルダとファイルを分ける
folders = [e for e in entries if os.path.isdir(os.path.join(dir_path, e))]
files = [e for e in entries if not os.path.isdir(os.path.join(dir_path, e))]
entries = folders + files # フォルダを先に表示

for index, entry in enumerate(entries):
path = os.path.join(dir_path, entry)
is_last = index == len(entries) - 1
connector = LAST_BRANCH_CHAR if is_last else BRANCH_CHAR
line = LAST_BRANCH_LINE if is_last else BRANCH_LINE
print(prefix + connector + line + entry)

if os.path.isdir(path):
extension = SPACE if is_last else VERTICAL_LINE + ' '
print_tree(path, prefix + extension)

def main():
if len(sys.argv) != 2:
print("使用方法: python tree.py <ディレクトリパス>")
sys.exit(1)

dir_path = sys.argv[1]

if not os.path.exists(dir_path):
print("指定されたパスが存在しません。")
sys.exit(1)

print(dir_path)
print_tree(dir_path)

if __name__ == "__main__":
main()

2. 出力をファイルに保存する機能の追加

次に、ツリー構造の出力をコンソールではなく、ファイルに保存する機能を追加する。
スクリプト実行ディレクトリに data フォルダを作成し、その中に fileDirList.txt ファイルを生成する。

修正点

  • 標準出力(print)からファイルへの書き込みに変更。
  • data フォルダの存在チェックと自動作成。
  • 出力ファイルのパスを指定し、ツリー構造をファイルに書き込む。

修正後のコード例

import os
import sys

# 使用するボックス描画文字の定義
BRANCH_CHAR = '┣'
LAST_BRANCH_CHAR = '┗'
VERTICAL_LINE = '┃'
SPACE = ' '
BRANCH_LINE = ' ┣━ '
LAST_BRANCH_LINE = ' ┗━ '

def print_tree(dir_path, prefix='', file_handle=None):
# ディレクトリ内のエントリを取得し、ソートする
try:
entries = sorted(os.listdir(dir_path))
except PermissionError:
file_handle.write(prefix + "アクセス拒否\n")
return

# フォルダとファイルを分ける
folders = [e for e in entries if os.path.isdir(os.path.join(dir_path, e))]
files = [e for e in entries if not os.path.isdir(os.path.join(dir_path, e))]
entries = folders + files # フォルダを先に表示

for index, entry in enumerate(entries):
path = os.path.join(dir_path, entry)
is_last = index == len(entries) - 1
connector = LAST_BRANCH_CHAR if is_last else BRANCH_CHAR
line = LAST_BRANCH_LINE if is_last else BRANCH_LINE
file_handle.write(prefix + connector + line + entry + "\n")

if os.path.isdir(path):
extension = SPACE if is_last else VERTICAL_LINE + ' '
print_tree(path, prefix + extension, file_handle)

def main():
if len(sys.argv) != 2:
print("使用方法: python tree.py <ディレクトリパス>")
sys.exit(1)

dir_path = sys.argv[1]

if not os.path.exists(dir_path):
print("指定されたパスが存在しません。")
sys.exit(1)

# スクリプト実行ディレクトリの取得
script_dir = os.path.dirname(os.path.abspath(__file__))

# dataフォルダのパス
data_dir = os.path.join(script_dir, 'data')

# dataフォルダが存在しない場合は作成
os.makedirs(data_dir, exist_ok=True)

# 出力ファイルのパス
output_file = os.path.join(data_dir, 'fileDirList.txt')

with open(output_file, 'w', encoding='utf-8') as f:
f.write(dir_path + "\n")
print_tree(dir_path, '', f)

print(f"ディレクトリ構造が '{output_file}' に出力されました。")

if __name__ == "__main__":
main()

3. コマンドライン引数を使用したディレクトリ指定

最後に、コマンドライン引数をより柔軟に扱えるように argparse モジュールを導入する。
これにより、ユーザーはスクリプト実行時に表示したいディレクトリパスを簡単に指定でき、ヘルプメッセージも自動生成される。

修正点

  • argparse モジュールの導入。
  • ヘルプメッセージの追加。
  • エラーハンドリングの強化。

修正後のコード例

import os
import sys
import argparse

# 使用するボックス描画文字の定義
BRANCH_CHAR = '┣'
LAST_BRANCH_CHAR = '┗'
VERTICAL_LINE = '┃'
SPACE = ' '
BRANCH_LINE = ' ┣━ '
LAST_BRANCH_LINE = ' ┗━ '

def print_tree(dir_path, prefix='', file_handle=None):
"""
指定されたディレクトリのツリー構造をファイルに書き込む再帰関数。

Args:
dir_path (str): 探索するディレクトリのパス。
prefix (str): 階層ごとのインデント。
file_handle (file object): 書き込み先のファイルオブジェクト。
"""
# ディレクトリ内のエントリを取得し、ソートする
try:
entries = sorted(os.listdir(dir_path))
except PermissionError:
file_handle.write(prefix + "アクセス拒否\n")
return
except FileNotFoundError:
file_handle.write(prefix + "ディレクトリが見つかりません\n")
return

# フォルダとファイルを分ける
folders = [e for e in entries if os.path.isdir(os.path.join(dir_path, e))]
files = [e for e in entries if not os.path.isdir(os.path.join(dir_path, e))]
entries = folders + files # フォルダを先に表示

for index, entry in enumerate(entries):
path = os.path.join(dir_path, entry)
is_last = index == len(entries) - 1
connector = LAST_BRANCH_CHAR if is_last else BRANCH_CHAR
line = LAST_BRANCH_LINE if is_last else BRANCH_LINE
file_handle.write(prefix + connector + line + entry + "\n")

if os.path.isdir(path):
extension = SPACE if is_last else VERTICAL_LINE + ' '
print_tree(path, prefix + extension, file_handle)

def main():
# argparseを使用してコマンドライン引数を処理
parser = argparse.ArgumentParser(
description="指定したディレクトリのファイル・フォルダの階層を一覧表示し、data/fileDirList.txtに出力します。"
)
parser.add_argument(
"directory",
metavar="ディレクトリパス",
type=str,
help="表示したいディレクトリのパスを指定してください。"
)
args = parser.parse_args()

dir_path = args.directory

if not os.path.exists(dir_path):
print("指定されたパスが存在しません。")
sys.exit(1)

if not os.path.isdir(dir_path):
print("指定されたパスはディレクトリではありません。")
sys.exit(1)

# スクリプト実行ディレクトリの取得
script_dir = os.path.dirname(os.path.abspath(__file__))

# dataフォルダのパス
data_dir = os.path.join(script_dir, 'data')

# dataフォルダが存在しない場合は作成
os.makedirs(data_dir, exist_ok=True)

# 出力ファイルのパス
output_file = os.path.join(data_dir, 'fileDirList.txt')

try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(dir_path + "\n")
print_tree(dir_path, '', f)
print(f"ディレクトリ構造が '{output_file}' に出力されました。")
except IOError as e:
print(f"ファイルの書き込み中にエラーが発生しました: {e}")
sys.exit(1)

if __name__ == "__main__":
main()

完成したプログラムのコード

以上のステップを踏まえた最終的なプログラムは以下の通りである。

import os
import sys
import argparse

# 使用するボックス描画文字の定義
BRANCH_CHAR = '┣'
LAST_BRANCH_CHAR = '┗'
VERTICAL_LINE = '┃'
SPACE = ' '
BRANCH_LINE = ' ┣━ '
LAST_BRANCH_LINE = ' ┗━ '

def print_tree(dir_path, prefix='', file_handle=None):
"""
指定されたディレクトリのツリー構造をファイルに書き込む再帰関数。

Args:
dir_path (str): 探索するディレクトリのパス。
prefix (str): 階層ごとのインデント。
file_handle (file object): 書き込み先のファイルオブジェクト。
"""
# ディレクトリ内のエントリを取得し、ソートする
try:
entries = sorted(os.listdir(dir_path))
except PermissionError:
file_handle.write(prefix + "アクセス拒否\n")
return
except FileNotFoundError:
file_handle.write(prefix + "ディレクトリが見つかりません\n")
return

# フォルダとファイルを分ける
folders = [e for e in entries if os.path.isdir(os.path.join(dir_path, e))]
files = [e for e in entries if not os.path.isdir(os.path.join(dir_path, e))]
entries = folders + files # フォルダを先に表示

for index, entry in enumerate(entries):
path = os.path.join(dir_path, entry)
is_last = index == len(entries) - 1
connector = LAST_BRANCH_CHAR if is_last else BRANCH_CHAR
line = LAST_BRANCH_LINE if is_last else BRANCH_LINE
file_handle.write(prefix + connector + line + entry + "\n")

if os.path.isdir(path):
extension = SPACE if is_last else VERTICAL_LINE + ' '
print_tree(path, prefix + extension, file_handle)

def main():
# argparseを使用してコマンドライン引数を処理
parser = argparse.ArgumentParser(
description="指定したディレクトリのファイル・フォルダの階層を一覧表示し、data/fileDirList.txtに出力します。"
)
parser.add_argument(
"directory",
metavar="ディレクトリパス",
type=str,
help="表示したいディレクトリのパスを指定してください。"
)
args = parser.parse_args()

dir_path = args.directory

if not os.path.exists(dir_path):
print("指定されたパスが存在しません。")
sys.exit(1)

if not os.path.isdir(dir_path):
print("指定されたパスはディレクトリではありません。")
sys.exit(1)

# スクリプト実行ディレクトリの取得
script_dir = os.path.dirname(os.path.abspath(__file__))

# dataフォルダのパス
data_dir = os.path.join(script_dir, 'data')

# dataフォルダが存在しない場合は作成
os.makedirs(data_dir, exist_ok=True)

# 出力ファイルのパス
output_file = os.path.join(data_dir, 'fileDirList.txt')

try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(dir_path + "\n")
print_tree(dir_path, '', f)
print(f"ディレクトリ構造が '{output_file}' に出力されました。")
except IOError as e:
print(f"ファイルの書き込み中にエラーが発生しました: {e}")
sys.exit(1)

if __name__ == "__main__":
main()

プログラムの使い方

1. スクリプトの保存

上記のコードを tree.py という名前で保存する。

2. 実行

コマンドラインから以下のように実行する。

python tree.py <ディレクトリパス>
  • &lt;ディレクトリパス&gt; を表示したいディレクトリのパスに置き換える。

python tree.py /home/user/Documents

3. ヘルプメッセージの表示

-h または --help オプションを使用して、スクリプトの使い方を確認できる。

python tree.py --help

出力例:

usage: tree.py [-h] ディレクトリパス

指定したディレクトリのファイル・フォルダの階層を一覧表示し、data/fileDirList.txtに出力します。

positional arguments:
ディレクトリパス 表示したいディレクトリのパスを指定してください。

optional arguments:
-h, --help show this help message and exit

4. 出力確認

スクリプトが実行されたディレクトリ内に data フォルダが作成され、その中に fileDirList.txt が生成される。fileDirList.txt を開いて、ディレクトリ構造が正しく出力されていることを確認する。

サンプル実行と出力結果

コマンドの実行例

python tree.py /path/to/directory

fileDirList.txt のサンプル出力

/path/to/directory
┣━ Folder1
┃ ┣━ Subfolder1
┃ ┗━ Subfolder2
┣━ Folder2
┃ ┣━ Subfolder3
┃ ┗━ Subfolder4
┗━ file1.txt
┣━ file2.txt
┗━ file3.txt

カスタマイズと拡張

使用するボックス描画文字の変更

プログラム冒頭で定義しているボックス描画文字(BRANCH_CHAR など)を変更することで、表示スタイルをカスタマイズできる。

BRANCH_CHAR = '├'
LAST_BRANCH_CHAR = '└'
VERTICAL_LINE = '│'
# 他の文字も同様に変更可能

ソート順の変更

現在、フォルダが先に表示され、次にファイルが表示されるようにソートされている。必要に応じて並べ替えロジックを変更できる。

entries = files + folders # ファイルを先に表示

追加情報の表示

ファイルサイズや最終更新日時など、追加の情報を表示したい場合は、print_tree 関数内で os.path モジュールの関数を使用して取得し、ファイルに書き込むように拡張できる。

size = os.path.getsize(path)
file_handle.write(prefix + connector + line + f"{entry} ({size} bytes)" + "\n")

まとめ

本記事では、Pythonを使用して指定したディレクトリ内のファイル・フォルダの階層をボックス描画文字を用いて可視化し、その結果をファイルに出力するプログラムの作成方法を紹介した。
argparse モジュールを活用することで、コマンドライン引数からディレクトリパスを柔軟に指定できるようになり、data フォルダへの出力機能を追加することで、後から簡単に結果を確認できるようになった。

コメント