298 lines
9.1 KiB
Bash
Executable File
298 lines
9.1 KiB
Bash
Executable File
#!/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
|