Русский flag Русский Español flag Español

Safe Find & Replace: How Not to Break Binaries

Published on 2025-12-30

When working on real projects (CDN change, migration, refactor) you often need to bulk-replace one URL with another.

At first glance the task seems trivial: sed -i 's|old|new|g' **/* — and done.

In reality that approach is Russian roulette.

A careless replacement leads to:

  • 💀 Corruption of binary files (images, pdf, archives);
  • 📉 Garbage in git history (binaries marked as changed);
  • 🚫 Inability to cleanly roll back if a backup wasn’t made.

In this note we’ll cover a production-grade algorithm: change only text, don’t touch binaries, make a targeted backup.

Input data

  • Old URL: https://static-old.example-cdn.net
  • New URL: https://cdn-new.example-storage.net
  • Context: Project with mixed content (HTML, JS, YAML + PNG, JPG, WOFF2).

🛠 Steps

1. Preparation and search (Dry Run)

The key tool is grep -I (ignores binary files, even if bytes in them match the string).

First, just see what we’re going to change:

# Ищем рекурсивно, игнорируем бинарники, выводим список файлов
grep -rIl 'https://static-old.example-cdn.net' .

Important: make sure the list contains only text files (no images, fonts, etc.). If everything looks reasonable — proceed.

2. Create a “smart” backup

Don’t copy the whole project — that’s slow and takes space. Save only the files that contain the target string, preserving hierarchy and metadata.

SEARCH="https://static-old.example-cdn.net"
BACKUP_DIR="backup_before_replace"

mkdir -p "$BACKUP_DIR"

# Копируем только файлы с совпадением
find . -type f -print0 \
  | xargs -0 grep -Il "$SEARCH" \
  | xargs -I{} sh -c 'mkdir -p "$BACKUP_DIR/$(dirname "{}")" && cp -p "{}" "$BACKUP_DIR/{}"'

Now backup_before_replace contains an exact copy of only the files to be changed. Rollback is a simple copy back.

3. Perform the replacement (Safe Replace)

sed -i behaves differently on Linux (GNU) and macOS (BSD):

  • Linux: sed -i 's/old/new/g'
  • macOS: sed -i '' 's/old/new/g'

To make the script work everywhere (locally, in CI, on Mac/Linux), use perl — it’s identical across *nix systems.

OLD="https://static-old.example-cdn.net"
NEW="https://cdn-new.example-storage.net"

find . -type f -not -path "./$BACKUP_DIR/*" -print0 \
  | xargs -0 grep -Il "$OLD" \
  | xargs -0 perl -pi -e "s|\Q$OLD\E|$NEW|g"

\Q and \E automatically escape all special characters in the URL (dots, slashes, dashes) — no need to escape manually.

4. Verify the result (Verification)

Make sure everything went well:

# Должно вернуть пустой вывод
grep -rIl "$OLD" . --exclude-dir="$BACKUP_DIR"

# Должно показать изменённые файлы (пример: первые 5)
grep -rIl "$NEW" . --exclude-dir="$BACKUP_DIR" | head -n 5

Additionally, you can run git status or git diff --stat to see the actual changes.

💡 Why this is correct

  1. grep -I — reliable protection against damaging binaries.
  2. Targeted backup — save only the delta, save space and time.
  3. perl instead of sed — cross-platform stability (Linux, macOS, CI).
  4. Variables and clear steps — the script is easy to read and adapt.

TL;DR — Ready-to-use snippet for “right now”

Paste into the terminal and replace your URLs:

SEARCH="https://static-old.example-cdn.net"
REPLACE="https://cdn-new.example-storage.net"
BACKUP="backup_$(date +%s)"

mkdir -p "$BACKUP"

# 1. Точечный бэкап только файлов с совпадением
find . -type f -print0 \
  | xargs -0 grep -Il "$SEARCH" \
  | xargs -I{} sh -c 'mkdir -p "$BACKUP/$(dirname "{}")" && cp -p "{}" "$BACKUP/{}"'

# 2. Безопасная замена (perl — работает на Linux и macOS)
find . -type f -not -path "./$BACKUP/*" -print0 \
  | xargs -0 grep -Il "$SEARCH" \
  | xargs -0 perl -pi -e "s|\Q$SEARCH\E|$REPLACE|g"

echo "Готово! Бэкап сохранён в папке $BACKUP"
echo "Откат (если нужно): cp -r $BACKUP/. ."

This approach is production-proven — safe, reliable, and universal. Good luck with your migrations! 🚀

Related reviews

There were several issues concerning both the technical side and overall understanding. Mikhail responded quickly, resolved the technical problems, and helped me understand them — many thanks. I'm satisfied with the result.

abazawolf · VPS setup, server setup

2026-02-18 · ⭐ 5/5

There were several issues concerning both the technical side and overall understanding. Mikhail responded quickly to the request, helped sort things out and resolved the technical problems and helped clarify understanding, for which a special thank you. I am satisfied with the result.

Need help?

Get in touch with me and I'll help solve the problem

Related Posts