From 870548020d8377cd03253895d5647babf770a199 Mon Sep 17 00:00:00 2001 From: jads Date: Tue, 12 May 2026 18:24:43 +0300 Subject: [PATCH] finder.sh --- README.md | 11 +++ finder.sh | 254 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100755 finder.sh diff --git a/README.md b/README.md index 32f8192..6c33917 100644 --- a/README.md +++ b/README.md @@ -6,3 +6,14 @@ # combine.sh Объединяет содержимое текстовых файлов из указанной директории в один файл. Удобно для подготовки контекста для AI, анализа кода или создания резюме проекта. + +# finder.sh +Утилита командной строки для анализа файлов в директории: сбор статистики по расширениям или отображение списка файлов с возможностью фильтрации и исключений + +## Возможности + +- Подсчёт количества файлов по расширениям с сортировкой +- Фильтр по целевому расширению +- Показ путей к файлам (режим `-p`) +- Исключение директорий и отдельных расширений +- Рекурсивный и нерекурсивный обход diff --git a/finder.sh b/finder.sh new file mode 100755 index 0000000..5005be6 --- /dev/null +++ b/finder.sh @@ -0,0 +1,254 @@ +#!/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