在python中列出目录树结构?

List directory tree structure in python?

我知道我们可以使用os.walk()列出一个目录中的所有子目录或所有文件。但是,我想列出完整的目录树内容:

  • 子目录1:
    • 文件11
    • 文件12
    • 子目录11:
      • 文件111
      • 文件112
  • 子目录2:
    • 文件21
    • 子目录21
    • 子目录22
      • 子子目录221
        • 文件2211

如何在Python中最好地实现这一点?


下面是一个函数,用于格式化:

1
2
3
4
5
6
7
8
9
10
import os

def list_files(startpath):
    for root, dirs, files in os.walk(startpath):
        level = root.replace(startpath, '').count(os.sep)
        indent = ' ' * 4 * (level)
        print('{}{}/'.format(indent, os.path.basename(root)))
        subindent = ' ' * 4 * (level + 1)
        for f in files:
            print('{}{}'.format(subindent, f))


没有缩进的解决方案:

1
2
3
4
for path, dirs, files in os.walk(given_path):
  print path
  for f in files:
    print f

os.walk已经完成了自上而下的深度优先行走。

忽略dirs列表可防止您提到的重叠。


我来这里是为了找同样的东西,我用了DHOBS的答案。作为感谢社区的一种方式,我添加了一些参数来写入一个文件,正如Akshay所要求的那样,并使显示文件成为可选的,因此它不是一个输出。还使缩进成为一个可选参数,这样您就可以更改它,因为有些人喜欢它是2,而另一些人喜欢它是4。

使用了不同的循环,因此不显示文件的循环不会在每次迭代中检查是否必须这样做。

希望它能帮助其他人,因为dhobbs的回答帮助了我。谢谢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def showFolderTree(path,show_files=False,indentation=2,file_output=False):
"""
Shows the content of a folder in a tree structure.
path -(string)- path of the root folder we want to show.
show_files -(boolean)-  Whether or not we want to see files listed.
                        Defaults to False.
indentation -(int)- Indentation we want to use, defaults to 2.  
file_output -(string)-  Path (including the name) of the file where we want
                        to save the tree.
"""



tree = []

if not show_files:
    for root, dirs, files in os.walk(path):
        level = root.replace(path, '').count(os.sep)
        indent = ' '*indentation*(level)
        tree.append('{}{}/'.format(indent,os.path.basename(root)))

if show_files:
    for root, dirs, files in os.walk(path):
        level = root.replace(path, '').count(os.sep)
        indent = ' '*indentation*(level)
        tree.append('{}{}/'.format(indent,os.path.basename(root)))    
        for f in files:
            subindent=' ' * indentation * (level+1)
            tree.append('{}{}'.format(subindent,f))

if file_output:
    output_file = open(file_output,'w')
    for line in tree:
        output_file.write(line)
        output_file.write('
'
)
else:
    # Default behaviour: print on screen.
    for line in tree:
        print line


与上面的答案类似,但是对于python3,可以说是可读的,可以说是可扩展的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
from pathlib import Path

class DisplayablePath(object):
    display_filename_prefix_middle = '├──'
    display_filename_prefix_last = '└──'
    display_parent_prefix_middle = '    '
    display_parent_prefix_last = '│   '

    def __init__(self, path, parent_path, is_last):
        self.path = Path(str(path))
        self.parent = parent_path
        self.is_last = is_last
        if self.parent:
            self.depth = self.parent.depth + 1
        else:
            self.depth = 0

    @property
    def displayname(self):
        if self.path.is_dir():
            return self.path.name + '/'
        return self.path.name

    @classmethod
    def make_tree(cls, root, parent=None, is_last=False, criteria=None):
        root = Path(str(root))
        criteria = criteria or cls._default_criteria

        displayable_root = cls(root, parent, is_last)
        yield displayable_root

        children = sorted(list(path
                               for path in root.iterdir()
                               if criteria(path)),
                          key=lambda s: str(s).lower())
        count = 1
        for path in children:
            is_last = count == len(children)
            if path.is_dir():
                yield from cls.make_tree(path,
                                         parent=displayable_root,
                                         is_last=is_last,
                                         criteria=criteria)
            else:
                yield cls(path, displayable_root, is_last)
            count += 1

    @classmethod
    def _default_criteria(cls, path):
        return True

    @property
    def displayname(self):
        if self.path.is_dir():
            return self.path.name + '/'
        return self.path.name

    def displayable(self):
        if self.parent is None:
            return self.displayname

        _filename_prefix = (self.display_filename_prefix_last
                            if self.is_last
                            else self.display_filename_prefix_middle)

        parts = ['{!s} {!s}'.format(_filename_prefix,
                                    self.displayname)]

        parent = self.parent
        while parent and parent.parent is not None:
            parts.append(self.display_parent_prefix_middle
                         if parent.is_last
                         else self.display_parent_prefix_last)
            parent = parent.parent

        return ''.join(reversed(parts))

示例用法:

1
2
3
paths = DisplayablePath.make_tree(Path('doc'))
for path in paths:
    print(path.displayable())

实例输出:

1
2
3
4
5
6
7
8
9
10
11
12
doc/
├── _static/
│   ├── embedded/
│   │   ├── deep_file
│   │   └── very/
│   │       └── deep/
│   │           └── folder/
│   │               └── very_deep_file
│   └── less_deep_file
├── about.rst
├── conf.py
└── index.rst

笔记

  • 这使用递归。它将在非常深的文件夹树上引发递归错误
  • 这棵树评价得很慢。它应该在非常宽的文件夹树上表现良好。但是,给定文件夹的直接子级不会被延迟计算。

编辑:

  • 增加奖金!筛选路径的条件回调。


基于这篇精彩的文章

http://code.activestate.com/recipes/217212-treepy-graphically-displays-the-directory-structur/

这里有一种优雅的举止

http://linux.die.net/man/1/tree/树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/usr/bin/env python2
# -*- coding: utf-8 -*-

# tree.py
#
# Written by Doug Dahms
#
# Prints the tree structure for the path specified on the command line

from os import listdir, sep
from os.path import abspath, basename, isdir
from sys import argv

def tree(dir, padding, print_files=False, isLast=False, isFirst=False):
    if isFirst:
        print padding.decode('utf8')[:-1].encode('utf8') + dir
    else:
        if isLast:
            print padding.decode('utf8')[:-1].encode('utf8') + '└── ' + basename(abspath(dir))
        else:
            print padding.decode('utf8')[:-1].encode('utf8') + '├── ' + basename(abspath(dir))
    files = []
    if print_files:
        files = listdir(dir)
    else:
        files = [x for x in listdir(dir) if isdir(dir + sep + x)]
    if not isFirst:
        padding = padding + '   '
    files = sorted(files, key=lambda s: s.lower())
    count = 0
    last = len(files) - 1
    for i, file in enumerate(files):
        count += 1
        path = dir + sep + file
        isLast = i == last
        if isdir(path):
            if count == len(files):
                if isFirst:
                    tree(path, padding, print_files, isLast, False)
                else:
                    tree(path, padding + ' ', print_files, isLast, False)
            else:
                tree(path, padding + '│', print_files, isLast, False)
        else:
            if isLast:
                print padding + '└── ' + file
            else:
                print padding + '├── ' + file

def usage():
    return '''Usage: %s [-f]
Print tree structure of path specified.
Options:
-f      Print files as well as directories
PATH    Path to process'''
% basename(argv[0])

def main():
    if len(argv) == 1:
        print usage()
    elif len(argv) == 2:
        # print just directories
        path = argv[1]
        if isdir(path):
            tree(path, '', False, False, True)
        else:
            print 'ERROR: \'' + path + '\' is not a directory'
    elif len(argv) == 3 and argv[1] == '-f':
        # print directories and files
        path = argv[2]
        if isdir(path):
            tree(path, '', True, False, True)
        else:
            print 'ERROR: \'' + path + '\' is not a directory'
    else:
        print usage()

if __name__ == '__main__':
    main()

1
2
3
4
5
6
7
8
import os

def fs_tree_to_dict(path_):
    file_token = ''
    for root, dirs, files in os.walk(path_):
        tree = {d: fs_tree_to_dict(os.path.join(root, d)) for d in dirs}
        tree.update({f: file_token for f in files})
        return tree  # note we discontinue iteration trough os.walk

如果有人感兴趣,那么递归函数返回字典的嵌套结构。键是(目录和文件的)file system名称,值可以是:

  • 目录的子词典
  • 文件字符串(见file_token)

在此示例中,指定文件的字符串为空。它们也可以是给定的文件内容或其所有者信息或特权,或者是与dict不同的任何对象。除非它是字典,否则在进一步的操作中可以很容易地与"目录类型"区分开来。

在文件系统中有这样一棵树:

1
2
3
4
5
6
7
8
9
10
11
12
13
# bash:
$ tree /tmp/ex
/tmp/ex
├── d_a
│   ├── d_a_a
│   ├── d_a_b
│   │   └── f1.txt
│   ├── d_a_c
│   └── fa.txt
├── d_b
│   ├── fb1.txt
│   └── fb2.txt
└── d_c

结果将是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# python 2 or 3:
>>> fs_tree_to_dict("/tmp/ex")
{
    'd_a': {
        'd_a_a': {},
        'd_a_b': {
            'f1.txt': ''
        },
        'd_a_c': {},
        'fa.txt': ''
    },
    'd_b': {
        'fb1.txt': '',
        'fb2.txt': ''
    },
    'd_c': {}
}

如果您喜欢的话,我已经创建了一个包(python 2&3)和这个包(以及一个不错的pyfakefs助手):https://pypi.org/project/fsforge/


您可以执行Linuxshell的"tree"命令。

安装:

1
   ~$sudo apt install tree

在Python中使用

1
2
    >>> import os
    >>> os.system('tree <desired path>')

例子:

1
    >>> os.system('tree ~/Desktop/myproject')

这给了你一个更干净的结构,视觉上更全面,更容易打字。


只有在系统上安装了tree后,此解决方案才能工作。不过,我将把这个解决方案留在这里,以防它帮助其他人。

您可以告诉tree将树结构输出为xml(tree -X或json(tree -J)。当然,JSON可以用python直接解析,XML可以很容易地用lxml读取。

以下面的目录结构为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[sri@localhost Projects]$ tree --charset=ascii bands
bands
|-- DreamTroll
|   |-- MattBaldwinson
|   |-- members.txt
|   |-- PaulCarter
|   |-- SimonBlakelock
|   `-- Rob Stringer
|-- KingsX
|   |-- DougPinnick
|   |-- JerryGaskill
|   |-- members.txt
|   `-- TyTabor
|-- Megadeth
|   |-- DaveMustaine
|   |-- DavidEllefson
|   |-- DirkVerbeuren
|   |-- KikoLoureiro
|   `-- members.txt
|-- Nightwish
|   |-- EmppuVuorinen
|   |-- FloorJansen
|   |-- JukkaNevalainen
|   |-- MarcoHietala
|   |-- members.txt
|   |-- TroyDonockley
|   `-- TuomasHolopainen
`-- Rush
    |-- AlexLifeson
    |-- GeddyLee
    `-- NeilPeart

5 directories, 25 files

XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<tree>
  <directory name="bands">
    <directory name="DreamTroll">
      <file name="MattBaldwinson"></file>
      <file name="members.txt"></file>
      <file name="PaulCarter"></file>
      <file name="RobStringer"></file>
      <file name="SimonBlakelock"></file>
    </directory>
    <directory name="KingsX">
      <file name="DougPinnick"></file>
      <file name="JerryGaskill"></file>
      <file name="members.txt"></file>
      <file name="TyTabor"></file>
    </directory>
    <directory name="Megadeth">
      <file name="DaveMustaine"></file>
      <file name="DavidEllefson"></file>
      <file name="DirkVerbeuren"></file>
      <file name="KikoLoureiro"></file>
      <file name="members.txt"></file>
    </directory>
    <directory name="Nightwish">
      <file name="EmppuVuorinen"></file>
      <file name="FloorJansen"></file>
      <file name="JukkaNevalainen"></file>
      <file name="MarcoHietala"></file>
      <file name="members.txt"></file>
      <file name="TroyDonockley"></file>
      <file name="TuomasHolopainen"></file>
    </directory>
    <directory name="Rush">
      <file name="AlexLifeson"></file>
      <file name="GeddyLee"></file>
      <file name="NeilPeart"></file>
    </directory>
  </directory>
  <report>
    <directories>5</directories>
    <files>25</files>
  </report>
</tree>

杰森

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[sri@localhost projects]$tree-j波段["type""directory""name""bands""contents":。["type""directory""name""dreamtroll""contents":。["type""file""name""mattbaldwinson<hr><P>也许比@ellockie更快(也许)</P>[cc lang="python"]
import os
def file_writer(text):
    with open("
folder_structure.txt","a") as f_output:
        f_output.write(text)
def list_files(startpath):


    for root, dirs, files in os.walk(startpath):
        level = root.replace(startpath, '').count(os.sep)
        indent = '\t' * 1 * (level)
        output_string = '{}{}/
'.format(indent, os.path.basename(root))
        file_writer(output_string)
        subindent = '\t' * 1 * (level + 1)
        output_string = '%s %s
' %(subindent,[f for f in files])
        file_writer(''.join(output_string))


list_files("
/")

以下屏幕截图中的测试结果:

enter image description here


除了上面的dhobbs答案(https://stackoverflow.com/a/9728478/624597),这里还有一个将结果存储到文件中的额外功能(我个人使用它来复制和粘贴到freemind,以便对结构有一个很好的概述,因此我使用了制表符而不是缩进的空格):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os

def list_files(startpath):

    with open("folder_structure.txt","w") as f_output:
        for root, dirs, files in os.walk(startpath):
            level = root.replace(startpath, '').count(os.sep)
            indent = '\t' * 1 * (level)
            output_string = '{}{}/'.format(indent, os.path.basename(root))
            print(output_string)
            f_output.write(output_string + '
'
)
            subindent = '\t' * 1 * (level + 1)
            for f in files:
                output_string = '{}{}'.format(subindent, f)
                print(output_string)
                f_output.write(output_string + '
'
)

list_files(".")