// DevOps

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! 🚀

// Reviews

Related reviews

// Contact

Need help?

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