From 1af369d9281d90e1b00c436964975817fcd5cee6 Mon Sep 17 00:00:00 2001 From: jadssss Date: Mon, 1 Jun 2026 02:03:00 +0300 Subject: [PATCH] update finder, add file_size --- file_size.sh | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++ finder.sh | 139 ++++++++++++++++++------ 2 files changed, 405 insertions(+), 31 deletions(-) create mode 100755 file_size.sh diff --git a/file_size.sh b/file_size.sh new file mode 100755 index 0000000..88462cc --- /dev/null +++ b/file_size.sh @@ -0,0 +1,297 @@ +#!/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 diff --git a/finder.sh b/finder.sh index 5005be6..0ddf7e3 100755 --- a/finder.sh +++ b/finder.sh @@ -9,6 +9,7 @@ INPUT_DIR="." NO_DEFAULT_EXCLUDES=0 EXCLUDE_DIRS=() EXCLUDE_EXTS=() +PROGRESS_BAR=1 # новый флаг # Функция помощи show_help() { @@ -23,6 +24,7 @@ show_help() { echo " -e, --exclude-dir DIR Исключить директорию (можно несколько раз)" echo " --exclude-ext EXT Исключить расширение файла (можно несколько раз)" echo " -D, --no-default-excludes Не исключать .git (по умолчанию .git исключается)" + echo " --no-progress Отключить прогресс-бар" echo " -h, --help Показать эту справку" echo "" echo "Примеры:" @@ -35,6 +37,7 @@ show_help() { echo " $0 -e build --exclude-ext log # Исключить папку build и .log файлы" echo " $0 -D # Отключить встроенное исключение .git" echo " $0 -i ~/projects -p .py # Показать .py файлы в указанной папке" + echo " $0 --no-progress # Без прогресс-бара" } # Парсинг аргументов @@ -84,6 +87,10 @@ while [[ $# -gt 0 ]]; do NO_DEFAULT_EXCLUDES=1 shift ;; + --no-progress) + PROGRESS_BAR=0 + shift + ;; -h|--help) show_help exit 0 @@ -133,36 +140,54 @@ get_extension() { fi } +# Функция отображения прогресс-бара (как в первой версии) +show_progress() { + local current=$1 + local total=$2 + local width=50 + local percentage=$((current * 100 / total)) + local completed=$((current * width / total)) + local remaining=$((width - completed)) + + if [[ $total -eq 0 ]]; then + percentage=0 + completed=0 + remaining=$width + fi + + printf "\r[" + printf "%${completed}s" | tr ' ' '=' + printf "%${remaining}s" | tr ' ' ' ' + printf "] %3d%% Обработано: %d/%d" "$percentage" "$current" "$total" + + if [[ $current -eq $total ]] && [[ $total -gt 0 ]]; then + echo "" + fi +} + # Основная функция поиска и вывода process_files() { # Построение массива аргументов для find local find_args=(".") local has_target=0 - # Глубина if [[ $RECURSIVE -eq 0 ]]; then find_args+=("-maxdepth" "1") fi - # Только файлы find_args+=("-type" "f") - # Стандартные исключения (только .git) if [[ $NO_DEFAULT_EXCLUDES -eq 0 ]]; then find_args+=("-not" "-path" "./.git/*" "-not" "-path" "./.git") fi - # Пользовательские исключения директорий for dir in "${EXCLUDE_DIRS[@]}"; do - dir="${dir%/}" # убрать слеш в конце + dir="${dir%/}" local clean_dir="${dir#./}" - # Исключаем саму директорию и всё внутри неё find_args+=("-not" "-path" "./$clean_dir" "-not" "-path" "./$clean_dir/*") - # На всякий случай, если вложена где-то ещё find_args+=("-not" "-path" "*/$clean_dir/*" "-not" "-path" "*/$clean_dir") done - # Фильтр по целевому расширению if [[ -n "$TARGET_EXT" ]]; then has_target=1 if [[ "$TARGET_EXT" == "no_extension" ]]; then @@ -173,13 +198,11 @@ process_files() { fi fi - # Исключение расширений for ext in "${EXCLUDE_EXTS[@]}"; do ext=$(clean_ext "$ext") find_args+=("-not" "-name" "*.$ext") done - # Вывод информации при verbose if [[ $VERBOSE -eq 1 ]]; then echo "Рабочая директория: $WORK_DIR" echo "Режим: $([[ $SHOW_PATHS -eq 1 ]] && echo 'Показ путей' || echo 'Статистика')" @@ -188,11 +211,14 @@ process_files() { [[ -n "$TARGET_EXT" ]] && echo "Целевое расширение: $TARGET_EXT" [[ ${#EXCLUDE_DIRS[@]} -gt 0 ]] && echo "Исключаемые папки: ${EXCLUDE_DIRS[*]}" [[ ${#EXCLUDE_EXTS[@]} -gt 0 ]] && echo "Исключаемые расширения: ${EXCLUDE_EXTS[*]}" + [[ $PROGRESS_BAR -eq 1 ]] && echo "Прогресс-бар: включен" echo "" fi + # ------------------------------------------------------------------ + # РЕЖИМ ПОКАЗА ПУТЕЙ (без прогресс-бара, как раньше) + # ------------------------------------------------------------------ if [[ $SHOW_PATHS -eq 1 ]]; then - # Режим вывода путей if [[ -n "$TARGET_EXT" ]]; then echo "Пути к .$(clean_ext "$TARGET_EXT") файлам:" else @@ -206,9 +232,45 @@ process_files() { done < <(find "${find_args[@]}" -print0 2>/dev/null) echo "-------------------------" echo "Найдено файлов: $count" + return + fi + + # ------------------------------------------------------------------ + # РЕЖИМ СТАТИСТИКИ + # ------------------------------------------------------------------ + declare -A stats + local total_files=0 + + # Если прогресс-бар включён – сначала собираем все файлы в массив, + # иначе работаем потоково (быстрее, без двойного прохода) + if [[ $PROGRESS_BAR -eq 1 ]]; then + echo "Сканирование файлов..." + # Собираем массив путей (используем mapfile, как в первой версии) + mapfile -d '' all_files < <(find "${find_args[@]}" -print0 2>/dev/null) + total_files=${#all_files[@]} + local processed=0 + + if [[ $total_files -gt 0 ]]; then + echo "Обработка файлов:" + fi + + for file in "${all_files[@]}"; do + local ext + if [[ -n "$TARGET_EXT" ]]; then + ext=$(clean_ext "$TARGET_EXT") + else + ext=$(get_extension "$file") + fi + ((stats["$ext"]++)) + ((processed++)) + show_progress "$processed" "$total_files" + done + # После завершения прогресс-бара переводим строку (если файлы были) + if [[ $total_files -gt 0 ]]; then + echo "" + fi else - # Режим статистики - declare -A stats + # Потоковый режим (без прогресс-бара) – оригинальное поведение while IFS= read -r -d '' file; do local ext if [[ -n "$TARGET_EXT" ]]; then @@ -218,30 +280,45 @@ process_files() { fi ((stats["$ext"]++)) done < <(find "${find_args[@]}" -print0 2>/dev/null) + # Подсчитаем общее количество для вывода (перебираем значения) + for count in "${stats[@]}"; do + ((total_files += count)) + done + fi - if [[ -n "$TARGET_EXT" ]]; then - local t=$(clean_ext "$TARGET_EXT") - echo "Статистика .$t файлов:" - echo "=============================" - echo "$t: ${stats[$t]:-0}" + # Вывод статистики + if [[ -n "$TARGET_EXT" ]]; then + local t=$(clean_ext "$TARGET_EXT") + echo "Статистика .$t файлов:" + echo "=============================" + echo "$t: ${stats[$t]:-0}" + else + echo "Статистика файлов:" + echo "=================" + # Сортировка расширений по убыванию количества + for ext in "${!stats[@]}"; do + printf "%s %d\n" "$ext" "${stats[$ext]}" + done | sort -k2 -nr | while read ext count; do + printf "%-15s %d\n" "$ext" "$count" + done + # Если прогресс-бар НЕ использовался, total_files уже посчитан в цикле выше. + # Если использовался, пересчитаем из stats (т.к. total_files уже есть, но можно перестраховаться) + if [[ $PROGRESS_BAR -eq 0 ]]; then + # total_files уже посчитан, но если по какой-то причине нет – пересчитаем + if [[ $total_files -eq 0 ]]; then + for count in "${stats[@]}"; do + ((total_files += count)) + done + fi else - echo "Статистика файлов:" - echo "=================" - local total_files=0 - # Сортировка расширений по убыванию количества - for ext in "${!stats[@]}"; do - printf "%s %d\n" "$ext" "${stats[$ext]}" - done | sort -k2 -nr | while read ext count; do - printf "%-15s %d\n" "$ext" "$count" - done - # Суммируем (цикл выше в подоболочке, поэтому считаем отдельно) + total_files=0 for count in "${stats[@]}"; do ((total_files += count)) done - echo "-------------------------" - echo "Всего файлов: $total_files" - echo "Уникальных расширений: ${#stats[@]}" fi + echo "-------------------------" + echo "Всего файлов: $total_files" + echo "Уникальных расширений: ${#stats[@]}" fi }