Скрипт меню для работы с документами Alt Basalt.

Для экономии нашего рабочего времени автоматизируем процесс работы с архивами с почтовых программ, а также работы с файлами в виде jpg, bmp.
Рассмотрим 2 варианта кода скрипта
ВАРИАНТ 1
Скрипт представляет собой функционал создания на рабочем столе папки Доки из архивов позволяет создавать папки по номерам, Epson для документов которые появляются в этой папке после сканирования в этой папке в скрипте добавлен функционал переименования файла сопровод в конверт, приложение1, и удаление всех файлов из папки Epson.
Есть функция склеивания картинок в один фаил pdf, также добавлен пункт меню Organize в эту папку кидаем pdf файлы и используем пункт меню Разложить PDF по папкам (из папки Organize) так удобнее регистрировать когда прислали в одном архиве несколько документов проще разложить по папкам и регистрировать. Добавлен пункт меню выбор символов.
ОБЯЗАТЕЛЬНО ПРОПИСАТЬ ПУТЬ ДО ПАПКИ В УТИЛИТЕ EPSON SCAN НАПРИМЕР доки из архивов/EPSON и указать имя файла сопровод по окончании сканирования этот фаил всегда будет создаваться с именем сопровод.pdf
Создайте фаил в программе Kwrite c именем menu.sh и добавьте в его содержание следующий код:

#!/bin/bash

# ==========================================
# НАСТРОЙКИ И ПЕРЕМЕННЫЕ
# ==========================================
BASE_DIR="$HOME/Рабочий стол/Доки из архивов"

# Цвета для красоты
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log() { echo -e "${GREEN}[INFO]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }

# Проверка наличия ghostscript
check_gs() {
if ! command -v gs &> /dev/null; then
echo ""
error "Ghostscript не установлен! Без него сжатие PDF невозможно."
echo "Установите командой: sudo apt-get install ghostscript"
echo ""
return 1
fi
return 0
}

# ==========================================
# ПОДМЕНЮ: СЖАТИЕ PDF (ВСТРОЕННОЕ)
# ==========================================
submenu_pdf_compress() {
local pdf_dir="$BASE_DIR/Compress"
local pdf_files=()
local selected_files=()

if [[ ! -d "$pdf_dir" ]]; then
error "Папка Compress не найдена: $pdf_dir. Сначала создайте её через пункт 3 главного меню."
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

find_pdf_files() {
pdf_files=()
local i=1
for file in "$pdf_dir"/*.pdf; do
if [[ -f "$file" ]]; then
pdf_files+=("$file")
echo " $i) $(basename "$file")"
((i++))
fi
done
if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo " В папке Compress нет PDF-файлов."
else
echo " Найдено PDF-файлов: ${#pdf_files[@]}"
fi
}

compress_one() {
local input_file="$1"
local output_file="$pdf_dir/compressed_$(basename "$input_file")"

echo "Сжимаю: $(basename "$input_file")..."

gs -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.4 \
-dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile="$output_file" \
"$input_file"

if [[ $? -eq 0 ]]; then
local original_size=$(stat -c%s "$input_file")
local compressed_size=$(stat -c%s "$output_file")
local savings=$((original_size - compressed_size))

if command -v bc &> /dev/null; then
local percent
percent=$(echo "scale=2; $savings * 100 / $original_size" | bc -l 2>/dev/null || echo 0)
printf " ✓ Успешно: экономия %d байт (%.1f%%)\n" "$savings" "$percent"
else
printf " ✓ Успешно: экономия %d байт\n" "$savings"
fi
else
echo " ✗ Ошибка при сжатии $(basename "$input_file")"
rm -f "$output_file"
fi
}

delete_files() {
local to_delete=("$@")

if [[ ${#to_delete[@]} -eq 0 ]]; then
echo "Ничего не выбрано для удаления."
return
fi

echo ""
echo "⚠️ ВНИМАНИЕ: Будут удалены следующие файлы:"
for f in "${to_delete[@]}"; do
echo " - $(basename "$f")"
done
echo ""
read -p "Вы уверены? Введите 'ДА' (или y) для подтверждения, любой другой текст для отмены: " confirm

if [[ "$confirm" == "ДА" || "$confirm" == "да" || "$confirm" == "y" || "$confirm" == "Y" ]]; then
for f in "${to_delete[@]}"; do
if [[ -f "$f" ]]; then
rm -f "$f"
log "Удален: $(basename "$f")"
fi
done
echo ""
echo "✅ Файлы удалены!"
else
echo "Удаление отменено."
fi
read -p "Нажмите Enter, чтобы продолжить..." _
}

while true; do
clear
echo ""
echo "=========================================="
echo " МЕНЮ СЖАТИЯ PDF-ФАЙЛОВ"
echo "=========================================="
echo "1) Сжать ВСЕ файлы в папке Compress"
echo "2) Выбрать конкретные файлы по номеру для сжатия"
echo "3) Удалить несжатые файлы (выбор по списку)"
echo "4) Назад в главное меню"
echo "=========================================="

read -p "Выберите пункт (1-4): " choice

case "$choice" in
1)
echo ""
echo "Список файлов для сжатия:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов. Нажмите Enter, чтобы вернуться..." _
continue
fi

echo ""
echo "Начинаю сжатие всех файлов..."
echo "------------------------------------------"
for file in "${pdf_files[@]}"; do
compress_one "$file"
done
echo ""
echo "✅ Все файлы сжаты!"
read -p "Нажмите Enter, чтобы продолжить..." _
;;

2)
echo ""
echo "Сначала покажем список файлов:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов для выбора. Нажмите Enter..." _
continue
fi

echo ""
echo "Введите номер(а) файла(ов) через пробел (например: 1 3 5)."
echo "Чтобы отменить, просто нажмите Enter без ввода."
read -r selection

if [[ -z "$selection" ]]; then
echo "Отмена."
read -p "Нажмите Enter..." _
continue
fi

selected_files=()
local error=0

for num in $selection; do
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "❌ Ошибка: '$num' — не номер."
error=1
break
fi
if [[ "$num" -lt 1 || "$num" -gt "${#pdf_files[@]}" ]]; then
echo "❌ Ошибка: номер '$num' вне диапазона (1-${#pdf_files[@]})."
error=1
break
fi
selected_files+=("${pdf_files[$((num-1))]}")
done

if [[ $error -eq 1 ]]; then
read -p "Нажмите Enter, чтобы попробовать снова..." _
continue
fi

if [[ ${#selected_files[@]} -eq 0 ]]; then
echo "Ничего не выбрано."
read -p "Нажмите Enter..." _
continue
fi

echo ""
echo "Выбрано: ${#selected_files[@]} файлов. Начинаю сжатие..."
echo "------------------------------------------"
for file in "${selected_files[@]}"; do
compress_one "$file"
done
echo ""
echo "✅ Выбранные файлы сжаты!"
read -p "Нажмите Enter, чтобы продолжить..." _
;;

3)
echo ""
echo "Сначала покажем список всех PDF в папке:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов для удаления. Нажмите Enter..." _
continue
fi

echo ""
echo "Введите номера файлов, которые нужно УДАЛИТЬ (через пробел)."
echo "⚠️ Будьте осторожны: это действие необратимо!"
echo "Чтобы отменить, просто нажмите Enter."
read -r selection

if [[ -z "$selection" ]]; then
echo "Отмена удаления."
read -p "Нажмите Enter..." _
continue
fi

selected_files=()
local error=0

for num in $selection; do
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "❌ Ошибка: '$num' — не номер."
error=1
break
fi
if [[ "$num" -lt 1 || "$num" -gt "${#pdf_files[@]}" ]]; then
echo "❌ Ошибка: номер '$num' вне диапазона (1-${#pdf_files[@]})."
error=1
break
fi
selected_files+=("${pdf_files[$((num-1))]}")
done

if [[ $error -eq 1 ]]; then
read -p "Нажмите Enter, чтобы попробовать снова..." _
continue
fi

if [[ ${#selected_files[@]} -eq 0 ]]; then
echo "Ничего не выбрано."
read -p "Нажмите Enter..." _
continue
fi

delete_files "${selected_files[@]}"
;;

4)
echo "Возврат в главное меню."
break
;;

*)
echo "Неверный выбор. Введите 1, 2, 3 или 4."
read -p "Нажмите Enter..." _
;;
esac
done
}

# ==========================================
# ГЛАВНОЕ МЕНЮ И ФУНКЦИИ
# ==========================================

show_main_menu() {
clear
echo ""
echo "=== МЕНЮ (Alt Basalt для работы с документами Делопроизводство) ==="
echo "1. Создать каталог «Доки из архивов» и папки 1–N (выбор количества)"
echo "2. Создать папку Epson внутри «Доки из архивов»"
echo "3. Создать папку Compress внутри «Доки из архивов»"
echo "4. Создать папку Organize внутри «Доки из архивов»"
echo "5. Сжатие PDF (отдельное подменю)"
echo "6. Выполнить скрипт объединения jpg/bmp в единый PDF"
echo "7. Извлечь документы из архивов (zip/tar.gz/7z)"
echo "8. Переименовать файл сопровод.pdf в папке Epson"
echo "9. Удалить папки с номерами 1-6 (и выше) в «Доки из архивов»"
echo "10. Разложить PDF по папкам (из папки Organize)"
echo "11. Выбрать и скопировать спецсимволы"
echo "12. Очистить папку Organize (удалить все файлы)"
echo "0. Выход"
echo ""
}

create_base_and_folders() {
log "Создание базовой папки: $BASE_DIR"
mkdir -p "$BASE_DIR" || { error "Не удалось создать папку! Проверьте права доступа."; return 1; }

read -p "Сколько папок (1-N) нужно создать внутри? (например, 5): " count
if ! [[ "$count" =~ ^[0-9]+$ ]] || [ "$count" -le 0 ]; then
warn "Некорректное число. Папки не созданы."
return
fi

for ((i=1; i<=count; i++)); do
mkdir -p "$BASE_DIR/$i"
log "Создана папка: $BASE_DIR/$i"
done
log "Готово!"
}

create_epson_folder() {
mkdir -p "$BASE_DIR/Epson"
log "Папка Epson создана: $BASE_DIR/Epson"
}

create_organize_folder() {
local target_dir="$BASE_DIR/Organize"

if [[ -d "$target_dir" ]]; then
warn "Папка уже существует: $target_dir"
read -p "Нажмите Enter, чтобы вернуться..." _
return 0
fi

mkdir -p "$target_dir"
if [[ $? -eq 0 ]]; then
log "Папка Organize успешно создана: $target_dir"
else
error "Не удалось создать папку: $target_dir (проверьте права доступа)"
fi

read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

create_compress_folder() {
mkdir -p "$BASE_DIR/Compress"
log "Папка Compress создана: $BASE_DIR/Compress"
}

merge_images_to_pdf() {
local base_dir="$BASE_DIR"

if ! command -v python3 &> /dev/null; then
error "Не найден python3!"
read -p "Нажмите Enter..." _
return 1
fi

# Сначала спрашиваем у пользователя в Bash (это точно сработает)
echo ""
echo "------------------------------------------"
echo "Что делать с картинками после объединения?"
echo "1) Удалить картинки (оставить только PDF)"
echo "2) Оставить картинки (PDF + картинки)"
echo "------------------------------------------"
read -p "Выберите вариант (1 или 2): " choice

case "$choice" in
1) remove_images=true ;;
2) remove_images=false ;;
*)
echo "❌ Неверный выбор. Запустите снова и введите 1 или 2."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
return 1
;;
esac

action_text="БУДУТ УДАЛЕНЫ"
[[ "$remove_images" == "false" ]] && action_text="ОСТАНУТСЯ"
echo "📋 Подтверждение: картинки ${action_text}. Начинаю обработку..."

# Временная папка для библиотеки
local temp_lib_dir
temp_lib_dir=$(mktemp -d)
trap "rm -rf '$temp_lib_dir'" EXIT

python3 - "$base_dir" "$temp_lib_dir" "$remove_images" <<'PYEOF'
import sys, os, subprocess
from pathlib import Path

base_dir = sys.argv[1]
temp_lib_dir = sys.argv[2]
remove_images_str = sys.argv[3]
remove_images = (remove_images_str == "true")

# Установка reportlab, если нет
try:
import reportlab
except ImportError:
print("⚠️ reportlab не найден. Ставлю локально...")
try:
subprocess.check_call([sys.executable, "-m", "pip", "install", "--target", temp_lib_dir, "--no-cache-dir", "reportlab"],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
sys.path.insert(0, temp_lib_dir)
import reportlab
print("✅ Библиотека готова.")
except Exception as e:
print(f"❌ Критическая ошибка: не удалось установить reportlab. {e}", file=sys.stderr)
print("Попробуй вручную: pip3 install --user reportlab", file=sys.stderr)
sys.exit(1)

from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader

def create_pdf_and_maybe_remove(images, output_path, remove_images):
c = canvas.Canvas(output_path)
width, height = A4
added_count = 0
removed_count = 0

for img_path in images:
try:
img = ImageReader(img_path)
w, h = img.getSize()
scale = min(width / w, height / h)
draw_w = w * scale
draw_h = h * scale
x = (width - draw_w) / 2
y = (height - draw_h) / 2

c.drawImage(img, x, y, draw_w, draw_h, preserveAspectRatio=True, mask='auto')
c.showPage()
added_count += 1

if remove_images:
os.remove(img_path)
removed_count += 1
except Exception as e:
print(f" ! Пропущено (не читается как картинка): {Path(img_path).name} (ошибка: {str(e)[:50]})", file=sys.stderr)
continue

c.save()
return added_count > 0, removed_count

if not os.path.isdir(base_dir):
print(f"Ошибка: папка {base_dir} не существует")
sys.exit(1)

folders = []
for item in os.listdir(base_dir):
full_path = os.path.join(base_dir, item)
if os.path.isdir(full_path) and item.isdigit():
folders.append((int(item), full_path))
folders.sort(key=lambda x: x[0])

print("🔍 Рекурсивный поиск картинок (JPG, PNG, BMP, GIF)...")
success_count = 0
all_folders_data = []

for num, folder_path in folders:
images = []
for root, dirs, files in os.walk(folder_path):
for fname in files:
fpath = os.path.join(root, fname)
ext = Path(fname).suffix.lower()
if ext in ['.jpg', '.jpeg', '.png', '.bmp', '.gif']:
if os.path.isfile(fpath) and os.path.getsize(fpath) > 0:
images.append(fpath)

if images:
all_folders_data.append((num, folder_path, images))
print(f" ✅ Папка {num}: найдено {len(images)} файлов.")
else:
print(f" ⚠️ Папка {num}: файлов не найдено")

if not all_folders_data:
print("\n❌ Не найдено ни одной картинки для объединения.", file=sys.stderr)
sys.exit(1)

for num, folder_path, images in all_folders_data:
out_pdf = os.path.join(folder_path, f"folder_{num}.pdf")
ok, removed_count = create_pdf_and_maybe_remove(images, out_pdf, remove_images)

if ok:
size = os.path.getsize(out_pdf)
status = f"({removed_count} файлов удалено)" if remove_images else "(файлы сохранены)"
print(f" 📄 Готово: {out_pdf} ({size} байт) {status}")
success_count += 1
else:
print(f" ❌ Не удалось создать PDF для папки {num}")

if success_count == 0:
print("\n❌ Ни один PDF не был создан.", file=sys.stderr)
sys.exit(1)
else:
print(f"\n🎉 Успешно создано PDF файлов: {success_count}")
PYEOF

read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

extract_from_target_folders() {
local base_dir="$BASE_DIR"

if [[ ! -d "$base_dir" ]]; then
echo "❌ Ошибка: папка '$base_dir' не найдена!"
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

clear
echo ""
echo "=========================================="
echo " МЕНЮ РАСПАКОВКИ АРХИВОВ"
echo "=========================================="
echo "Выберите режим извлечения:"
echo ""
echo "1) С сохранением структуры (как в архиве)"
echo " Пример: archive.zip → папка/файл.pdf"
echo ""
echo "2) ПЛОСКИЙ РЕЖИМ (без вложенных папок)"
echo " Файлы будут вытащены прямо в папку."
echo " Пример: archive.zip/папка/файл.pdf → файл.pdf"
echo ""
echo "3) Назад в главное меню"
echo "=========================================="

read -p "Ваш выбор (1-3): " mode_choice

# Если пользователь хочет назад
[[ "$mode_choice" == "3" ]] && return 0

# Проверка валидности выбора
if [[ "$mode_choice" != "1" && "$mode_choice" != "2" ]]; then
warn "Неверный выбор. Вернусь в главное меню."
read -p "Нажмите Enter..." _
return 0
fi

local flat_mode=false
if [[ "$mode_choice" == "2" ]]; then
flat_mode=true
echo ""
echo "⚠️ Выбран ПЛОСКИЙ режим. Внутренние папки архивов будут удалены."
echo ""
read -p "Нажмите Enter для продолжения..." _
fi

# Список целевых папок
local target_folders=("1" "2" "3" "4" "5" "6" "Organize")

local total_found=0
local total_extracted=0

for folder_name in "${target_folders[@]}"; do
local current_path="$base_dir/$folder_name"

if [[ ! -d "$current_path" ]]; then
echo "⚠️ Папка '$folder_name' не найдена. Пропускаем."
continue
fi

echo ""
echo "------------------------------------------"
echo "📁 Обработка: $folder_name (режим: $([ "$flat_mode" == "true" ] && echo "ПЛОСКИЙ" || echo "СТРУКТУРА")) "
echo ""

# Ищем архивы ТОЛЬКО в корне этой папки (без захода в подпапки самой папки)
# Если нужно искать и в подпапках тоже - убери -maxdepth 1
while IFS= read -r -d '' file; do
total_found=$((total_found + 1))
local filename=$(basename "$file")
local dirname=$(dirname "$file")

echo " 💾 Найдено: $filename"

case "$filename" in
*.tar.gz|*.tgz)
if [[ "$flat_mode" == "true" ]]; then
# --strip-components=1 убирает одну лишнюю папку из пути
if tar -xzf "$file" --strip-components=1 -C "$dirname"; then
echo " ✅ Распаковано (плоский)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки tar.gz"
fi
else
# Обычный режим
if tar -xzf "$file" -C "$dirname"; then
echo " ✅ Распаковано (структура сохранена)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки tar.gz"
fi
fi
;;
*.tar)
if [[ "$flat_mode" == "true" ]]; then
if tar -xf "$file" --strip-components=1 -C "$dirname"; then
echo " ✅ Распаковано (плоский)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки tar"
fi
else
if tar -xf "$file" -C "$dirname"; then
echo " ✅ Распаковано (структура сохранена)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки tar"
fi
fi
;;
*.zip)
if command -v unzip &> /dev/null; then
if [[ "$flat_mode" == "true" ]]; then
# -j (junk paths) игнорирует все пути внутри архива
if unzip -o -q -j "$file" -d "$dirname"; then
echo " ✅ Распаковано (плоский)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки zip"
fi
else
# Обычный режим
if unzip -o -q "$file" -d "$dirname"; then
echo " ✅ Распаковано (структура сохранена)"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки zip"
fi
fi
else
echo " ⚠️ Не найдена утилита 'unzip'. Установите: sudo apt install unzip"
fi
;;
*.gz)
# Для одиночных .gz файлов плоский режим не имеет смысла,
# так как там нет структуры папок, просто распаковываем файл
local out_file="${file%.gz}"
if gzip -dk "$file"; then
echo " ✅ Распакован файл: $out_file"
total_extracted=$((total_extracted + 1))
else
echo " ❌ Ошибка распаковки gz"
fi
;;
esac
done < <(find "$current_path" -maxdepth 1 -type f \( -iname "*.zip" -o -iname "*.tar" -o -iname "*.tar.gz" -o -iname "*.tgz" -o -iname "*.gz" \) -print0)
done

echo ""
echo "🏁 ИТОГО:"
echo " Найдено архивов: $total_found"
echo " Успешно распаковано: $total_extracted"

read -p "Нажмите Enter, чтобы вернуться в главное меню..." _
}

rename_accompanying_file() {
local dir="$BASE_DIR/Epson"
local prefix_old="сопровод"
local prefix_new="приложение"
local max_count=15

if [[ ! -d "$dir" ]]; then
warn "Папка Epson не найдена: $dir"
read -p "Нажмите Enter, чтобы продолжить..." _
return 1
fi

while true; do
clear
echo ""
echo "=== ПЕРЕИМЕНОВАНИЕ В ПАПКЕ EPSON ==="
echo "1) Жёстко: сопровод.pdf → конверт.pdf"
echo "2) Пакетно: сохранить цифру из имени (сопроводX → приложениеX)"
echo "3) Очистить папку Epson (удалить ВСЕ файлы)"
echo "4) Назад в главное меню"
echo ""

read -p "Выберите пункт (1-4): " choice

case "$choice" in
1)
# Жёсткое переименование в конверт.pdf
local old_path="$dir/сопровод.pdf"
local new_path="$dir/конверт.pdf"

if [[ ! -f "$old_path" ]]; then
warn "Файл $old_path не найден."
read -p "Нажмите Enter..." _
continue
fi
if [[ -f "$new_path" ]]; then
error "Файл $new_path уже существует! Переименование отменено."
read -p "Нажмите Enter..." _
continue
fi

if mv "$old_path" "$new_path"; then
log "Переименовано: сопровод.pdf → конверт.pdf"
else
error "Не удалось переименовать файл."
fi
read -p "Нажмите Enter, чтобы вернуться..." _
;;

2)
# ПАКЕТНОЕ: логика "цифра из старого имени = цифра в новом"
local files=()
local names=()

# Ищем базовый файл без цифры
if [[ -f "$dir/${prefix_old}.pdf" ]]; then
files+=("$dir/${prefix_old}.pdf")
names+=("${prefix_old}.pdf")
fi

# Ищем файлы с цифрами 1..15
for ((i=1; i<=max_count; i++)); do
local fname="${prefix_old}${i}.pdf"
if [[ -f "$dir/$fname" ]]; then
files+=("$dir/$fname")
names+=("$fname")
fi
done

if [[ ${#files[@]} -eq 0 ]]; then
warn "В папке Epson нет файлов вида ${prefix_old}*.pdf"
read -p "Нажмите Enter, чтобы вернуться..." _
continue
fi

echo ""
echo "Список доступных файлов:"
for i in "${!names[@]}"; do
printf " %d) %s\n" "$((i+1))" "${names[i]}"
done
echo ""

read -p "Введите номера файлов для переименования (через пробел): " selection

if [[ -z "$selection" ]]; then
echo "Отмена."
read -p "Нажмите Enter..." _
continue
fi

local selected_indices=()
for num in $selection; do
# Валидация номера
if [[ "$num" =~ ^[0-9]+$ ]] && (( num >= 1 && num <= ${#files[@]} )); then
selected_indices+=("$num")
else
warn "Пропущен неверный номер: '$num'"
fi
done

if [[ ${#selected_indices[@]} -eq 0 ]]; then
echo "Ничего не выбрано для обработки."
read -p "Нажмите Enter..." _
continue
fi

echo ""
echo "Начинаю обработку выбранных файлов..."
echo "------------------------------------------"

local success_count=0
local skip_count=0

for idx in "${selected_indices[@]}"; do
local real_idx=$((idx - 1))
local old_full_path="${files[real_idx]}"
local old_name="${names[real_idx]}"

# --- ГЛАВНАЯ ЛОГИКА ИЗВЛЕЧЕНИЯ ЦИФРЫ ---
local number=""

if [[ "$old_name" == "${prefix_old}.pdf" ]]; then
number=""
elif [[ "$old_name" =~ ^${prefix_old}([0-9]+)\.pdf$ ]]; then
number="${BASH_REMATCH[1]}"
else
warn "Не удалось распознать номер в файле: $old_name. Пропущено."
((skip_count++))
continue
fi
# -----------------------------------------

# Формируем новое имя
local new_name
if [[ -z "$number" ]]; then
new_name="${prefix_new}.pdf"
else
new_name="${prefix_new}${number}.pdf"
fi

local new_full_path="$dir/$new_name"

# Проверка: существует ли уже целевой файл?
if [[ -f "$new_full_path" ]]; then
echo "⚠️ ПРОПУЩЕНО: $new_name уже существует в папке. Не перезаписываем."
((skip_count++))
continue
fi

# Выполняем переименование
if mv "$old_full_path" "$new_full_path"; then
log "OK: $old_name → $new_name"
((success_count++))
else
error "Ошибка при переименовании: $old_name"
((skip_count++))
fi
done

echo ""
echo "✅ Успешно переименовано: $success_count"
if (( skip_count > 0 )); then
echo "⚠️ Пропущено (файл уже был или ошибка): $skip_count"
fi
echo ""
read -p "Нажмите Enter, чтобы вернуться..." _
;;

3)
# ОЧИСТКА ПАПКИ EPSON: удалить все файлы БЕЗ подтверждения
local file_count=0
local deleted_files=()

# Считаем файлы и собираем их список
while IFS= read -r -d '' file; do
((file_count++))
deleted_files+=("$file")
done < <(find "$dir" -maxdepth 1 -type f -print0)

if [[ $file_count -eq 0 ]]; then
echo ""
log "В папке Epson нет файлов для удаления."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
continue
fi

clear
echo ""
echo "=========================================="
echo " ОЧИСТКА ПАПКИ EPSON"
echo "=========================================="
echo "Папка: $dir"
echo "Найдено файлов: $file_count"
echo ""
echo "Начинаю удаление..."
echo "------------------------------------------"

# Удаляем и сразу запоминаем имена для отчёта
for f in "${deleted_files[@]}"; do
if rm -f "$f"; then
log "Удален: $(basename "$f")"
else
error "Не удалось удалить: $(basename "$f")"
fi
done

echo ""
echo "✅ Все файлы из папки Epson удалены."
echo ""
read -p "Нажмите Enter, чтобы вернуться в главное меню..." _
;;

4)
echo "Возврат в главное меню."
return
;;

*)
echo "Неверный выбор. Введите 1, 2, 3 или 4."
read -p "Нажмите Enter..." _
;;
esac
done
}

delete_numbered_folders() {
# Ищем папки с именами, состоящими только из цифр (1, 2, 3... 10 и т.д.)
local folders_to_delete=()

for item in "$BASE_DIR"/*; do
if [[ -d "$item" ]] && [[ "$(basename "$item")" =~ ^[0-9]+$ ]]; then
folders_to_delete+=("$item")
fi
done

if [[ ${#folders_to_delete[@]} -eq 0 ]]; then
echo ""
warn "В папке $BASE_DIR нет папок с цифровыми именами (1, 2, 3...)."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
return
fi

echo ""
echo "⚠️ НАЙДЕНЫ СЛЕДУЮЩИЕ ПАПКИ ДЛЯ УДАЛЕНИЯ:"
for f in "${folders_to_delete[@]}"; do
echo " - $(basename "$f")"
done
echo ""
read -p "ПАПКИ БУДУТ УДАЛЕНЫ. Нажмите Enter для подтверждения (Ctrl+C для отмены): " confirm

# Если пользователь просто нажал Enter — удаляем. Если Ctrl+C — скрипт прервётся сам.
for f in "${folders_to_delete[@]}"; do
rm -rf "$f"
log "Удалена папка: $(basename "$f")"
done

echo ""
echo "✅ Папки успешно удалены!"
read -p "Нажмите Enter, чтобы вернуться в главное меню..." _
}

organize_pdf_by_name() {
local base_dir="$BASE_DIR"
local target_dir="$base_dir/Organize"

# Проверяем, существует ли папка Organize
if [ ! -d "$target_dir" ]; then
warn "Папка Organize не найдена: $target_dir"
echo "💡 Сначала создайте её через пункт 4 главного меню."
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

cd "$target_dir" || {
error "Не удалось перейти в папку: $target_dir"
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
}

local files_found=false
local success_count=0
local skip_count=0

echo ""
log "Начинаю разбор PDF по папкам в: $target_dir"
echo "------------------------------------------"

for file in *.pdf *.PDF; do
[ ! -f "$file" ] && continue

files_found=true

local filename="${file%.*}"

# Твоя логика очистки имени (оставляем как у тебя)
local dirname
dirname=$(echo "$filename" | sed 's/[/?:*|"<>]/_/g; s/%/percent/g; s/|/pipe/g')
dirname=$(echo "$dirname" | sed 's/_\+/_/g; s/^_*//; s/_*$//')

if [ -z "$dirname" ]; then
echo "⚠️ Пропущен файл с пустым именем: $file"
((skip_count++))
continue
fi

mkdir -p "$dirname"

if mv "$file" "$dirname/" 2>/dev/null; then
log "✅ Перемещён: $file → $dirname/"
((success_count++))
else
error "❌ Не удалось переместить: $file"
((skip_count++))
fi
done

echo ""
echo "=========================================="
if [ "$files_found" = false ]; then
warn "❌ PDF-файлы не найдены в папке: $target_dir"
echo "💡 Файлы внутри подпапок этим пунктом не обрабатываются."
else
printf "📊 Результат:\n"
printf " ✅ Успешно перемещено: %d\n" "$success_count"
if (( skip_count > 0 )); then
printf " ⚠️ Ошибки/пропуски: %d\n" "$skip_count"
fi
echo "✅ Готово!"
fi
echo "=========================================="
read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

clear_organize_folder() {
local target_dir="$BASE_DIR/Organize"

# Проверяем, существует ли папка
if [ ! -d "$target_dir" ]; then
warn "Папка Organize не найдена: $target_dir"
echo "💡 Сначала создайте её через пункт 4 главного меню."
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

# Считаем, что будем удалять
local item_count
item_count=$(find "$target_dir" -mindepth 1 | wc -l)

if [ "$item_count" -eq 0 ]; then
log "В папке Organize нет содержимого для очистки."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
return 0
fi

echo ""
warn "⚠️ Начинаю полную очистку папки: $target_dir"
printf " 🗑️ Будет удалено объектов: %d\n" "$item_count"
echo "------------------------------------------"

# Удаляем ВСЁ содержимое папки (файлы и подпапки), но не саму папку Organize
rm -rf "$target_dir"/*

if [ $? -eq 0 ]; then
log "✅ Папка Organize полностью очищена."
else
error "❌ Произошла ошибка при очистке папки."
fi

read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

copy_special_symbols() {
# Цвета для оформления
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Общий массив символов с разбивкой по категориям
declare -a symbols
declare -a categories

categories[0]="Надстрочные цифры (суперскрипты)"
symbols[0]="¹"; symbols[1]="²"; symbols[2]="³"; symbols[3]="⁴"; symbols[4]="⁵"
symbols[5]="⁶"; symbols[6]="⁷"; symbols[7]="⁸"; symbols[8]="⁹"; symbols[9]="⁰"

categories[1]="Математические символы"
symbols[10]="∑"; symbols[11]="∏"; symbols[12]="∫"; symbols[13]="∂"; symbols[14]="∇"
symbols[15]="∆"; symbols[16]="∞"; symbols[17]="≈"; symbols[18]="≠"; symbols[19]="≤"
symbols[20]="≥"; symbols[21]="∈"; symbols[22]="∉"; symbols[23]="⊂"; symbols[24]="⊃"
symbols[25]="∩"; symbols[26]="∪"; symbols[27]="√"; symbols[28]="∛"; symbols[29]="∜"

categories[2]="Греческие буквы"
symbols[30]="α"; symbols[31]="β"; symbols[32]="γ"; symbols[33]="δ"; symbols[34]="ε"
symbols[35]="ζ"; symbols[36]="η"; symbols[37]="θ"; symbols[38]="ι"; symbols[39]="κ"
symbols[40]="λ"; symbols[41]="μ"; symbols[42]="ν"; symbols[43]="ξ"; symbols[44]="ο"
symbols[45]="π"; symbols[46]="ρ"; symbols[47]="σ"; symbols[48]="τ"; symbols[49]="υ"
symbols[50]="φ"; symbols[51]="χ"; symbols[52]="ψ"; symbols[53]="ω"

categories[3]="Стрелки"
symbols[54]="←"; symbols[55]="→"; symbols[56]="↑"; symbols[57]="↓"; symbols[58]="↔"
symbols[59]="↕"; symbols[60]="⇐"; symbols[61]="⇒"; symbols[62]="⇑"; symbols[63]="⇓"
symbols[64]="⇔"; symbols[65]="⇕"

categories[4]="Знаки препинания и спецсимволы"
symbols[66]="©"; symbols[67]="®"; symbols[68]="™"; symbols[69]="§"; symbols[70]="¶"
symbols[71]="†"; symbols[72]="‡"; symbols[73]="•"; symbols[74]="·"; symbols[75]="…"

# Функция копирования в буфер обмена
copy_to_clipboard() {
local symbol="$1"
if command -v xclip &> /dev/null; then
echo -n "$symbol" | xclip -selection clipboard
echo -e "${GREEN}Скопировано: $symbol${NC}"
elif command -v xsel &> /dev/null; then
echo -n "$symbol" | xsel --clipboard --input
echo -e "${GREEN}Скопировано: $symbol${NC}"
else
echo -e "${RED}Нет утилиты для копирования. Скопируйте символ вручную: ${NC}$symbol"
fi
}

# Вывод списка с разбивкой по категориям
show_symbols() {
local idx=0
local cat_idx=0

for category in "${categories[@]}"; do
echo -e "${BLUE}${category}:${NC}"

# Количество символов в каждой категории
case $cat_idx in
0) count=10;; # Надстрочные цифры
1) count=20;; # Математические символы
2) count=24;; # Греческие буквы
3) count=12;; # Стрелки
4) count=10;; # Знаки препинания
esac

for ((i=0; i<count; i++)); do
printf " %2d) %s\n" "$((idx + 1))" "${symbols[$idx]}"
((idx++))
done

echo ""
((cat_idx++))
done
}

while true; do
clear
echo ""
echo -e "${YELLOW}=== ВЫБОР СПЕЦСИМВОЛОВ (расширенный список) ===${NC}"
echo ""
show_symbols

echo -e "${YELLOW}Как использовать:${NC}"
echo "1. Найдите нужный символ в списке."
echo "2. Введите его номер для копирования."
echo "3. Для выхода нажмите Enter или введите 'q'."
echo ""

read -r -p "> " choice

# Выход по нажатию Enter
if [ -z "$choice" ]; then
break
fi

case $choice in
q|Q)
break
;;
[1-9]|[1-7][0-5]) # Номера от 1 до 75
if [ "$choice" -ge 1 ] && [ "$choice" -le 75 ]; then
selected_symbol="${symbols[$((choice-1))]}"
copy_to_clipboard "$selected_symbol"
else
echo -e "${RED}Неверный номер. Попробуйте снова.${NC}"
fi
;;
*)
echo -e "${RED}Неверный ввод. Введите номер символа, 'q' или нажмите Enter для выхода.${NC}"
;;
esac
echo ""
read -p "Нажмите Enter, чтобы выбрать другой символ или выйти..." _
done
}

main() {
check_gs

while true; do
show_main_menu
read -rp "Выберите пункт: " choice

case "$choice" in
1) create_base_and_folders ;;
2) create_epson_folder ;;
3) create_compress_folder ;;
4) create_organize_folder ;;
5) submenu_pdf_compress ;;
6) merge_images_to_pdf ;;
7) extract_from_target_folders ;;
8) rename_accompanying_file ;;
9) delete_numbered_folders ;;
10) organize_pdf_by_name ;;
11) copy_special_symbols ;;
12) clear_organize_folder ;;
0)
echo "Выход из программы."
exit 0
;;
*)
warn "Неверный пункт. Попробуйте снова."
sleep 1
;;
esac
done
}

# Запуск
main

---------------------------------------------------------------------------------------------------------------------------------------------
Воспользуйтесь терминалом вызовите его сочетанием клавиш Ctrl+Alt+T и просто перекиньте этот фаил в терминал из окна рабочего стола или проводника смотря где вы его создавали.

Примечание !!!!!!!!
Фаил может не сработать если не стоит в свойствах файла галочка Является выполняемым и не стоят права на просмотр и изменение.
Скопируйте этот код с этой старницы и сохраните его как menuofforganize.sh


ВАРИАНТ 2
Скрипт все тоже самое что описано выше отсутствует функция создания и работы с папкой organize
ОБЯЗАТЕЛЬНО ПРОПИСАТЬ ПУТЬ ДО ПАПКИ В УТИЛИТЕ EPSON SCAN НАПРИМЕР доки из архивов/EPSON и указать имя файла сопровод по окончании сканирования этот фаил всегда будет создаваться с именем сопровод.pdf
Создайте фаил в программе Kwrite c именем menu.sh и добавьте в его содержание следующий код:

#!/bin/bash

# ==========================================
# НАСТРОЙКИ И ПЕРЕМЕННЫЕ
# ==========================================
BASE_DIR="$HOME/Рабочий стол/Доки из архивов"

# Цвета для красоты
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

log() { echo -e "${GREEN}[INFO]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }

# Проверка наличия ghostscript
check_gs() {
if ! command -v gs &> /dev/null; then
echo ""
error "Ghostscript не установлен! Без него сжатие PDF невозможно."
echo "Установите командой: sudo apt-get install ghostscript"
echo ""
return 1
fi
return 0
}

# ==========================================
# ПОДМЕНЮ: СЖАТИЕ PDF (ВСТРОЕННОЕ)
# ==========================================
submenu_pdf_compress() {
local pdf_dir="$BASE_DIR/Compress"
local pdf_files=()
local selected_files=()

if [[ ! -d "$pdf_dir" ]]; then
error "Папка Compress не найдена: $pdf_dir. Сначала создайте её через пункт 3 главного меню."
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

find_pdf_files() {
pdf_files=()
local i=1
for file in "$pdf_dir"/*.pdf; do
if [[ -f "$file" ]]; then
pdf_files+=("$file")
echo " $i) $(basename "$file")"
((i++))
fi
done
if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo " В папке Compress нет PDF-файлов."
else
echo " Найдено PDF-файлов: ${#pdf_files[@]}"
fi
}

compress_one() {
local input_file="$1"
local output_file="$pdf_dir/compressed_$(basename "$input_file")"

echo "Сжимаю: $(basename "$input_file")..."

gs -sDEVICE=pdfwrite \
-dCompatibilityLevel=1.4 \
-dPDFSETTINGS=/ebook \
-dNOPAUSE -dQUIET -dBATCH \
-sOutputFile="$output_file" \
"$input_file"

if [[ $? -eq 0 ]]; then
local original_size=$(stat -c%s "$input_file")
local compressed_size=$(stat -c%s "$output_file")
local savings=$((original_size - compressed_size))

if command -v bc &> /dev/null; then
local percent
percent=$(echo "scale=2; $savings * 100 / $original_size" | bc -l 2>/dev/null || echo 0)
printf " ✓ Успешно: экономия %d байт (%.1f%%)\n" "$savings" "$percent"
else
printf " ✓ Успешно: экономия %d байт\n" "$savings"
fi
else
echo " ✗ Ошибка при сжатии $(basename "$input_file")"
rm -f "$output_file"
fi
}

delete_files() {
local to_delete=("$@")

if [[ ${#to_delete[@]} -eq 0 ]]; then
echo "Ничего не выбрано для удаления."
return
fi

echo ""
echo "⚠️ ВНИМАНИЕ: Будут удалены следующие файлы:"
for f in "${to_delete[@]}"; do
echo " - $(basename "$f")"
done
echo ""
read -p "Вы уверены? Введите 'ДА' (или y) для подтверждения, любой другой текст для отмены: " confirm

if [[ "$confirm" == "ДА" || "$confirm" == "да" || "$confirm" == "y" || "$confirm" == "Y" ]]; then
for f in "${to_delete[@]}"; do
if [[ -f "$f" ]]; then
rm -f "$f"
log "Удален: $(basename "$f")"
fi
done
echo ""
echo "✅ Файлы удалены!"
else
echo "Удаление отменено."
fi
read -p "Нажмите Enter, чтобы продолжить..." _
}

while true; do
clear
echo ""
echo "=========================================="
echo " МЕНЮ СЖАТИЯ PDF-ФАЙЛОВ"
echo "=========================================="
echo "1) Сжать ВСЕ файлы в папке Compress"
echo "2) Выбрать конкретные файлы по номеру для сжатия"
echo "3) Удалить несжатые файлы (выбор по списку)"
echo "4) Назад в главное меню"
echo "=========================================="

read -p "Выберите пункт (1-4): " choice

case "$choice" in
1)
echo ""
echo "Список файлов для сжатия:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов. Нажмите Enter, чтобы вернуться..." _
continue
fi

echo ""
echo "Начинаю сжатие всех файлов..."
echo "------------------------------------------"
for file in "${pdf_files[@]}"; do
compress_one "$file"
done
echo ""
echo "✅ Все файлы сжаты!"
read -p "Нажмите Enter, чтобы продолжить..." _
;;

2)
echo ""
echo "Сначала покажем список файлов:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов для выбора. Нажмите Enter..." _
continue
fi

echo ""
echo "Введите номер(а) файла(ов) через пробел (например: 1 3 5)."
echo "Чтобы отменить, просто нажмите Enter без ввода."
read -r selection

if [[ -z "$selection" ]]; then
echo "Отмена."
read -p "Нажмите Enter..." _
continue
fi

selected_files=()
local error=0

for num in $selection; do
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "❌ Ошибка: '$num' — не номер."
error=1
break
fi
if [[ "$num" -lt 1 || "$num" -gt "${#pdf_files[@]}" ]]; then
echo "❌ Ошибка: номер '$num' вне диапазона (1-${#pdf_files[@]})."
error=1
break
fi
selected_files+=("${pdf_files[$((num-1))]}")
done

if [[ $error -eq 1 ]]; then
read -p "Нажмите Enter, чтобы попробовать снова..." _
continue
fi

if [[ ${#selected_files[@]} -eq 0 ]]; then
echo "Ничего не выбрано."
read -p "Нажмите Enter..." _
continue
fi

echo ""
echo "Выбрано: ${#selected_files[@]} файлов. Начинаю сжатие..."
echo "------------------------------------------"
for file in "${selected_files[@]}"; do
compress_one "$file"
done
echo ""
echo "✅ Выбранные файлы сжаты!"
read -p "Нажмите Enter, чтобы продолжить..." _
;;

3)
echo ""
echo "Сначала покажем список всех PDF в папке:"
find_pdf_files

if [[ ${#pdf_files[@]} -eq 0 ]]; then
echo ""
read -p "Нет файлов для удаления. Нажмите Enter..." _
continue
fi

echo ""
echo "Введите номера файлов, которые нужно УДАЛИТЬ (через пробел)."
echo "⚠️ Будьте осторожны: это действие необратимо!"
echo "Чтобы отменить, просто нажмите Enter."
read -r selection

if [[ -z "$selection" ]]; then
echo "Отмена удаления."
read -p "Нажмите Enter..." _
continue
fi

selected_files=()
local error=0

for num in $selection; do
if ! [[ "$num" =~ ^[0-9]+$ ]]; then
echo "❌ Ошибка: '$num' — не номер."
error=1
break
fi
if [[ "$num" -lt 1 || "$num" -gt "${#pdf_files[@]}" ]]; then
echo "❌ Ошибка: номер '$num' вне диапазона (1-${#pdf_files[@]})."
error=1
break
fi
selected_files+=("${pdf_files[$((num-1))]}")
done

if [[ $error -eq 1 ]]; then
read -p "Нажмите Enter, чтобы попробовать снова..." _
continue
fi

if [[ ${#selected_files[@]} -eq 0 ]]; then
echo "Ничего не выбрано."
read -p "Нажмите Enter..." _
continue
fi

delete_files "${selected_files[@]}"
;;

4)
echo "Возврат в главное меню."
break
;;

*)
echo "Неверный выбор. Введите 1, 2, 3 или 4."
read -p "Нажмите Enter..." _
;;
esac
done
}

# ==========================================
# ГЛАВНОЕ МЕНЮ И ФУНКЦИИ
# ==========================================

show_main_menu() {
clear
echo ""
echo "=== МЕНЮ (Alt Basalt для работы с документами Делопроизводство) ==="
echo "1. Создать каталог «Доки из архивов» и папки 1–N (выбор количества)"
echo "2. Создать папку Epson внутри «Доки из архивов»"
echo "3. Создать папку Compress внутри «Доки из архивов»"
echo "4. Сжатие PDF (отдельное подменю)"
echo "5. Выполнить скрипт объединения jpg/bmp в единый PDF"
echo "6. Извлечь документы из архивов (zip/tar.gz/7z)"
echo "7. Переименовать файл сопровод.pdf в папке Epson"
echo "8. Удалить папки с номерами 1-6 (и выше) в «Доки из архивов»"
echo "9. Выбрать и скопировать спецсимволы"
echo "0. Выход"
echo ""
}

create_base_and_folders() {
log "Создание базовой папки: $BASE_DIR"
mkdir -p "$BASE_DIR" || { error "Не удалось создать папку! Проверьте права доступа."; return 1; }

read -p "Сколько папок (1-N) нужно создать внутри? (например, 5): " count
if ! [[ "$count" =~ ^[0-9]+$ ]] || [ "$count" -le 0 ]; then
warn "Некорректное число. Папки не созданы."
return
fi

for ((i=1; i<=count; i++)); do
mkdir -p "$BASE_DIR/$i"
log "Создана папка: $BASE_DIR/$i"
done
log "Готово!"
}

create_epson_folder() {
mkdir -p "$BASE_DIR/Epson"
log "Папка Epson создана: $BASE_DIR/Epson"
}

create_compress_folder() {
mkdir -p "$BASE_DIR/Compress"
log "Папка Compress создана: $BASE_DIR/Compress"
}

merge_images_to_pdf() {
local base_dir="$BASE_DIR"

# Проверка наличия python3
if ! command -v python3 &> /dev/null; then
error "Не найден python3! Без него этот пункт не работает."
read -p "Нажмите Enter, чтобы продолжить..." _
return 1
fi

python3 - "$base_dir" <<'PYEOF'
import sys, os, struct, zlib, io

class SimplePDF:
def __init__(self):
self.objects = []

def add_obj(self, content):
self.objects.append(content)
return len(self.objects)

def build(self):
buf = io.BytesIO()
# Заголовок PDF
buf.write(b"%PDF-1.4\n%\xE2\xE3\xCF\xD3\n")

offsets = []
for i, obj_content in enumerate(self.objects):
offsets.append(buf.tell())
buf.write(f"{i+1} 0 obj\n".encode('latin-1'))
buf.write(obj_content)
buf.write(b"\nendobj\n")

xref_start = buf.tell()
total_objs = len(self.objects) + 1

buf.write(f"xref\n0 {total_objs}\n".encode('latin-1'))
buf.write(b"0000000000 65535 f \n")
for off in offsets:
buf.write(f"{off:010d} 00000 n \n".encode('latin-1'))

buf.write(f"trailer\n<< /Size {total_objs} /Root 1 0 R >>\nstartxref\n{xref_start}\n%%EOF".encode('latin-1'))
return buf.getvalue()

def create_folder_pdf(image_paths, output_path):
pdf = SimplePDF()

# 1. Catalog
pdf.add_obj(b"<< /Type /Catalog /Pages 2 0 R >>")
# 2. Pages (placeholder)
pdf.add_obj(b"")

page_ids = []

for img_path in image_paths:
ext = os.path.splitext(img_path)[1].lower()
stream = None
filter_type = None

try:
# Читаем файл целиком как бинарные данные
with open(img_path, 'rb') as f:
stream = f.read()

if ext in ['.jpg', '.jpeg', '.jpe', '.jfif']:
# ВАЖНО: Мы НЕ читаем размеры!
# PDF сам поймет размеры из потока JPEG при рендере.
# Но для структуры PDF нам все равно нужно указать Width/Height.
# Так как мы не можем их надежно прочитать, мы используем хитрость:
# Вставляем картинку на страницу 100x100, а потом масштабируем её
# через матрицу трансформации, чтобы она влезла.
# НО! Самый простой способ для сканов — использовать оператор Do без явных размеров в MediaBox,
# а задать размеры страницы под картинку.

# Поскольку мы не знаем размеры, мы сделаем страницу "бесконечной" (условно большой)
# и положим картинку в угол. Большинство просмотрщиков покажут её корректно.
# Однако, стандарт PDF требует Width/Height в словаре изображения.
# Если мы их не укажем, PDF будет битым.

# ХАК ДЛЯ СКАНОВ: Попробуем прочитать размеры самым простым способом (первые байты)
# Если не выйдет — поставим заглушку 1000x1000. Для сканов это не критично,
# так как картинка будет масштабироваться.
w, h = 1000, 1000
try:
# Очень простая попытка найти SOF маркер
if len(stream) > 10:
pos = 2
while pos < len(stream) - 9:
if stream[pos] == 0xFF:
marker = stream[pos+1]
if marker in (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF):
h = struct.unpack('>H', stream[pos+5:pos+7])[0]
w = struct.unpack('>H', stream[pos+7:pos+9])[0]
break
pos += 1
except:
pass

filter_type = b"/DCTDecode"

elif ext == '.bmp':
# Для BMP тоже попробуем прочитать заголовок, если нет - заглушка
w, h = 1000, 1000
if len(stream) >= 26:
try:
width = struct.unpack_from('<i', stream, 18)[0]
height = abs(struct.unpack_from('<i', stream, 22)[0])
if width > 0 and height > 0:
w, h = width, height
except:
pass

# Конвертируем BMP в RGB и сжимаем
# (Упрощенная логика конвертации для надежности)
header_size = struct.unpack_from('<I', stream, 14)[0]
offset = struct.unpack_from('<I', stream, 10)[0]
planes = struct.unpack_from('<H', stream, 26)[0]
bit_count = struct.unpack_from('<H', stream, 28)[0]
compression = struct.unpack_from('<I', stream, 30)[0]

if planes == 1 and compression == 0 and bit_count == 24:
row_size = ((w * bit_count + 31) // 32) * 4
rgb = bytearray()
for y in range(h):
src_y = h - 1 - y # BMP хранится снизу вверх
row_offset = offset + src_y * row_size
for x in range(w):
off = row_offset + x * 3
b = stream[off]
g = stream[off+1]
r = stream[off+2]
rgb.append(r); rgb.append(g); rgb.append(b)
stream = zlib.compress(bytes(rgb), 6)
filter_type = b"/FlateDecode"
else:
print(f" ! Неподдерживаемый BMP формат: {os.path.basename(img_path)}", file=sys.stderr)
continue
else:
continue

# Создаем объект изображения
# Даже если размеры приблизительные (1000x1000), картинка отобразится,
# просто может потребоваться зум в просмотрщике.
img_hdr = f"<< /Type /XObject /Subtype /Image /Width {w} /Height {h} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter ".encode('latin-1')
img_hdr += filter_type + f" /Length {len(stream)} >>".encode('latin-1')

img_obj = img_hdr + b"\nstream\n" + stream + b"\nendstream"
img_id = pdf.add_obj(img_obj)

# Контент страницы: рисуем картинку на весь лист (масштабируем если нужно)
# Матрица: w 0 0 h 0 0 -> растягивает картинку на размер страницы [0 0 w h]
content_stream = f"q\n1 0 0 1 0 0 cm\n/Im1 Do\nQ".encode('latin-1')
cs_obj = f"<< /Length {len(content_stream)} >>\nstream\n".encode('latin-1') + content_stream + b"\nendstream"
cs_id = pdf.add_obj(cs_obj)

# Страница: MediaBox должен быть достаточно большим, чтобы вместить картинку
# Если размеры были заглушкой (1000), страница будет 1000x1000.
page_obj = f"<< /Type /Page /Parent 2 0 R /MediaBox [0 0 {w} {h}] /Contents {cs_id} 0 R /Resources << /XObject << /Im1 {img_id} 0 R >> >> >>".encode('latin-1')
page_id = pdf.add_obj(page_obj)
page_ids.append(page_id)

except Exception as e:
print(f" ! Критическая ошибка обработки {os.path.basename(img_path)}: {e}", file=sys.stderr)
continue

if not page_ids:
return False

kids_str = " ".join(f"{pid} 0 R" for pid in page_ids)
pages_obj = f"<< /Type /Pages /Kids [{kids_str}] /Count {len(page_ids)} >>".encode('latin-1')
pdf.objects[1] = pages_obj

with open(output_path, 'wb') as f:
f.write(pdf.build())
return True

# === ГЛАВНАЯ ЛОГИКА ===
base_dir = sys.argv[1]

if not os.path.isdir(base_dir):
print(f"Ошибка: папка {base_dir} не существует", file=sys.stderr)
sys.exit(1)

folders = []
for item in os.listdir(base_dir):
full_path = os.path.join(base_dir, item)
if os.path.isdir(full_path) and item.isdigit():
folders.append((int(item), full_path))

folders.sort(key=lambda x: x[0])

print("🔍 Поиск картинок и создание PDF по папкам:")
success_count = 0

for num, folder_path in folders:
images = []
for fname in os.listdir(folder_path):
if fname.lower().endswith(('.jpg', '.jpeg', '.jpe', '.jfif', '.bmp')):
images.append(os.path.join(folder_path, fname))

if not images:
print(f" ⚠️ Папка {num}: нет картинок")
continue

images.sort(key=os.path.basename)

print(f" ✅ Папка {num}: найдено {len(images)} файлов. Создаю PDF...")

out_pdf = os.path.join(folder_path, f"folder_{num}.pdf")

if create_folder_pdf(images, out_pdf):
size = os.path.getsize(out_pdf)
print(f" 📄 Готово: {out_pdf} ({size} байт)")
success_count += 1
else:
print(f" ❌ Ошибка: не удалось создать PDF для папки {num}")

if success_count == 0:
print("\n❌ Не создано ни одного PDF.")
sys.exit(1)
else:
print(f"\n🎉 Успешно создано PDF файлов: {success_count}")
PYEOF

read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

extract_archives() {
local base_dir="$BASE_DIR"
# Папки, которые проверяем. Можно расширить до 10, 20 — как нужно.
local folders=(1 2 3 4 5 6)
local total_processed=0
local skipped=0

if [[ ! -d "$base_dir" ]]; then
warn "Папка $base_dir не найдена. Сначала создайте её через пункт 1 главного меню."
read -p "Нажмите Enter, чтобы вернуться..." _
return 1
fi

clear
echo ""
echo "=== РАСПАКОВКА С СОХРАНЕНИЕМ СТРУКТУРЫ + УДАЛЕНИЕМ АРХИВОВ ==="
echo "Папки внутри архивов сохраняются, архивы удаляются только при успехе."
echo "Рабочая папка: $base_dir"
echo "------------------------------------------"

for folder in "${folders[@]}"; do
local full_path="$base_dir/$folder"

if [[ ! -d "$full_path" ]]; then
warn "Папка '$folder' не найдена, пропускаем."
continue
fi

echo ""
log "Обрабатываем папку: $folder (путь: $full_path)"

# ---------------------------------------------------------
# ЭТАП 1: ZIP (.zip)
shopt -s nullglob
for zip_file in "$full_path"/*.zip "$full_path"/*.ZIP "$full_path"/*.Zip; do
[[ -f "$zip_file" ]] || continue

log "📦 ZIP: $(basename "$zip_file")"

# -o: перезаписать без вопросов, структуру папок сохраняем (без флага -j)
if unzip -o -d "$full_path" "$zip_file" > /dev/null 2>&1; then
# Распаковка успешна → удаляем архив
rm -f "$zip_file"
((total_processed++))
log "✅ Успешно: структура сохранена, архив удалён"
else
warn "⚠️ Ошибка ZIP — архив НЕ удалён (проверьте содержимое)"
((skipped++))
fi
done
shopt -u nullglob

# ---------------------------------------------------------
# ЭТАП 2: TAR.GZ / TGZ (.tar.gz, .tgz)
shopt -s nullglob
for tar_file in "$full_path"/*.tar.gz "$full_path"/*.tgz "$full_path"/*.TAR.GZ; do
[[ -f "$tar_file" ]] || continue

log "📦 TAR.GZ: $(basename "$tar_file")"

# Структура сохраняется автоматически, ничего дополнительно делать не надо
if tar -xzf "$tar_file" -C "$full_path"; then
rm -f "$tar_file"
((total_processed++))
log "✅ Успешно: структура сохранена, архив удалён"
else
warn "⚠️ Ошибка TAR.GZ — архив НЕ удалён"
((skipped++))
fi
done
shopt -u nullglob

# ---------------------------------------------------------
# ЭТАП 3: 7Z (.7z)
if command -v 7z &> /dev/null; then
shopt -s nullglob
for sevenz_file in "$full_path"/*.7z "$full_path"/*.7Z; do
[[ -f "$sevenz_file" ]] || continue

log "📦 7Z: $(basename "$sevenz_file")"

# -aoa: перезаписать все без вопросов
if 7z x -aoa -o"$full_path" "$sevenz_file" > /dev/null 2>&1; then
rm -f "$sevenz_file"
((total_processed++))
log "✅ Успешно: 7Z распакован, архив удалён"
else
warn "⚠️ Ошибка 7Z — архив НЕ удалён"
((skipped++))
fi
done
shopt -u nullglob
else
warn "Утилита '7z' не найдена — пропускаем архивы .7z"
fi
done

echo ""
echo "=========================================="
printf "📊 Результат:\n"
printf " ✅ Обработано и удалено: %d\n" "$total_processed"
if (( skipped > 0 )); then
printf " ⚠️ Ошибки (архивы сохранены): %d\n" "$skipped"
fi
echo "=========================================="
echo ""
echo "💡 Папки из архивов сохранены."
echo " Успешные архивы удалены, битые — оставлены для проверки."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
}

rename_accompanying_file() {
local dir="$BASE_DIR/Epson"
local prefix_old="сопровод"
local prefix_new="приложение"
local max_count=15

if [[ ! -d "$dir" ]]; then
warn "Папка Epson не найдена: $dir"
read -p "Нажмите Enter, чтобы продолжить..." _
return 1
fi

while true; do
clear
echo ""
echo "=== ПЕРЕИМЕНОВАНИЕ В ПАПКЕ EPSON ==="
echo "1) Жёстко: сопровод.pdf → конверт.pdf"
echo "2) Пакетно: сохранить цифру из имени (сопроводX → приложениеX)"
echo "3) Очистить папку Epson (удалить ВСЕ файлы)"
echo "4) Назад в главное меню"
echo ""

read -p "Выберите пункт (1-4): " choice

case "$choice" in
1)
# Жёсткое переименование в конверт.pdf
local old_path="$dir/сопровод.pdf"
local new_path="$dir/конверт.pdf"

if [[ ! -f "$old_path" ]]; then
warn "Файл $old_path не найден."
read -p "Нажмите Enter..." _
continue
fi
if [[ -f "$new_path" ]]; then
error "Файл $new_path уже существует! Переименование отменено."
read -p "Нажмите Enter..." _
continue
fi

if mv "$old_path" "$new_path"; then
log "Переименовано: сопровод.pdf → конверт.pdf"
else
error "Не удалось переименовать файл."
fi
read -p "Нажмите Enter, чтобы вернуться..." _
;;

2)
# ПАКЕТНОЕ: логика "цифра из старого имени = цифра в новом"
local files=()
local names=()

# Ищем базовый файл без цифры
if [[ -f "$dir/${prefix_old}.pdf" ]]; then
files+=("$dir/${prefix_old}.pdf")
names+=("${prefix_old}.pdf")
fi

# Ищем файлы с цифрами 1..15
for ((i=1; i<=max_count; i++)); do
local fname="${prefix_old}${i}.pdf"
if [[ -f "$dir/$fname" ]]; then
files+=("$dir/$fname")
names+=("$fname")
fi
done

if [[ ${#files[@]} -eq 0 ]]; then
warn "В папке Epson нет файлов вида ${prefix_old}*.pdf"
read -p "Нажмите Enter, чтобы вернуться..." _
continue
fi

echo ""
echo "Список доступных файлов:"
for i in "${!names[@]}"; do
printf " %d) %s\n" "$((i+1))" "${names[i]}"
done
echo ""

read -p "Введите номера файлов для переименования (через пробел): " selection

if [[ -z "$selection" ]]; then
echo "Отмена."
read -p "Нажмите Enter..." _
continue
fi

local selected_indices=()
for num in $selection; do
# Валидация номера
if [[ "$num" =~ ^[0-9]+$ ]] && (( num >= 1 && num <= ${#files[@]} )); then
selected_indices+=("$num")
else
warn "Пропущен неверный номер: '$num'"
fi
done

if [[ ${#selected_indices[@]} -eq 0 ]]; then
echo "Ничего не выбрано для обработки."
read -p "Нажмите Enter..." _
continue
fi

echo ""
echo "Начинаю обработку выбранных файлов..."
echo "------------------------------------------"

local success_count=0
local skip_count=0

for idx in "${selected_indices[@]}"; do
local real_idx=$((idx - 1))
local old_full_path="${files[real_idx]}"
local old_name="${names[real_idx]}"

# --- ГЛАВНАЯ ЛОГИКА ИЗВЛЕЧЕНИЯ ЦИФРЫ ---
local number=""

if [[ "$old_name" == "${prefix_old}.pdf" ]]; then
number=""
elif [[ "$old_name" =~ ^${prefix_old}([0-9]+)\.pdf$ ]]; then
number="${BASH_REMATCH[1]}"
else
warn "Не удалось распознать номер в файле: $old_name. Пропущено."
((skip_count++))
continue
fi
# -----------------------------------------

# Формируем новое имя
local new_name
if [[ -z "$number" ]]; then
new_name="${prefix_new}.pdf"
else
new_name="${prefix_new}${number}.pdf"
fi

local new_full_path="$dir/$new_name"

# Проверка: существует ли уже целевой файл?
if [[ -f "$new_full_path" ]]; then
echo "⚠️ ПРОПУЩЕНО: $new_name уже существует в папке. Не перезаписываем."
((skip_count++))
continue
fi

# Выполняем переименование
if mv "$old_full_path" "$new_full_path"; then
log "OK: $old_name → $new_name"
((success_count++))
else
error "Ошибка при переименовании: $old_name"
((skip_count++))
fi
done

echo ""
echo "✅ Успешно переименовано: $success_count"
if (( skip_count > 0 )); then
echo "⚠️ Пропущено (файл уже был или ошибка): $skip_count"
fi
echo ""
read -p "Нажмите Enter, чтобы вернуться..." _
;;

3)
# ОЧИСТКА ПАПКИ EPSON: удалить все файлы БЕЗ подтверждения
local file_count=0
local deleted_files=()

# Считаем файлы и собираем их список
while IFS= read -r -d '' file; do
((file_count++))
deleted_files+=("$file")
done < <(find "$dir" -maxdepth 1 -type f -print0)

if [[ $file_count -eq 0 ]]; then
echo ""
log "В папке Epson нет файлов для удаления."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
continue
fi

clear
echo ""
echo "=========================================="
echo " ОЧИСТКА ПАПКИ EPSON"
echo "=========================================="
echo "Папка: $dir"
echo "Найдено файлов: $file_count"
echo ""
echo "Начинаю удаление..."
echo "------------------------------------------"

# Удаляем и сразу запоминаем имена для отчёта
for f in "${deleted_files[@]}"; do
if rm -f "$f"; then
log "Удален: $(basename "$f")"
else
error "Не удалось удалить: $(basename "$f")"
fi
done

echo ""
echo "✅ Все файлы из папки Epson удалены."
echo ""
read -p "Нажмите Enter, чтобы вернуться в главное меню..." _
;;

4)
echo "Возврат в главное меню."
return
;;

*)
echo "Неверный выбор. Введите 1, 2, 3 или 4."
read -p "Нажмите Enter..." _
;;
esac
done
}

delete_numbered_folders() {
# Ищем папки с именами, состоящими только из цифр (1, 2, 3... 10 и т.д.)
local folders_to_delete=()

for item in "$BASE_DIR"/*; do
if [[ -d "$item" ]] && [[ "$(basename "$item")" =~ ^[0-9]+$ ]]; then
folders_to_delete+=("$item")
fi
done

if [[ ${#folders_to_delete[@]} -eq 0 ]]; then
echo ""
warn "В папке $BASE_DIR нет папок с цифровыми именами (1, 2, 3...)."
read -p "Нажмите Enter, чтобы вернуться в меню..." _
return
fi

echo ""
echo "⚠️ НАЙДЕНЫ СЛЕДУЮЩИЕ ПАПКИ ДЛЯ УДАЛЕНИЯ:"
for f in "${folders_to_delete[@]}"; do
echo " - $(basename "$f")"
done
echo ""
read -p "ПАПКИ БУДУТ УДАЛЕНЫ. Нажмите Enter для подтверждения (Ctrl+C для отмены): " confirm

# Если пользователь просто нажал Enter — удаляем. Если Ctrl+C — скрипт прервётся сам.
for f in "${folders_to_delete[@]}"; do
rm -rf "$f"
log "Удалена папка: $(basename "$f")"
done

echo ""
echo "✅ Папки успешно удалены!"
read -p "Нажмите Enter, чтобы вернуться в главное меню..." _
}

copy_special_symbols() {
# Цвета для оформления
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Общий массив символов с разбивкой по категориям
declare -a symbols
declare -a categories

categories[0]="Надстрочные цифры (суперскрипты)"
symbols[0]="¹"; symbols[1]="²"; symbols[2]="³"; symbols[3]="⁴"; symbols[4]="⁵"
symbols[5]="⁶"; symbols[6]="⁷"; symbols[7]="⁸"; symbols[8]="⁹"; symbols[9]="⁰"

categories[1]="Математические символы"
symbols[10]="∑"; symbols[11]="∏"; symbols[12]="∫"; symbols[13]="∂"; symbols[14]="∇"
symbols[15]="∆"; symbols[16]="∞"; symbols[17]="≈"; symbols[18]="≠"; symbols[19]="≤"
symbols[20]="≥"; symbols[21]="∈"; symbols[22]="∉"; symbols[23]="⊂"; symbols[24]="⊃"
symbols[25]="∩"; symbols[26]="∪"; symbols[27]="√"; symbols[28]="∛"; symbols[29]="∜"

categories[2]="Греческие буквы"
symbols[30]="α"; symbols[31]="β"; symbols[32]="γ"; symbols[33]="δ"; symbols[34]="ε"
symbols[35]="ζ"; symbols[36]="η"; symbols[37]="θ"; symbols[38]="ι"; symbols[39]="κ"
symbols[40]="λ"; symbols[41]="μ"; symbols[42]="ν"; symbols[43]="ξ"; symbols[44]="ο"
symbols[45]="π"; symbols[46]="ρ"; symbols[47]="σ"; symbols[48]="τ"; symbols[49]="υ"
symbols[50]="φ"; symbols[51]="χ"; symbols[52]="ψ"; symbols[53]="ω"

categories[3]="Стрелки"
symbols[54]="←"; symbols[55]="→"; symbols[56]="↑"; symbols[57]="↓"; symbols[58]="↔"
symbols[59]="↕"; symbols[60]="⇐"; symbols[61]="⇒"; symbols[62]="⇑"; symbols[63]="⇓"
symbols[64]="⇔"; symbols[65]="⇕"

categories[4]="Знаки препинания и спецсимволы"
symbols[66]="©"; symbols[67]="®"; symbols[68]="™"; symbols[69]="§"; symbols[70]="¶"
symbols[71]="†"; symbols[72]="‡"; symbols[73]="•"; symbols[74]="·"; symbols[75]="…"

# Функция копирования в буфер обмена
copy_to_clipboard() {
local symbol="$1"
if command -v xclip &> /dev/null; then
echo -n "$symbol" | xclip -selection clipboard
echo -e "${GREEN}Скопировано: $symbol${NC}"
elif command -v xsel &> /dev/null; then
echo -n "$symbol" | xsel --clipboard --input
echo -e "${GREEN}Скопировано: $symbol${NC}"
else
echo -e "${RED}Нет утилиты для копирования. Скопируйте символ вручную: ${NC}$symbol"
fi
}

# Вывод списка с разбивкой по категориям
show_symbols() {
local idx=0
local cat_idx=0

for category in "${categories[@]}"; do
echo -e "${BLUE}${category}:${NC}"

# Количество символов в каждой категории
case $cat_idx in
0) count=10;; # Надстрочные цифры
1) count=20;; # Математические символы
2) count=24;; # Греческие буквы
3) count=12;; # Стрелки
4) count=10;; # Знаки препинания
esac

for ((i=0; i<count; i++)); do
printf " %2d) %s\n" "$((idx + 1))" "${symbols[$idx]}"
((idx++))
done

echo ""
((cat_idx++))
done
}

while true; do
clear
echo ""
echo -e "${YELLOW}=== ВЫБОР СПЕЦСИМВОЛОВ (расширенный список) ===${NC}"
echo ""
show_symbols

echo -e "${YELLOW}Как использовать:${NC}"
echo "1. Найдите нужный символ в списке."
echo "2. Введите его номер для копирования."
echo "3. Для выхода нажмите Enter или введите 'q'."
echo ""

read -r -p "> " choice

# Выход по нажатию Enter
if [ -z "$choice" ]; then
break
fi

case $choice in
q|Q)
break
;;
[1-9]|[1-7][0-5]) # Номера от 1 до 75
if [ "$choice" -ge 1 ] && [ "$choice" -le 75 ]; then
selected_symbol="${symbols[$((choice-1))]}"
copy_to_clipboard "$selected_symbol"
else
echo -e "${RED}Неверный номер. Попробуйте снова.${NC}"
fi
;;
*)
echo -e "${RED}Неверный ввод. Введите номер символа, 'q' или нажмите Enter для выхода.${NC}"
;;
esac
echo ""
read -p "Нажмите Enter, чтобы выбрать другой символ или выйти..." _
done
}

main() {
check_gs

while true; do
show_main_menu
read -rp "Выберите пункт: " choice

case "$choice" in
1) create_base_and_folders ;;
2) create_epson_folder ;;
3) create_compress_folder ;;
4) submenu_pdf_compress ;;
5) merge_images_to_pdf ;;
6) extract_archives ;;
7) rename_accompanying_file ;;
8) delete_numbered_folders ;;
9) copy_special_symbols ;;
0)
echo "Выход из программы."
exit 0
;;
*)
warn "Неверный пункт. Попробуйте снова."
sleep 1
;;
esac
done
}

# Запуск
main

---------------------------------------------------------------------------------------------------------------------------------------------
Воспользуйтесь терминалом вызовите его сочетанием клавиш Ctrl+Alt+T и просто перекиньте этот фаил в терминал из окна рабочего стола или проводника смотря где вы его создавали.

Примечание !!!!!!!!
Фаил может не сработать если не стоит в свойствах файла галочка Является выполняемым и не стоят права на просмотр и изменение.
Скопируйте этот код с этой страницы и сохраните его как menu.sh