replace_hash_text_only.sh
· 3.1 KiB · Bash
Raw
#!/usr/bin/env bash
set -euo pipefail
# Recursively scans a folder (including hidden files/folders), but only processes
# human-readable text files. It:
# 1) Lists files containing OLD value
# 2) Asks for confirmation
# 3) Creates .backup copies (AFTER confirmation)
# 4) Replaces OLD -> NEW in those files
#
# Usage:
# ./replace_hash_text_only.sh /path/to/folder
OLD='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
NEW='yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
if [[ $# -ne 1 ]]; then
echo "Usage: $0 /path/to/folder"
exit 1
fi
ROOT="$1"
if [[ ! -d "$ROOT" ]]; then
echo "Error: '$ROOT' is not a directory."
exit 1
fi
for cmd in find grep perl file cp; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Error: required command not found: $cmd"
exit 1
}
done
is_human_readable_text() {
# file --mime output example:
# text/plain; charset=utf-8
# application/octet-stream; charset=binary
local mime
mime="$(file --mime -b -- "$1" 2>/dev/null || true)"
[[ -n "$mime" ]] || return 1
[[ "$mime" == *"charset=binary"* ]] && return 1
return 0
}
declare -a MATCHED_FILES=()
declare -a MATCH_COUNTS=()
files_seen=0
text_files_scanned=0
non_text_skipped=0
while IFS= read -r -d '' file; do
((files_seen+=1))
if ! is_human_readable_text "$file"; then
((non_text_skipped+=1))
continue
fi
((text_files_scanned+=1))
if LC_ALL=C grep -qF -- "$OLD" "$file" 2>/dev/null; then
count="$(LC_ALL=C grep -oF -- "$OLD" "$file" | wc -l | tr -d ' ')"
MATCHED_FILES+=("$file")
MATCH_COUNTS+=("$count")
fi
done < <(find "$ROOT" -type f -print0)
matched_total="${#MATCHED_FILES[@]}"
echo "Scanned files (all types): $files_seen"
echo "Text files scanned: $text_files_scanned"
echo "Non-text files skipped: $non_text_skipped"
echo "Files with matches: $matched_total"
if (( matched_total == 0 )); then
echo "No matches found in text files. Nothing to do."
exit 0
fi
echo
echo "Potential changes:"
for i in "${!MATCHED_FILES[@]}"; do
idx=$((i + 1))
printf " %4d) %s (occurrences: %s)\n" \
"$idx" "${MATCHED_FILES[$i]}" "${MATCH_COUNTS[$i]}"
done
echo
read -r -p "Proceed? This will create .backup files, then apply replacements. [y/N]: " confirm
case "$confirm" in
y|Y|yes|YES) ;;
*)
echo "Canceled. No backups created. No files modified."
exit 0
;;
esac
# Safety check: do not overwrite existing backups
for file in "${MATCHED_FILES[@]}"; do
if [[ -e "${file}.backup" ]]; then
echo "Error: backup already exists: ${file}.backup"
echo "Aborting without making changes."
exit 1
fi
done
echo
echo "Creating backups..."
for file in "${MATCHED_FILES[@]}"; do
cp -p -- "$file" "${file}.backup"
echo " [backup] ${file}.backup"
done
echo
echo "Applying replacements..."
updated=0
total_replacements=0
for i in "${!MATCHED_FILES[@]}"; do
file="${MATCHED_FILES[$i]}"
count="${MATCH_COUNTS[$i]}"
OLD="$OLD" NEW="$NEW" perl -i -pe 's/\Q$ENV{OLD}\E/$ENV{NEW}/g' "$file"
((updated+=1))
((total_replacements+=count))
echo " [updated] $file"
done
echo
echo "Done."
echo "Updated files: $updated"
echo "Total replacements: $total_replacements"
echo "Backups created as: <file>.backup"
| 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 | |
| 137 |