#!/bin/bash # Флаги VERBOSE=0 SHOW_PATHS=0 RECURSIVE=1 TARGET_EXT="" INPUT_DIR="." NO_DEFAULT_EXCLUDES=0 EXCLUDE_DIRS=() EXCLUDE_EXTS=() # Функция помощи 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 " -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 файлы в указанной папке" } # Парсинг аргументов 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 ;; -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 } # Основная функция поиска и вывода 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%/}" # убрать слеш в конце 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 # Вывод информации при verbose 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[*]}" 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" else # Режим статистики declare -A stats 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) if [[ -n "$TARGET_EXT" ]]; then local t=$(clean_ext "$TARGET_EXT") echo "Статистика .$t файлов:" echo "=============================" echo "$t: ${stats[$t]:-0}" 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 # Суммируем (цикл выше в подоболочке, поэтому считаем отдельно) for count in "${stats[@]}"; do ((total_files += count)) done echo "-------------------------" echo "Всего файлов: $total_files" echo "Уникальных расширений: ${#stats[@]}" fi fi } # Запуск обработки process_files # Возврат в исходную директорию if [[ "$CURRENT_DIR" != "$WORK_DIR" ]]; then cd "$CURRENT_DIR" || exit 1 fi