在bash中可视化树,就像unix“tree”的输出一样

时间:2015-08-22 02:50:54

标签: python bash tree

给定输入:

apple: banana eggplant
banana: cantaloupe durian
eggplant:
fig:

我想将其连接成格式:

├─ apple
│  ├─ banana
│  │   ├─ cantaloupe
│  │   └─ durian
│  └─ eggplant
└─ fig

可能有也可能没有多个根元素(在上面的示例中,有两个根元素),我想找到一个解决方案来处理它们而没有问题。

是否有任何命令行工具可以处理这种转换?如果不这样,其他脚本语言中是否有任何东西可以轻松地处理这个问题(我已经看过Python的pprint但是我不确定如何将它用于这样的东西)?

3 个答案:

答案 0 :(得分:3)

以下代码将生成您要求的树结构:

branch = '├'
pipe = '|'
end = '└'
dash = '─'


class Tree(object):
    def __init__(self, tag):
        self.tag = tag


class Node(Tree):
    def __init__(self, tag, *nodes):
        super(Node, self).__init__(tag)
        self.nodes = list(nodes)


class Leaf(Tree):
    pass


def _draw_tree(tree, level, last=False, sup=[]):
    def update(left, i):
        if i < len(left):
            left[i] = '   '
        return left

    print ''.join(reduce(update, sup, ['{}  '.format(pipe)] * level)) \
          + (end if last else branch) + '{} '.format(dash) \
          + str(tree.tag)
    if isinstance(tree, Node):
        level += 1
        for node in tree.nodes[:-1]:
            _draw_tree(node, level, sup=sup)
        _draw_tree(tree.nodes[-1], level, True, [level] + sup)


def draw_tree(trees):
    for tree in trees[:-1]:
        _draw_tree(tree, 0)
    _draw_tree(trees[-1], 0, True, [0])

它要求您使用给定的表单来表示数据。

关于你的数据反序列化,你只需要跟踪父节点,这样当一个叶子看起来像一个节点时,你只需要替换它:

class Track(object):
    def __init__(self, parent, idx):
        self.parent, self.idx = parent, idx


def parser(text):
    trees = []
    tracks = {}
    for line in text.splitlines():
        line = line.strip()
        key, value = map(lambda s: s.strip(), line.split(':', 1))
        nodes = value.split()
        if len(nodes):
            parent = Node(key)
            for i, node in enumerate(nodes):
                tracks[node] = Track(parent, i)
                parent.nodes.append(Leaf(node))
            curnode = parent
            if curnode.tag in tracks:
                t = tracks[curnode.tag]
                t.parent.nodes[t.idx] = curnode
            else:
                trees.append(curnode)
        else:
            curnode = Leaf(key)
            if curnode.tag in tracks:
                # well, how you want to handle it?
                pass # ignore
            else:
                trees.append(curnode)
    return trees

它运行:

>>> text='''apple: banana eggplant
banana: cantaloupe durian
eggplant:
fig:'''
>>> draw_tree(parser(text))
├─ apple
|  ├─ banana
|  |  ├─ cantaloupe
|  |  └─ durian
|  └─ eggplant
└─ fig

希望它完全解决您的问题。

更新

我的代码对极端情况提出了一些担忧,例如:

>>> text='''apple: banana eggplant
banana: cantaloupe durian
eggplant:'''
>>> draw_tree(parser(text))
└─ apple
   ├─ banana
   |  ├─ cantaloupe
   |  └─ durian
   └─ eggplant

注意apple子节点的最左侧,最后没有|因为它们被抑制了。

或在中间空着:

>>> text='''apple: banana
banana: cantaloupe durian
eggplant:'''
>>> draw_tree(parser(text))
├─ apple
|  └─ banana
|     ├─ cantaloupe
|     └─ durian
└─ eggplant

答案 1 :(得分:1)

这是另一个版本供您参考。

  • 此版本有一个解析器,但没有非常强大的错误检查。
  • 根据@HuStmpHrrr的答案,我还会更新代码来处理“空角”。并且“在中间案例中为空”&#39;
  • 我还从stdin添加了读取文件,因此能够与bash脚本集成。您需要取消注释最后一部分。假设此脚本名为script.py,您可以调用python script.py < test.txt来读取文件。 text.txt将存储您提供的文本内容。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    import sys
    input_str = """apple: banana eggplant
    banana: cantaloupe durian
    eggplant:
    fig:
    """
    leaf_end_str = '└─ '
    leaf_inner_str = '├─ '
    child_conn_str = '│  '
    empty_str = '   '
    
    #debug = True
    debug = False
    
    def recursive_print(cur_root, p2r_list, prefix, is_end=False, only_one=False):
        # first print current node
        if only_one or is_end:
            print '%s%s%s'%(prefix, leaf_end_str, cur_root)
        else:
            print '%s%s%s'%(prefix, leaf_inner_str, cur_root)
    
        if only_one == True:
            next_prefix = prefix + empty_str
        else:
            next_prefix = prefix + child_conn_str
    
        #print p2r_list[cur_root]
        if p2r_list.has_key(cur_root):
            next_only_one = ( len(p2r_list[cur_root]) == 1 )
            for child in p2r_list[cur_root]:
                next_is_end = (child == p2r_list[cur_root][-1] )
                recursive_print(child, p2r_list, next_prefix, is_end = next_is_end, only_one = next_only_one)
    
    def tree_print(content):
        # get root and parent-children relation
        root = {} # check whether a node is root
        p2r_list = {} # store the parent-child relation
        for line in content.split('\n'):
            line = line.strip()
            if line == "":
                continue
    
            ws = line.split(':') # separate parent and child
            if not root.has_key(ws[0]):
                root[ws[0]] = True
            if not p2r_list.has_key(ws[0]):
                p2r_list[ws[0]] = []
            if len(ws) > 1:
                for child in ws[1].strip().split(' '):
                    if child == '':
                        continue
                    root[child] = False
                    p2r_list[ws[0]].append(child)
    
        if debug:
            print root, '\n', p2r_list
    
        root_list = [r for r in root.keys() if root[r]]
        for r in root_list:
            if r == root_list[-1]:
                recursive_print(r, p2r_list, '', is_end = True, only_one=True)
            else:
                recursive_print(r, p2r_list,'')
    
    if __name__ == "__main__":
        tree_print(input_str )
        """
        content = sys.stdin.read()
        #print content
        if content != '':
            tree_print( content)
        #"""
    

答案 2 :(得分:1)

这个问题很老,但这是第一个解决方案的networkx版本:

def nx_ascii_tree(graph, key=None):
    """
    Creates an printable ascii representation of a directed tree / forest.

    Args:
        graph (nx.DiGraph): each node has at most one parent (
            i.e. graph must be a directed forest)
        key (str): if specified, uses this node attribute as a label instead of
            the id

    References:
        https://stackoverflow.com/questions/32151776/visualize-tree-in-bash-like-the-output-of-unix-tree

    Example:
        >>> import networkx as nx
        >>> graph = nx.dfs_tree(nx.balanced_tree(2, 2), 0)
        >>> text = nx_ascii_tree(graph)
        >>> print(text)
        └── 0
           ├── 1
           │  ├── 3
           │  └── 4
           └── 2
              ├── 5
              └── 6
    """
    import six
    import networkx as nx
    branch = '├─'
    pipe = '│'
    end = '└─'
    dash = '─'

    assert nx.is_forest(graph)
    assert nx.is_directed(graph)

    lines = []

    def _draw_tree_nx(graph, node, level, last=False, sup=[]):
        def update(left, i):
            if i < len(left):
                left[i] = '   '
            return left

        initial = ['{}  '.format(pipe)] * level
        parts = six.moves.reduce(update, sup, initial)
        prefix = ''.join(parts)
        if key is None:
            node_label = str(node)
        else:
            node_label = str(graph.nodes[node]['label'])

        suffix = '{} '.format(dash) + node_label
        if last:
            line = prefix + end + suffix
        else:
            line = prefix + branch + suffix
        lines.append(line)

        children = list(graph.succ[node])
        if children:
            level += 1
            for node in children[:-1]:
                _draw_tree_nx(graph, node, level, sup=sup)
            _draw_tree_nx(graph, children[-1], level, True, [level] + sup)

    def draw_tree(graph):
        source_nodes = [n for n in graph.nodes if graph.in_degree[n] == 0]
        if source_nodes:
            level = 0
            for node in source_nodes[:-1]:
                _draw_tree_nx(graph, node, level, last=False, sup=[])
            _draw_tree_nx(graph, source_nodes[-1], level, last=True, sup=[0])

    draw_tree(graph)
    text = '\n'.join(lines)
    return text