(Python) ファイルツリー出力

コード
Python2、Python3 どちらでも動作します。

Download: filetree.py

#!/usr/bin/python
# coding:utf-8

#==============================
# ファイルツリーを標準出力に出力
#
# filetree.py <directory>
#==============================

import os
import sys

if len(sys.argv) < 2:
    print('usage: ' + sys.argv[0] + ' <directory>')
    sys.exit(0)

# ファイルリスト作成

flist = []
for root,dirs,files in os.walk(sys.argv[1]):
    root = os.path.relpath(root, sys.argv[1])
    if root == '.': root = ''

    flist.append([root, sorted(dirs), sorted(files)])

# ファイル名表示

def print_file(fname,lvlist,last):
    t = ''

    if len(lvlist): t += ' '

    if len(lvlist) >= 2:
        for b in lvlist[1:]:
            if b:
                t += '   '
            else:
                t += '│  '

    if len(lvlist):
        if last:
            t += '└ '
        else:
            t += '├ '

    print(t + fname)

# メイン処理

def func_main(arg,lvlist):
    root,dirs,files = arg

    dirlen = len(dirs)
    flen = len(files)

    # サブディレクトリ処理

    for i,dname in enumerate(dirs):
        nounder = (i == dirlen - 1 and flen == 0)

        print_file('<' + dname + '>', lvlist, nounder)

        # このサブディレクトリを親とするディレクトリ

        under_root = os.path.join(root, dname)
        under_list = []

        for t in flist:
            if t[0] == under_root:
                under_list.append(t)

        for j,t in enumerate(under_list):
            if nounder and j == len(under_list) - 1:
                add = [True]
            else:
                add = [False]

            func_main(t, lvlist + add)

    # ファイル処理

    for i,fname in enumerate(files):
        print_file(fname, lvlist, (i == flen - 1))

# 開始

func_main(flist.pop(0), [])
説明
指定したディレクトリのファイルリストを、罫線文字でツリー上にして、標準出力に出力します。
CD や DVD のファイルリストを作りたい時などに。
使い方
第一引数に、トップディレクトリのパスを指定します。

指定したディレクトリ内の、サブディレクトリを含むすべてのファイルが出力されます。
※ トップディレクトリの名前はリストには書き込まれません。
出力例
<dir1>
 ├ <subdir>
 │  ├ file1
 │  └ file2
 ├ file1
 ├ file2
 └ file3
<dir2>
 └ file
スクリプト解説
ファイルリストの作成
flist = []
for root,dirs,files in os.walk(sys.argv[1]):
    root = os.path.relpath(root, sys.argv[1])
    if root == '.': root = ''

    flist.append([root, sorted(dirs), sorted(files)])

os.walk() は、指定したディレクトリ下のファイルを検索して、
タプル (dirpath, dirnames, filenames) を返します。

ここでは、
root = ディレクトリのフルパス、
dirs = サブディレクトリの名前のリスト、
files = ファイル名のリスト、
となります。

root は、引数で指定したディレクトリからの相対パスにします。

サブディレクトリ名とファイル名は、sorted() で名前順にソートし、それらを flist にリストとして格納します。
開始
func_main() がファイルリストを処理する関数です。
再帰的に実行されます。

func_main(flist.pop(0), [])

第一引数は、flist から先頭のデータを取り出して渡します。
第二引数は後述します。
メイン処理
ファイルリストを出力するメインの処理です。
サブディレクトリとファイルでそれぞれ処理を分けます。

# サブディレクトリの処理

for i,dname in enumerate(dirs):
    # このサブディレクトリが、最後のアイテムかどうか。
    # サブディレクトリの最後で、かつファイルがない場合。

    nounder = (i == dirlen - 1 and flen == 0)

    # サブディレクトリ名出力

    print_file('<' + dname + '>', lvlist, nounder)

    # このサブディレクトリを親とするディレクトリを、
    # flist から検索して、under_list に追加。
    # その後、各ディレクトリを再帰的に処理。

    under_root = os.path.join(root, dname)
    under_list = []

    for t in flist:
        if t[0] == under_root:
            under_list.append(t)

    for j,t in enumerate(under_list):
        # 最後のアイテムなら、lvlist に True を追加

        if nounder and j == len(under_list) - 1:
            add = [True]
        else:
            add = [False]

        func_main(t, lvlist + add)

# ファイル一覧出力

for i,fname in enumerate(files):
    print_file(fname, lvlist, (i == flen - 1))
ファイル名の出力
def print_file(fname,lvlist,last):
    t = ''

    # 最上階層でなければ余白あり

    if len(lvlist): t += ' '

    # 2階層以上 (top/dir1/dir2...) なら、
    # その階層分の余白または罫線を左側に。
    # その階層が親の最後のアイテムなら空白に、
    # そうでなければ罫線に。

    if len(lvlist) >= 2:
        for b in lvlist[1:]:
            if b:
                t += '   '
            else:
                t += '│  '

    # 1階層以上なら、ファイル用の罫線

    if len(lvlist):
        if last:
            t += '└ '
        else:
            t += '├ '

    # 出力

    print(t + fname)

lvlist はリストです。
ディレクトリ階層が深くなるごとにリストが増えます。

また、リストの値はブール値で、True の場合は、そのディレクトリは親ディレクトリの最後のアイテムです。
この値がなぜ必要かというと、名前の左側を罫線にするか空白にするかの判定を行うのに必要な材料だからです。

以下の例を見てください。

<dir>
 ├ <dir2>
 │  └ file1
 └ file2

<dir>
 └ <dir2>
    └ file1

dir2 が dir の最後のアイテムかどうかで、file1 の左側が罫線か空白となります。
各ディレクトリ階層ごとに、それが親ディレクトリの最後のアイテムであるかどうかを知る必要があります。

last は、そのファイルまたはディレクトリが、親ディレクトリにおける最後のアイテムかどうかのブール値です。