#!/bin/bash # Цвета как в zsh COLOR_RESET='\033[0m' COLOR_WHITE='\033[37m' # обычные файлы COLOR_GREEN='\033[32m' # исполняемые (+x) COLOR_CYAN='\033[36m' # аудио/видео COLOR_RED='\033[31m' # архивы, бинарники COLOR_YELLOW='\033[33m' # директории # Типы аудио/видео AUDIO_VIDEO_EXTS="mp4|mkv|avi|mov|flv|wmv|webm|m4v|3gp|ogv|ts|m2ts|mts|mp3|wav|flac|aac|ogg|wma|opus|m4a|alac|aiff" # Дефолтные значения DIRECTORY="." RECURSIVE=0 MAX_DEPTH=0 NO_COUNT=0 NO_NESTED_COUNT=0 SORT_MODE="none" declare -a EXCLUDE_PATTERNS=() declare -a EXCLUDE_EXACT=() declare -a TABLE_DATA=() # Help show_help() { cat << 'EOF' Usage: script [OPTIONS] [DIRECTORY] Options: -h, --help Показать эту справку -r, --recursive Рекурсивно обойти все папки -d, --depth LEVEL Глубина рекурсии (используется с -r) --no-count Не считать объекты внутри папок --no-nested-count Не показывать кол-во вложенных файлов/папок -e, --exclude PATTERN Исключить файлы/папки (можно использовать несколько раз) Поддерживает точные названия и паттерны (*, ?) -s, --sort MODE Сортировка: az|za|up|down az - по имени А→Я za - по имени Я→А up - по размеру ↑ (меньше→больше) down - по размеру ↓ (больше→меньше) Examples: script . script -r /path/to/dir script -r -d 3 -e venv -e __pycache__ . script -s up /path script -e "*.pyc" -e "node_modules" . EOF } # Определение типа файла и цвета get_color() { local file="$1" # Если это директория if [[ -d "$file" ]]; then echo -e "${COLOR_YELLOW}" return fi # Если исполняемый файл if [[ -x "$file" && ! -d "$file" ]]; then echo -e "${COLOR_GREEN}" return fi # Проверяем расширение для аудио/видео local ext="${file##*.}" if [[ "$ext" =~ ^($AUDIO_VIDEO_EXTS)$ ]]; then echo -e "${COLOR_CYAN}" return fi # Для архивов и бинарников используем 'file' local file_type=$(file -b "$file" 2>/dev/null) if [[ "$file_type" =~ (archive|compressed|executable|ELF|PE32) ]]; then echo -e "${COLOR_RED}" return fi # По умолчанию белый для текста echo -e "${COLOR_WHITE}" } # Проверка: нужно ли исключить файл should_exclude() { local name="$1" # Проверяем точные названия for pattern in "${EXCLUDE_EXACT[@]}"; do if [[ "$name" == "$pattern" ]]; then return 0 fi done # Проверяем паттерны for pattern in "${EXCLUDE_PATTERNS[@]}"; do if [[ "$name" == $pattern ]]; then return 0 fi done return 1 } # Подсчет объектов внутри папки count_objects() { local dir="$1" local count=0 if [[ $NO_COUNT -eq 1 || $NO_NESTED_COUNT -eq 1 ]]; then return fi # Считаем только прямых потомков for item in "$dir"/*; do [[ ! -e "$item" ]] && break local base_name=$(basename "$item") if ! should_exclude "$base_name"; then ((count++)) fi done if [[ $count -gt 0 ]]; then echo "$count" else echo "" fi } # Основная функция вывода process_directory() { local dir="$1" local current_depth="${2:-0}" local indent="${3:-}" # Проверка глубины при рекурсии if [[ $RECURSIVE -eq 1 && $MAX_DEPTH -gt 0 && $current_depth -ge $MAX_DEPTH ]]; then return fi # Собираем список файлов local -a items=() for item in "$dir"/*; do [[ ! -e "$item" && ! -L "$item" ]] && break local base_name=$(basename "$item") # Пропускаем исключённые if should_exclude "$base_name"; then continue fi items+=("$item") done # Сортируем если нужно if [[ "$SORT_MODE" != "none" ]]; then case "$SORT_MODE" in az) IFS=$'\n' items=($(sort <<<"${items[*]}")) ;; za) IFS=$'\n' items=($(sort -r <<<"${items[*]}")) ;; up) # Сортируем по размеру возрастающе items=($(for item in "${items[@]}"; do size=$(du -sh "$item" 2>/dev/null | cut -f1) echo "$size|$item" done | sort -V | cut -d'|' -f2-)) ;; down) # Сортируем по размеру убывающе items=($(for item in "${items[@]}"; do size=$(du -sh "$item" 2>/dev/null | cut -f1) echo "$size|$item" done | sort -Vr | cut -d'|' -f2-)) ;; esac fi # Добавляем в таблицу for item in "${items[@]}"; do local base_name=$(basename "$item") local color=$(get_color "$item") local size=$(du -sh "$item" 2>/dev/null | cut -f1) local nested_count="" # Если папка, считаем объекты if [[ -d "$item" ]]; then nested_count=$(count_objects "$item") fi # Добавляем цветное имя для вывода local colored_name="${color}${indent}${base_name}${COLOR_RESET}" # Собираем данные для таблицы TABLE_DATA+=("${colored_name}|${size}|${nested_count}") # Рекурсия если флаг -r if [[ $RECURSIVE -eq 1 && -d "$item" ]]; then local new_indent="${indent} " process_directory "$item" $((current_depth + 1)) "$new_indent" fi done } # Вывод таблицы print_table() { if [[ ${#TABLE_DATA[@]} -eq 0 ]]; then echo "Директория пуста или не найдено файлов" return fi # Заголовок таблицы echo -e "\n${COLOR_WHITE}┌─────────────────────────────────────┬──────────┬──────────────┐" printf "│ %-35s │ %-8s │ %-12s │\n" "Имя" "Размер" "Объектов" echo -e "├─────────────────────────────────────┼──────────┼──────────────┤${COLOR_RESET}" # Строки таблицы for row in "${TABLE_DATA[@]}"; do IFS='|' read -r name size nested <<< "$row" # Очищаем ANSI коды для подсчета длины local clean_name=$(echo -e "$name" | sed 's/\x1b\[[0-9;]*m//g') local name_len=${#clean_name} local padding=$((35 - name_len)) echo -ne "$name" printf "%${padding}s │ %8s │ %12s │\n" "" "$size" "$nested" done echo -e "${COLOR_WHITE}└─────────────────────────────────────┴──────────┴──────────────┘${COLOR_RESET}\n" } # Парсинг аргументов while [[ $# -gt 0 ]]; do case "$1" in -h|--help) show_help exit 0 ;; -r|--recursive) RECURSIVE=1 shift ;; -d|--depth) MAX_DEPTH="$2" shift 2 ;; --no-count) NO_COUNT=1 shift ;; --no-nested-count) NO_NESTED_COUNT=1 shift ;; -e|--exclude) if [[ "$2" == *"*"* || "$2" == *"?"* ]]; then EXCLUDE_PATTERNS+=("$2") else EXCLUDE_EXACT+=("$2") fi shift 2 ;; -s|--sort) SORT_MODE="$2" shift 2 ;; -*) echo "Неизвестный флаг: $1" >&2 show_help exit 1 ;; *) DIRECTORY="$1" shift ;; esac done # Проверка что директория существует if [[ ! -d "$DIRECTORY" ]]; then echo "Ошибка: '$DIRECTORY' не является директорией" >&2 exit 1 fi # Запуск process_directory "$DIRECTORY" # Вывод таблицы print_table