#!/bin/bash # Флаги VERBOSE=0 SHOW_PATHS=0 RECURSIVE=1 TARGET_EXT="" INPUT_DIR="." NO_DEFAULT_EXCLUDES=0 EXCLUDE_DIRS=() EXCLUDE_EXTS=() PROGRESS_BAR=1 # новый флаг # Функция помощи show_help() { echo "Использование: $0 [OPTIONS] [EXTENSION]" echo "" echo "Опции:" echo " -i, --input DIR Входная директория (по умолчанию текущая)" echo " -v, --verbose Подробный вывод" echo " -p, --paths Показать пути к файлам (вместо статистики)" echo " -n, --no-recursive Только текущая директория" echo " -r, --recursive Рекурсивный обход (по умолчанию)" echo " -e, --exclude-dir DIR Исключить директорию (можно несколько раз)" echo " --exclude-ext EXT Исключить расширение файла (можно несколько раз)" echo " -D, --no-default-excludes Не исключать .git (по умолчанию .git исключается)" echo " --no-progress Отключить прогресс-бар" echo " -h, --help Показать эту справку" echo "" echo "Примеры:" echo " $0 # Статистика всех файлов" echo " $0 .txt # Статистика txt файлов" echo " $0 -p .py # Показать все пути к .py файлам" echo " $0 -p # Показать пути ко всем файлам" echo " $0 -v # Подробная статистика всех файлов" echo " $0 -n # Только в текущей директории" echo " $0 -e build --exclude-ext log # Исключить папку build и .log файлы" echo " $0 -D # Отключить встроенное исключение .git" echo " $0 -i ~/projects -p .py # Показать .py файлы в указанной папке" echo " $0 --no-progress # Без прогресс-бара" } # Парсинг аргументов while [[ $# -gt 0 ]]; do case $1 in -v|--verbose) VERBOSE=1 shift ;; -p|--paths) SHOW_PATHS=1 shift ;; -r|--recursive) RECURSIVE=1 shift ;; -n|--no-recursive) RECURSIVE=0 shift ;; -i|--input) if [[ -z "$2" ]]; then echo "Ошибка: опция -i требует аргумент" exit 1 fi INPUT_DIR="$2" shift 2 ;; -e|--exclude-dir) if [[ -z "$2" ]]; then echo "Ошибка: опция -e требует аргумент" exit 1 fi EXCLUDE_DIRS+=("$2") shift 2 ;; --exclude-ext) if [[ -z "$2" ]]; then echo "Ошибка: опция --exclude-ext требует аргумент" exit 1 fi EXCLUDE_EXTS+=("$2") shift 2 ;; -D|--no-default-excludes) NO_DEFAULT_EXCLUDES=1 shift ;; --no-progress) PROGRESS_BAR=0 shift ;; -h|--help) show_help exit 0 ;; -*) echo "Ошибка: Неизвестная опция $1" show_help exit 1 ;; *) TARGET_EXT="$1" shift ;; esac done # Проверка существования входной директории if [[ ! -d "$INPUT_DIR" ]]; then echo "Ошибка: Директория '$INPUT_DIR' не существует" exit 1 fi # Переход в рабочую директорию CURRENT_DIR="$(pwd)" cd "$INPUT_DIR" || { echo "Ошибка: Не удалось перейти в директорию '$INPUT_DIR'" exit 1 } WORK_DIR="$(pwd)" # Очистка расширения: убираем ведущую точку, если есть clean_ext() { local ext="$1" ext="${ext#.}" echo "$ext" } # Функция получения расширения файла (без точки) get_extension() { local file="$1" local filename=$(basename -- "$file") local ext="${filename##*.}" if [[ "$ext" == "$filename" ]]; then echo "no_extension" else echo "$ext" 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") if [[ $NO_DEFAULT_EXCLUDES -eq 0 ]]; then find_args+=("-not" "-path" "./.git/*" "-not" "-path" "./.git") fi for dir in "${EXCLUDE_DIRS[@]}"; do 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 find_args+=("!" "-name" "*.*") else local ext_clean=$(clean_ext "$TARGET_EXT") find_args+=("-name" "*.$ext_clean") fi fi for ext in "${EXCLUDE_EXTS[@]}"; do ext=$(clean_ext "$ext") find_args+=("-not" "-name" "*.$ext") done if [[ $VERBOSE -eq 1 ]]; then echo "Рабочая директория: $WORK_DIR" echo "Режим: $([[ $SHOW_PATHS -eq 1 ]] && echo 'Показ путей' || echo 'Статистика')" echo "Рекурсивно: $([[ $RECURSIVE -eq 1 ]] && echo 'Да' || echo 'Нет')" echo "Стандартные исключения: $([[ $NO_DEFAULT_EXCLUDES -eq 0 ]] && echo '.git' || echo 'Нет')" [[ -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 echo "Пути ко всем файлам:" fi echo "==========================" local count=0 while IFS= read -r -d '' file; do echo "${file#./}" ((count++)) 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 # Потоковый режим (без прогресс-бара) – оригинальное поведение while IFS= read -r -d '' file; do local ext if [[ -n "$TARGET_EXT" ]]; then ext=$(clean_ext "$TARGET_EXT") else ext=$(get_extension "$file") 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}" 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 total_files=0 for count in "${stats[@]}"; do ((total_files += count)) done fi echo "-------------------------" echo "Всего файлов: $total_files" echo "Уникальных расширений: ${#stats[@]}" fi } # Запуск обработки process_files # Возврат в исходную директорию if [[ "$CURRENT_DIR" != "$WORK_DIR" ]]; then cd "$CURRENT_DIR" || exit 1 fi