Last active 4 hours ago

kyx revised this gist 4 hours ago. Go to revision

1 file changed, 136 insertions

replace_hash_text_only.sh(file created)

@@ -0,0 +1,136 @@
1 + #!/usr/bin/env bash
2 + set -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 +
14 + OLD='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
15 + NEW='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
16 +
17 + if [[ $# -ne 1 ]]; then
18 + echo "Usage: $0 /path/to/folder"
19 + exit 1
20 + fi
21 +
22 + ROOT="$1"
23 + if [[ ! -d "$ROOT" ]]; then
24 + echo "Error: '$ROOT' is not a directory."
25 + exit 1
26 + fi
27 +
28 + for 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 + }
33 + done
34 +
35 + is_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 +
46 + declare -a MATCHED_FILES=()
47 + declare -a MATCH_COUNTS=()
48 +
49 + files_seen=0
50 + text_files_scanned=0
51 + non_text_skipped=0
52 +
53 + while 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
68 + done < <(find "$ROOT" -type f -print0)
69 +
70 + matched_total="${#MATCHED_FILES[@]}"
71 +
72 + echo "Scanned files (all types): $files_seen"
73 + echo "Text files scanned: $text_files_scanned"
74 + echo "Non-text files skipped: $non_text_skipped"
75 + echo "Files with matches: $matched_total"
76 +
77 + if (( matched_total == 0 )); then
78 + echo "No matches found in text files. Nothing to do."
79 + exit 0
80 + fi
81 +
82 + echo
83 + echo "Potential changes:"
84 + for i in "${!MATCHED_FILES[@]}"; do
85 + idx=$((i + 1))
86 + printf " %4d) %s (occurrences: %s)\n" \
87 + "$idx" "${MATCHED_FILES[$i]}" "${MATCH_COUNTS[$i]}"
88 + done
89 +
90 + echo
91 + read -r -p "Proceed? This will create .backup files, then apply replacements. [y/N]: " confirm
92 + case "$confirm" in
93 + y|Y|yes|YES) ;;
94 + *)
95 + echo "Canceled. No backups created. No files modified."
96 + exit 0
97 + ;;
98 + esac
99 +
100 + # Safety check: do not overwrite existing backups
101 + for 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
107 + done
108 +
109 + echo
110 + echo "Creating backups..."
111 + for file in "${MATCHED_FILES[@]}"; do
112 + cp -p -- "$file" "${file}.backup"
113 + echo " [backup] ${file}.backup"
114 + done
115 +
116 + echo
117 + echo "Applying replacements..."
118 + updated=0
119 + total_replacements=0
120 +
121 + for 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"
129 + done
130 +
131 + echo
132 + echo "Done."
133 + echo "Updated files: $updated"
134 + echo "Total replacements: $total_replacements"
135 + echo "Backups created as: <file>.backup"
136 +
Newer Older