Команда linux tree ограничивает количество отображаемых файлов в каждом каталоге.

Вопрос или проблема

Я хочу использовать tree (или аналог) для просмотра структуры каталога и наличия файлов в каждом подкаталоге. Итак, как я могу использовать tree, но ограничить максимальное количество файлов для отображения в данном подкаталоге?

Если это невозможно с tree, как это можно сделать, изменив код на Python с этого сайта?

Вот рабочий пример с кодом на Python, который вы упомянули:

Использование: tree.py -f [file limit] <directory>

Если для -f [file limit] указано число, то выводится ... <additional files>, а остальные файлы пропускаются. Дополнительные каталоги, однако, не должны пропускаться. Если лимит файлов установлен на 10000 (по умолчанию), это действует как отсутствие лимита

#! /usr/bin/env python
# tree.py
#
# Автор: Doug Dahms
# изменено: glallen @ StackExchange
#
# Выводит структуру дерева для пути, указанного в командной строке

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

def tree(dir, padding, print_files=False, limit=10000):
    print padding[:-1] + '+-' + basename(abspath(dir)) + "https://superuser.com/"
    padding = padding + ' '
    limit = int(limit)
    files = []
    if print_files:
        files = listdir(dir)
    else:
        files = [x for x in listdir(dir) if isdir(dir + sep + x)]
    count = 0
    for file in files:
        count += 1
        path = dir + sep + file
        if isdir(path):
            print padding + '|'
            if count == len(files):
                tree(path, padding + ' ', print_files, limit)
            else:
                tree(path, padding + '|', print_files, limit)
        else:
            if limit == 10000:
                print padding + '|'
                print padding + '+-' + file
                continue
            elif limit == 0:
                print padding + '|'
                print padding + '+-' + '... <additional files>'
                limit -= 1
            elif limit <= 0:
                continue
            else:
                print padding + '|'
                print padding + '+-' + file
                limit -= 1

def usage():
    return '''Использование: %s [-f] [file-listing-limit(int)] <PATH>
Выводит структуру дерева для указанного пути.
Опции:
-f          Выводить файлы вместе с каталогами
-f [limit]  Выводить файлы вместе с каталогами до числа limit
PATH        Обрабатываемый путь''' % basename(argv[0])

def main():
    if len(argv) == 1:
        print usage()
    elif len(argv) == 2:
        # выводить только каталоги
        path = argv[1]
        if isdir(path):
            tree(path, ' ')
        else:
            print 'ОШИБКА: \'' + path + '\' не является каталогом'
    elif len(argv) == 3 and argv[1] == '-f':
        # выводить каталоги и файлы
        path = argv[2]
        if isdir(path):
            tree(path, ' ', True)
        else:
            print 'ОШИБКА: \'' + path + '\' не является каталогом'
    elif len(argv) == 4 and argv[1] == '-f':
        # выводить каталоги и файлы до максимума
        path = argv[3]
        if isdir(path):
            tree(path, ' ', True, argv[2])
        else:
            print 'ОШИБКА: \'' + path + '\' не является каталогом'
    else:
        print usage()

if __name__ == '__main__':
    main()

Когда запускается, должно получиться подобное выходное сообщение:

user@host /usr/share/doc $ python /tmp/recipe-217212-1.py -f 2 . | head -n 40
+-doc/
  |
  +-libgnuradio-fft3.7.2.1/
  | |
  | +-copyright
  | |
  | +-changelog.Debian.gz
  |
  +-libqt4-script/
  | |
  | +-LGPL_EXCEPTION.txt
  | |
  | +-copyright
  | |
  | +-... <additional files>
  |
  +-xscreensaver-gl/
  | |
  | +-copyright
  | |
  | +-changelog.Debian.gz
  | |
  | +-... <additional files>

Можно использовать tree --filelimit=N, чтобы ограничить количество подкаталогов/файлов для отображения. К сожалению, это не откроет каталог, который имеет более чем N подкаталогов и файлов.

Для простых случаев, когда у вас несколько каталогов и большинство из них имеют слишком много (например, > 100) файлов, вы можете использовать tree --filelimit=100.

.
├── A1
│   ├── A2
│   ├── B2
│   ├── C2 [369 entries exceeds filelimit, not opening dir]
│   └── D2 [3976 entries exceeds filelimit, not opening dir]
├── B1
│   └── A2
│       ├── A3.jpeg
│       └── B3.png
└── C1.sh

Примечание, если A1/C2 имеет подкаталог A3, он не будет показан.

P.S. Это не полное решение, но будет быстрее для нескольких.

Другой подход может заключаться в фильтрации json вывода, предоставляемого tree. Например, tree -J отображает:

[
  {"type":"directory","name":"root_directory/","contents":[
    {"type":"directory","name":"child_directory","contents":[
      {"type":"file","name":"file06.txt"},
      {"type":"file","name":"file07.txt"}
    ]},
    {"type":"file","name":"file00.txt"},
    {"type":"file","name":"file01.txt"},
    {"type":"file","name":"file02.txt"},
    {"type":"file","name":"file03.txt"},
    {"type":"file","name":"file04.txt"},
    {"type":"file","name":"file05.txt"}
  ]},
  {"type":"report","directories":2,"files":8}
]

Этот json можно отфильтровать, чтобы усечь длинные списки файлов.

import json

def truncate_directories(json_data, max_files):
    # Разбираем JSON данные
    data = json.loads(json_data)

    # Проходим по каждому элементу в JSON данных
    for item in data:
        if item.get('type') == 'directory':
            contents = item.get('contents')
            if contents and len(contents) > max_files:
                # Усекаем содержимое директории
                item['contents'] = contents[:3] + [{"type": "file", "name": "..."}] + contents[-3:]

    # Преобразуем модифицированные данные обратно в JSON формат
    return json.dumps(data, indent=2)

# Пример JSON данных
json_data=""'
[
  {"type":"directory","name":"root_directory/","contents":[
    {"type":"directory","name":"child_directory","contents":[
      {"type":"file","name":"file06.txt"},
      {"type":"file","name":"file07.txt"}
    ]},
    {"type":"file","name":"file00.txt"},
    {"type":"file","name":"file01.txt"},
    {"type":"file","name":"file02.txt"},
    {"type":"file","name":"file03.txt"},
    {"type":"file","name":"file04.txt"},
    {"type":"file","name":"file05.txt"}
  ]},
  {"type":"report","directories":2,"files":8}
]
'''

# Устанавливаем максимальное количество файлов, разрешенных в каталоге
max_files = 3

# Усекать каталоги с чрезмерным количеством файлов
new_json_data = truncate_directories(json_data, max_files)

# Печать модифицированных JSON данных
print(new_json_data)

[
  {
    "type": "directory",
    "name": "root_directory/",
    "contents": [
      {
        "type": "directory",
        "name": "child_directory",
        "contents": [
          {"type": "file", "name": "file06.txt"},
          {"type": "file", "name": "file07.txt"}
        ]
      },
      {"type": "file", "name": "file00.txt"},
      {"type": "file", "name": "..."},             # УСЕЧЕННЫЕ ФАЙЛЫ
      {"type": "file", "name": "file05.txt"}
    ]
  },
  {"type": "report", "directories": 2, "files": 8}
]

В имитированном выводе “root_directory/” содержит шесть файлов вместо восьми. Средние файлы “file01.txt”, “file02.txt”, “file03.txt” и “file04.txt” были заменены {“type”: “file”, “name”: “…”} для соответствия критерию усечения.

Если этот JSON правильно выведен (например, в виде путей или файлов с табуляцией), ‘tree’ должен быть в состоянии отобразить данные пользовательского усеченного дерева.

$ tree --help

------- Вводные опции -------
-fromfile    Читает пути из файлов (.=stdin)
--fromtabfile Читает деревья из файлов с табуляцией (.=stdin)

Внесено несколько улучшений в код @glallen:

  • форматирование похоже на команду tree
  • по умолчанию используется текущий рабочий каталог
  • игнорировать файлы, начинающиеся с . по умолчанию
  • обновлено до Python 3
  • отформатирован код

Пример вывода:

tree.py -f 5
 └── bq_labeled_patents
     ├── extracted_data_EN.csv
     ├── phase_0
     │   ├── espacenet_en1.pdf
     │   ├── espacenet_en10.pdf
     │   ├── espacenet_en100.pdf
     │   ├── espacenet_en11.pdf
     │   ├── espacenet_en12.pdf
     │   ├── ... <additional files>
     ├── phase_0.json
     └── phase_1
         ├── eu
         │   ├── espacenet_en1.pdf
         │   ├── espacenet_en10.pdf
         │   ├── espacenet_en100.pdf
         │   ├── espacenet_en11.pdf
         │   ├── espacenet_en12.pdf
         │   ├── ... <additional files>
         ├── eu.json
         ├── us
         │   ├── us_001.pdf
         │   ├── us_002.pdf
         │   ├── us_003.pdf
         │   ├── us_004.pdf
         │   ├── us_005.pdf
         │   ├── ... <additional files>
         └── us.json

Обновленный код:

#! /usr/bin/env python
# tree.py
#
# Автор: Doug Dahms
# изменено: glallen @ StackExchange
# изменено: kym @ StackExchange
# https://superuser.com/q/840152/992568

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


def is_hidden(file):
    return file.startswith(".") or file == "__pycache__"


def tree(
    dir,
    padding,
    print_files=False,
    limit=10000,
    is_last=True,
    level=0,
    ignore_hidden=True,
):
    basename_dir = basename(abspath(dir))
    connector = "└── " if is_last else "├── "
    print(padding + connector + basename_dir)
    padding = padding + ("    " if is_last else "│   ")

    files = []
    if print_files:
        files = listdir(dir)
    else:
        files = [x for x in listdir(dir) if isdir(dir + sep + x)]

    if ignore_hidden:
        files = [f for f in files if not is_hidden(f)]

    files = sorted(files)
    total_files = len(files)
    file_count = 0

    for i, file in enumerate(files):
        path = dir + sep + file
        if isdir(path):
            tree(
                path,
                padding,
                print_files,
                limit,
                is_last=(i == total_files - 1),
                level=level + 1,
                ignore_hidden=ignore_hidden,
            )
        else:
            file_count += 1
            if file_count <= limit:
                connector = "└── " if i == total_files - 1 else "├── "
                print(padding + connector + file)
            elif file_count == limit + 1:
                connector = "└── " if i == total_files - 1 else "├── "
                print(padding + connector + "... <additional files>")


def main():
    parser = argparse.ArgumentParser(
        description="Выводит структуру дерева для указанного пути."
    )
    parser.add_argument(
        "path",
        nargs="?",
        default=os.getcwd(),
        help="Обрабатываемый путь (по умолчанию: текущая директория)",
    )
    parser.add_argument(
        "-f",
        "--files",
        nargs="?",
        const=10000,
        type=int,
        help="Выводить файлы вместе с каталогами до числа limit",
    )
    parser.add_argument(
        "--show-hidden",
        action="store_true",
        help="Показывать скрытые файлы (по умолчанию: скрытые файлы игнорируются)",
    )

    args = parser.parse_args()

    path = args.path
    if isdir(path):
        if args.files is not None:
            tree(path, " ", True, args.files, ignore_hidden=not args.show_hidden)
        else:
            tree(path, " ", ignore_hidden=not args.show_hidden)
    else:
        print("ОШИБКА: '" + path + "' не является каталогом")


if __name__ == "__main__":
    main()

Обновленная версия Python 3 вышеупомянутого tree.py от @glallen.

#!/usr/bin/env python3
# tree.py
#
# Автор: Doug Dahms
# изменено: glallen @ StackExchange
#
# Выводит структуру дерева для пути, указанного в командной строке

import os
import sys

def tree(directory, padding, print_files=False, limit=10000):
    print(padding[:-1] + '+-' + os.path.basename(os.path.abspath(directory)) + "https://superuser.com/")
    padding = padding + ' '
    limit = int(limit)
    files = []
    if print_files:
        files = os.listdir(directory)
    else:
        files = [x for x in os.listdir(directory) if os.path.isdir(os.path.join(directory, x))]
    count = 0
    for file in files:
        count += 1
        path = os.path.join(directory, file)
        if os.path.isdir(path):
            print(padding + '|')
            if count == len(files):
                tree(path, padding + ' ', print_files, limit)
            else:
                tree(path, padding + '|', print_files, limit)
        else:
            if limit == 10000:
                print(padding + '|')
                print(padding + '+-' + file)
                continue
            elif limit == 0:
                print(padding + '|')
                print(padding + '+-' + '... <additional files>')
                limit -= 1
            elif limit <= 0:
                continue
            else:
                print(padding + '|')
                print(padding + '+-' + file)
                limit -= 1

def usage():
    return '''Использование: {} [-f] [file-listing-limit(int)] <PATH>
Выводит структуру дерева для указанного пути.
Опции:
-f          Выводить файлы вместе с каталогами
-f [limit]  Выводить файлы вместе с каталогами до числа limit
PATH        Обрабатываемый путь'''.format(os.path.basename(sys.argv[0]))

def main():
    if len(sys.argv) == 1:
        print(usage())
    elif len(sys.argv) == 2:
        # выводить только каталоги
        path = sys.argv[1]
        if os.path.isdir(path):
            tree(path, ' ')
        else:
            print('ОШИБКА: \'' + path + '\' не является каталогом')
    elif len(sys.argv) == 3 and sys.argv[1] == '-f':
        # выводить каталоги и файлы
        path = sys.argv[2]
        if os.path.isdir(path):
            tree(path, ' ', True)
        else:
            print('ОШИБКА: \'' + path + '\' не является каталогом')
    elif len(sys.argv) == 4 and sys.argv[1] == '-f':
        # выводить каталоги и файлы до максимума
        path = sys.argv[3]
        if os.path.isdir(path):
            tree(path, ' ', True, sys.argv[2])
        else:
            print('ОШИБКА: \'' + path + '\' не является каталогом')
    else:
        print(usage())

if __name__ == '__main__':
    main()

Если кто-то пришел сюда через google, как я, можно также просто использовать awk:

tree -L 3 | awk '
BEGIN {
  last_prefix = ""
  count = 1
}
{
  # Извлекаем отступ
  prefix = $0
  sub(/[^│├└─]+$/, "", prefix)

  if (count < 5) {
    print
  }

  if (prefix == last_prefix) {
    count++
  } else {
    if (count > 5) {
      print prefix " " count " more files..."
    }
    count = 1
  }

  last_prefix = prefix
}'

Пример вывода:

├── venv-metal
│   ├── bin
│   │   ├── Activate.ps1
│   │   ├── activate
│   │   ├── activate.csh
│   │   ├── activate.fish
│   │   ├── estimator_ckpt_converter
│   │   └── 28 more files...              <<<<<<<<<<<
│   ├── include
│   │   └── python3.11
│   ├── lib
│   │   └── python3.11
│   └── pyvenv.cfg

https://gist.github.com/TTy32/4b66351dde364af54b01ca7a4dd7df02

Ответ или решение

Определение и понимание структуры файловой системы – это важный аспект для технического специалиста, работающего с операционными системами на базе Linux. Одним из популярных инструментов для отображения структуры директорий является команда tree. Эта команда предоставляет визуальное представление иерархического расположения файлов и подпапок от указанной директории. Однако у tree есть свои ограничения, когда возникает необходимость ограничить количество отображаемых файлов в каждой поддиректории, и в некоторых случаях стандартного функционала tree становится недостаточно.

Теория

Команда tree является мощным инструментом для визуализации файловой системы, позволяя быстро оценить структуру директорий. Она отображает дерево директорий и файлов, где каждая строка обозначает файл или директорию. Одним из свойств команды является ее возможность ограничивать количество отображаемых файлов с использованием параметра --filelimit. Однако, если папка превышает указанное ограничение, она просто не будет отображена полностью. В то же время, часто требуется показать часть содержимого директории, даже если общее число файлов превышает лимит.

Пример

Рассмотрим ситуацию: вы хотите просмотреть структуру директорий с ограничением на количество отображаемых файлов в каждой из них. Например, у вас есть корневая директория с несколькими поддиректориями, и в каждой из этих поддиректорий находятся сотни файлов. Используя только команду tree с параметром --filelimit, вы бы видели лишь предупреждение о превышении лимита без визуальной информации о структуре большой директории.

В Python предоставляется возможность модифицировать скрипт tree.py, чтобы обойти это ограничение. С помощью дополнительного параметра -f, который задает количество отображаемых файлов, можно управлять представлением структуры директорий, ограничивая видимость лишь первых нескольких файлов, включая некоторые ключевые файлы и свертывание оставшихся с указанием количества пропущенных файлов.

Применение

В одной из версий модифицированного скрипта на Python код был адаптирован под задачи вывода структуры с ограничением количества файлов. Например:

def tree(dir, padding, print_files=False, limit=10000):
    # Получение списка файлов и директорий
    files = sorted([x for x in listdir(dir) if isdir(os.path.join(dir, x)) or print_files])
    # Учет лимита
    for count, file in enumerate(files, start=1):
        path = os.path.join(dir, file)
        # Вывод структуры в зависимости от того, файл это или директория
        if isdir(path):
            print(padding + '+-' + file)
            tree(path, padding + '| ', print_files, limit)
        else:
            if count <= limit:
                print(padding + '+-' + file)
            elif count == limit + 1:
                print(padding + '+- ... <дополнительные файлы>')
                break

Таким образом, использовав такой скрипт или его усовершенствованную версию, можно достичь более изящного подхода к отображению структуры файловой системы. Важно заметить, что дополнительная функциональность скрипта позволяет более точно контролировать вывод информации, избегая перегрузки дисплея избыточными данными и упрощая навигацию по сложной файловой системе.

Заключение

Модификация и адаптация инструментов под конкретные нужды – одна из ключевых задач IT-специалиста. В ситуации с tree, возможно, стандартной функциональности недостаточно, и на помощь приходит программирование с использованием языков вроде Python, что позволяет делать вывод более осмысленным и информативным, фильтруя избыточные данные. Такой подход способствует повышению производительности работы и улучшению опыта взаимодействия с системой.

Оцените материал
Добавить комментарий

Капча загружается...