Compare commits
3 Commits
4122afeda6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1af369d928 | ||
|
|
870548020d | ||
|
|
c7edca3c3e |
16
README.md
16
README.md
@@ -1,3 +1,19 @@
|
|||||||
# linux_tools
|
# linux_tools
|
||||||
|
|
||||||
Инструменты для Линукс
|
Инструменты для Линукс
|
||||||
|
|
||||||
|
использование всех инструментов: chmod +x combine.sh
|
||||||
|
|
||||||
|
# combine.sh
|
||||||
|
Объединяет содержимое текстовых файлов из указанной директории в один файл. Удобно для подготовки контекста для AI, анализа кода или создания резюме проекта.
|
||||||
|
|
||||||
|
# finder.sh
|
||||||
|
Утилита командной строки для анализа файлов в директории: сбор статистики по расширениям или отображение списка файлов с возможностью фильтрации и исключений
|
||||||
|
|
||||||
|
## Возможности
|
||||||
|
|
||||||
|
- Подсчёт количества файлов по расширениям с сортировкой
|
||||||
|
- Фильтр по целевому расширению
|
||||||
|
- Показ путей к файлам (режим `-p`)
|
||||||
|
- Исключение директорий и отдельных расширений
|
||||||
|
- Рекурсивный и нерекурсивный обход
|
||||||
|
|||||||
215
combine.sh
Executable file
215
combine.sh
Executable file
@@ -0,0 +1,215 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Название выходного файла по умолчанию
|
||||||
|
OUTPUT_FILE="combined_files.txt"
|
||||||
|
# Директория по умолчанию
|
||||||
|
INPUT_DIR="."
|
||||||
|
# Флаги
|
||||||
|
VERBOSE=0
|
||||||
|
RECURSIVE=1
|
||||||
|
# Массив для исключений
|
||||||
|
EXCLUDE_PATHS=()
|
||||||
|
|
||||||
|
# Функция для показа помощи
|
||||||
|
show_help() {
|
||||||
|
echo "Использование: $0 [OPTIONS]"
|
||||||
|
echo ""
|
||||||
|
echo "Опции:"
|
||||||
|
echo " -o, --output FILE Выходной файл (по умолчанию: combined_files.txt)"
|
||||||
|
echo " -i, --input DIR Входная директория (по умолчанию: текущая)"
|
||||||
|
echo " -e, --exclude PATH Исключить путь (можно указать несколько раз)"
|
||||||
|
echo " -r, --recursive Рекурсивный поиск (включено по умолчанию)"
|
||||||
|
echo " -n, --no-recursive Не рекурсивный поиск"
|
||||||
|
echo " -v, --verbose Подробный вывод"
|
||||||
|
echo " -h, --help Показать эту справку"
|
||||||
|
echo ""
|
||||||
|
echo "Примеры:"
|
||||||
|
echo " $0 -v -o result.txt"
|
||||||
|
echo " $0 -i ~/projects -o /tmp/combined.txt"
|
||||||
|
echo " $0 -e venv -e foto -e node_modules"
|
||||||
|
echo " $0 --no-recursive --verbose -e temp"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Парсинг аргументов командной строки
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-o|--output)
|
||||||
|
OUTPUT_FILE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-i|--input)
|
||||||
|
INPUT_DIR="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-e|--exclude)
|
||||||
|
EXCLUDE_PATHS+=("$2")
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-r|--recursive)
|
||||||
|
RECURSIVE=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n|--no-recursive)
|
||||||
|
RECURSIVE=0
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-v|--verbose)
|
||||||
|
VERBOSE=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
show_help
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Ошибка: Неизвестный параметр $1"
|
||||||
|
echo "Используйте $0 --help для справки"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Проверка существования входной директории
|
||||||
|
if [[ ! -d "$INPUT_DIR" ]]; then
|
||||||
|
echo "Ошибка: Директория '$INPUT_DIR' не существует"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Проверка на запись в выходной файл
|
||||||
|
if touch "$OUTPUT_FILE" 2>/dev/null; then
|
||||||
|
rm -f "$OUTPUT_FILE"
|
||||||
|
else
|
||||||
|
echo "Ошибка: Нет прав для записи в '$OUTPUT_FILE'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Запоминаем текущую директорию для возврата
|
||||||
|
CURRENT_DIR="$(pwd)"
|
||||||
|
|
||||||
|
# Переход в входную директорию если она указана не как текущая
|
||||||
|
if [[ "$INPUT_DIR" != "." ]]; then
|
||||||
|
cd "$INPUT_DIR" || {
|
||||||
|
echo "Ошибка: Не удалось перейти в директорию '$INPUT_DIR'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Очищаем выходной файл
|
||||||
|
> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
# Счетчики
|
||||||
|
total_files=0
|
||||||
|
processed_files=0
|
||||||
|
skipped_files=0
|
||||||
|
|
||||||
|
# Вывод информации о запуске
|
||||||
|
if [[ $VERBOSE -eq 1 ]]; then
|
||||||
|
echo "Запуск combine.sh с параметрами:"
|
||||||
|
echo " Входная директория: $(pwd)"
|
||||||
|
echo " Выходной файл: $OUTPUT_FILE"
|
||||||
|
echo " Рекурсивный поиск: $([[ $RECURSIVE -eq 1 ]] && echo 'Да' || echo 'Нет')"
|
||||||
|
if [[ ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
|
||||||
|
echo " Исключения: ${EXCLUDE_PATHS[*]}"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Функция для обработки файлов
|
||||||
|
process_files() {
|
||||||
|
local find_cmd="find . -type f"
|
||||||
|
|
||||||
|
# Если не рекурсивно, ищем только в текущей директории
|
||||||
|
if [[ $RECURSIVE -eq 0 ]]; then
|
||||||
|
find_cmd="find . -maxdepth 1 -type f"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Добавляем стандартные исключения
|
||||||
|
find_cmd="$find_cmd \
|
||||||
|
-not -path \"./venv/*\" \
|
||||||
|
-not -path \"*/venv/*\" \
|
||||||
|
-not -path \"*/__pycache__/*\" \
|
||||||
|
-not -path \"*cache*\" \
|
||||||
|
-not -path \"*.pyc\" \
|
||||||
|
-not -path \"db.*\" \
|
||||||
|
-not -path \"*.lock\" \
|
||||||
|
-not -path \"*.git/*\" \
|
||||||
|
-not -path \"*.db\" \
|
||||||
|
-not -path \"*.pack\" \
|
||||||
|
-not -path \"*/node_modules/*\" \
|
||||||
|
-not -path \"./$OUTPUT_FILE\""
|
||||||
|
|
||||||
|
# Добавляем пользовательские исключения
|
||||||
|
for exclude in "${EXCLUDE_PATHS[@]}"; do
|
||||||
|
# Убираем начальные ./ если есть
|
||||||
|
exclude="${exclude#./}"
|
||||||
|
find_cmd="$find_cmd -not -path \"./$exclude\" -not -path \"./$exclude/*\" -not -path \"*/$exclude/*\""
|
||||||
|
done
|
||||||
|
|
||||||
|
find_cmd="$find_cmd 2>/dev/null"
|
||||||
|
|
||||||
|
# Временный файл для результатов find
|
||||||
|
local temp_file=$(mktemp)
|
||||||
|
|
||||||
|
# Выполняем поиск и сохраняем во временный файл
|
||||||
|
eval "$find_cmd" > "$temp_file"
|
||||||
|
|
||||||
|
# Обрабатываем файлы
|
||||||
|
while IFS= read -r file; do
|
||||||
|
[[ -z "$file" ]] && continue
|
||||||
|
|
||||||
|
((total_files++))
|
||||||
|
|
||||||
|
# Пропускаем бинарные файлы
|
||||||
|
if file "$file" | grep -q "text"; then
|
||||||
|
# Получаем относительный путь файла
|
||||||
|
relative_path="${file#./}"
|
||||||
|
|
||||||
|
if [[ $VERBOSE -eq 1 ]]; then
|
||||||
|
echo "Добавляем: $relative_path"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Добавляем разделитель с именем файла в выходной файл
|
||||||
|
{
|
||||||
|
echo "# ========================================="
|
||||||
|
echo "# Файл: $relative_path"
|
||||||
|
echo "# ========================================="
|
||||||
|
echo ""
|
||||||
|
cat "$file" 2>/dev/null
|
||||||
|
echo ""
|
||||||
|
echo ""
|
||||||
|
} >> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
((processed_files++))
|
||||||
|
else
|
||||||
|
if [[ $VERBOSE -eq 1 ]]; then
|
||||||
|
echo "Пропускаем (бинарный): ${file#./}"
|
||||||
|
fi
|
||||||
|
((skipped_files++))
|
||||||
|
fi
|
||||||
|
done < "$temp_file"
|
||||||
|
|
||||||
|
# Удаляем временный файл
|
||||||
|
rm -f "$temp_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Запускаем обработку
|
||||||
|
process_files
|
||||||
|
|
||||||
|
# Возвращаемся в исходную директорию если нужно
|
||||||
|
if [[ "$INPUT_DIR" != "." ]]; then
|
||||||
|
cd "$CURRENT_DIR" || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Итоговый отчет
|
||||||
|
echo "Готово!"
|
||||||
|
echo "Обработано файлов: $processed_files из $total_files"
|
||||||
|
if [[ $skipped_files -gt 0 ]]; then
|
||||||
|
echo "Пропущено бинарных файлов: $skipped_files"
|
||||||
|
fi
|
||||||
|
if [[ ${#EXCLUDE_PATHS[@]} -gt 0 ]]; then
|
||||||
|
echo "Исключено путей: ${#EXCLUDE_PATHS[@]}"
|
||||||
|
fi
|
||||||
|
echo "Результат в: $OUTPUT_FILE"
|
||||||
|
if [[ "$INPUT_DIR" != "." ]]; then
|
||||||
|
echo "Исходная директория: $INPUT_DIR"
|
||||||
|
fi
|
||||||
297
file_size.sh
Executable file
297
file_size.sh
Executable file
@@ -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
|
||||||
331
finder.sh
Executable file
331
finder.sh
Executable file
@@ -0,0 +1,331 @@
|
|||||||
|
#!/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
|
||||||
Reference in New Issue
Block a user