Last active 2 hours ago

replace_hash_text_only.sh Raw
1#!/usr/bin/env bash
2set -euo pipefail
3
4# Recursively scans a folder (including hidden files/folders), but only processes
5# human-readable text files. It:
6# 1) Lists files containing OLD value
7# 2) Asks for confirmation
8# 3) Creates .backup copies (AFTER confirmation)
9# 4) Replaces OLD -> NEW in those files
10#
11# Usage:
12# ./replace_hash_text_only.sh /path/to/folder
13
14OLD='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
15NEW='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
16
17if [[ $# -ne 1 ]]; then
18 echo "Usage: $0 /path/to/folder"
19 exit 1
20fi
21
22ROOT="$1"
23if [[ ! -d "$ROOT" ]]; then
24 echo "Error: '$ROOT' is not a directory."
25 exit 1
26fi
27
28for cmd in find grep perl file cp; do
29 command -v "$cmd" >/dev/null 2>&1 || {
30 echo "Error: required command not found: $cmd"
31 exit 1
32 }
33done
34
35is_human_readable_text() {
36 # file --mime output example:
37 # text/plain; charset=utf-8
38 # application/octet-stream; charset=binary
39 local mime
40 mime="$(file --mime -b -- "$1" 2>/dev/null || true)"
41 [[ -n "$mime" ]] || return 1
42 [[ "$mime" == *"charset=binary"* ]] && return 1
43 return 0
44}
45
46declare -a MATCHED_FILES=()
47declare -a MATCH_COUNTS=()
48
49files_seen=0
50text_files_scanned=0
51non_text_skipped=0
52
53while IFS= read -r -d '' file; do
54 ((files_seen+=1))
55
56 if ! is_human_readable_text "$file"; then
57 ((non_text_skipped+=1))
58 continue
59 fi
60
61 ((text_files_scanned+=1))
62
63 if LC_ALL=C grep -qF -- "$OLD" "$file" 2>/dev/null; then
64 count="$(LC_ALL=C grep -oF -- "$OLD" "$file" | wc -l | tr -d ' ')"
65 MATCHED_FILES+=("$file")
66 MATCH_COUNTS+=("$count")
67 fi
68done < <(find "$ROOT" -type f -print0)
69
70matched_total="${#MATCHED_FILES[@]}"
71
72echo "Scanned files (all types): $files_seen"
73echo "Text files scanned: $text_files_scanned"
74echo "Non-text files skipped: $non_text_skipped"
75echo "Files with matches: $matched_total"
76
77if (( matched_total == 0 )); then
78 echo "No matches found in text files. Nothing to do."
79 exit 0
80fi
81
82echo
83echo "Potential changes:"
84for i in "${!MATCHED_FILES[@]}"; do
85 idx=$((i + 1))
86 printf " %4d) %s (occurrences: %s)\n" \
87 "$idx" "${MATCHED_FILES[$i]}" "${MATCH_COUNTS[$i]}"
88done
89
90echo
91read -r -p "Proceed? This will create .backup files, then apply replacements. [y/N]: " confirm
92case "$confirm" in
93 y|Y|yes|YES) ;;
94 *)
95 echo "Canceled. No backups created. No files modified."
96 exit 0
97 ;;
98esac
99
100# Safety check: do not overwrite existing backups
101for file in "${MATCHED_FILES[@]}"; do
102 if [[ -e "${file}.backup" ]]; then
103 echo "Error: backup already exists: ${file}.backup"
104 echo "Aborting without making changes."
105 exit 1
106 fi
107done
108
109echo
110echo "Creating backups..."
111for file in "${MATCHED_FILES[@]}"; do
112 cp -p -- "$file" "${file}.backup"
113 echo " [backup] ${file}.backup"
114done
115
116echo
117echo "Applying replacements..."
118updated=0
119total_replacements=0
120
121for i in "${!MATCHED_FILES[@]}"; do
122 file="${MATCHED_FILES[$i]}"
123 count="${MATCH_COUNTS[$i]}"
124
125 OLD="$OLD" NEW="$NEW" perl -i -pe 's/\Q$ENV{OLD}\E/$ENV{NEW}/g' "$file"
126 ((updated+=1))
127 ((total_replacements+=count))
128 echo " [updated] $file"
129done
130
131echo
132echo "Done."
133echo "Updated files: $updated"
134echo "Total replacements: $total_replacements"
135echo "Backups created as: <file>.backup"
136
137