#!/usr/bin/env bash set -Eeuo pipefail SCRIPT_NAME=$(basename "$0") DEFAULT_PREFIX=$(basename "$PWD") TMP_DIR=$(mktemp -d) cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT declare -a FILES declare -a TEMPS declare -a TARGETS # 문자열을 NFC(완성형 한글)로 변환하는 헬퍼 함수 to_nfc() { python3 -c "import unicodedata, sys; sys.stdout.write(unicodedata.normalize('NFC', sys.argv[1]))" "$1" } # 기본 Prefix 및 스크립트 이름 NFC 통일 DEFAULT_PREFIX=$(to_nfc "$DEFAULT_PREFIX") SCRIPT_NAME=$(to_nfc "$SCRIPT_NAME") ######################################## # 1. 대상 선택 ######################################## echo echo "========================================" echo " 파일 이름 일괄 변경 스크립트" echo "========================================" echo echo "1) 현재 폴더의 모든 파일" echo "2) 16자리 영문/숫자/-/_/+ 파일만" echo read -r -p "선택 [1/2]: " MODE case "$MODE" in 1) echo "→ 모든 일반 파일을 대상으로 합니다." ;; 2) echo "→ 16자리 영문/숫자/-/_/+ 파일만 대상으로 합니다." ;; *) echo "잘못된 선택입니다." exit 1 ;; esac ######################################## # 2. Prefix 선택 ######################################## echo echo "========================================" echo " 파일명 Prefix 설정" echo "========================================" echo echo "기본 Prefix: $DEFAULT_PREFIX" echo echo "1) $DEFAULT_PREFIX 사용" echo "2) 다른 이름 사용" echo read -r -p "선택 [1/2]: " PREFIX_MODE case "$PREFIX_MODE" in 1) PREFIX="$DEFAULT_PREFIX" ;; 2) read -r -p "사용할 Prefix: " PREFIX if [[ -z "$PREFIX" ]]; then echo "Prefix가 비어 있습니다." exit 1 fi PREFIX=$(to_nfc "$PREFIX") ;; *) echo "잘못된 선택입니다." exit 1 ;; esac ######################################## # 3. 기존 번호 탐색 (NFC 스트림 파싱) ######################################## LAST_NUMBER=0 # python3를 통해 파일명을 일괄 NFC로 정규화하여 전달 while IFS= read -r -d '' file; do name=$(basename -- "$file") [[ "$name" == "$SCRIPT_NAME" ]] && continue # PREFIX_숫자.ext 패턴 검사 if [[ "$name" == "${PREFIX}_"*.* ]]; then remain="${name#"${PREFIX}_"}" number_str="${remain%.*}" # 3자리 초과(1000 이상) 숫자도 제한 없이 매칭 if [[ "$number_str" =~ ^[0-9]+$ ]]; then number=$((10#$number_str)) if (( number > LAST_NUMBER )); then LAST_NUMBER=$number fi fi fi done < <( find . -maxdepth 1 -type f -print0 | python3 -c ' import sys, unicodedata data = sys.stdin.buffer.read() for item in data.split(b"\x00"): if item: text = unicodedata.normalize("NFC", item.decode("utf-8", errors="surrogateescape")) sys.stdout.buffer.write(text.encode("utf-8") + b"\x00") ' ) ######################################## # 4. 번호 시작 방식 선택 ######################################## echo echo "========================================" echo " 번호 시작 설정" echo "========================================" if (( LAST_NUMBER > 0 )); then printf '현재 "%s_NNN.ext" 형식의 가장 큰 번호: %d\n' \ "$PREFIX" "$LAST_NUMBER" else echo "현재 번호가 지정된 파일이 없습니다." fi echo echo "1) 001부터 다시 시작" if (( LAST_NUMBER > 0 )); then printf '2) %d부터 시작 (기존 번호 파일 제외)\n' \ "$((LAST_NUMBER + 1))" else echo "2) 마지막 번호 다음부터 시작" fi echo read -r -p "선택 [1/2]: " NUMBER_MODE case "$NUMBER_MODE" in 1) START_NUMBER=1 ;; 2) START_NUMBER=$((LAST_NUMBER + 1)) ;; *) echo "잘못된 선택입니다." exit 1 ;; esac ######################################## # 5. 대상 파일 수집 ######################################## while IFS= read -r -d '' file; do name=$(basename -- "$file") [[ "$name" == "$SCRIPT_NAME" ]] && continue # 번호 이어서 지정하는 경우: 이미 PREFIX_숫자.ext 형식인 파일 제외 if [[ "$NUMBER_MODE" == "2" ]]; then if [[ "$name" == "${PREFIX}_"*.* ]]; then remain="${name#"${PREFIX}_"}" number_str="${remain%.*}" if [[ "$number_str" =~ ^[0-9]+$ ]]; then continue fi fi fi # 2번 선택 시 16자리 파일만 if [[ "$MODE" == "2" ]]; then [[ "$name" =~ ^[A-Za-z0-9_+\-]{16}\.[^./]+$ ]] || continue fi FILES+=("$file") done < <( find . -maxdepth 1 -type f -printf '%T@ %p\0' | sort -z -n | sed -z 's/^[^ ]* //' | python3 -c ' import sys, unicodedata data = sys.stdin.buffer.read() for item in data.split(b"\x00"): if item: text = unicodedata.normalize("NFC", item.decode("utf-8", errors="surrogateescape")) sys.stdout.buffer.write(text.encode("utf-8") + b"\x00") ' ) ######################################## # 6. 대상 파일 확인 ######################################## TOTAL_FILES=${#FILES[@]} if (( TOTAL_FILES == 0 )); then echo echo "변경할 대상 파일이 없습니다." exit 0 fi ######################################## # 7. 새 이름 생성 (자릿수 동적 패딩) ######################################## # 예상 최대 번호 계산 MAX_EXPECTED=$((START_NUMBER + TOTAL_FILES - 1)) # 기본 최소 3자리, 숫자가 더 길어지면 자릿수 자동 확장 (예: 1000 -> 4자리) PAD_WIDTH=${#MAX_EXPECTED} if (( PAD_WIDTH < 3 )); then PAD_WIDTH=3 fi FMT="%0${PAD_WIDTH}d" for ((i = 0; i < TOTAL_FILES; i++)); do name=$(basename -- "${FILES[i]}") if [[ "$name" == *.* ]]; then ext=".${name##*.}" else ext="" fi number=$((START_NUMBER + i)) printf -v number_fmt "$FMT" "$number" TARGETS[i]="${PREFIX}_${number_fmt}${ext}" done ######################################## # 8. 변경 목록 출력 ######################################## echo echo "========================================" echo " 변경 예정 목록" echo "========================================" echo echo "Prefix : $PREFIX" printf "시작 번호 : $FMT\n" "$START_NUMBER" echo "대상 파일 수 : $TOTAL_FILES" echo for ((i = 0; i < TOTAL_FILES; i++)); do printf '%s\n' "$(basename -- "${FILES[i]}")" printf ' → %s\n\n' "${TARGETS[i]}" done ######################################## # 9. 최종 확인 ######################################## read -r -p "위 목록대로 변경하시겠습니까? [y/N] " answer if [[ ! "$answer" =~ ^[Yy]$ ]]; then echo "취소되었습니다." exit 0 fi ######################################## # 10. 임시 이름으로 변경 ######################################## for ((i = 0; i < TOTAL_FILES; i++)); do tmp="$TMP_DIR/file_$i" mv -- "${FILES[i]}" "$tmp" TEMPS[i]="$tmp" done ######################################## # 11. 최종 이름으로 변경 ######################################## for ((i = 0; i < TOTAL_FILES; i++)); do mv -- "${TEMPS[i]}" "${TARGETS[i]}" done ######################################## # 완료 ######################################## echo echo "========================================" echo " 변경 완료" echo "========================================" echo echo "${TOTAL_FILES}개 파일의 이름을 변경했습니다."