Skip to main content

Advanced Search & Replace Techniques

Overview

Beyond basic search-replace operations, WP-CLI offers advanced techniques for complex scenarios: regex-like patterns, conditional replacements, data transformation, and specialized migration workflows.

Complex Migration Scenarios

Multi-Domain Migrations

#!/bin/bash
# multi-domain-migration.sh - Handle multiple domain replacements

# Backup first
wp db export pre-multi-domain-migration.sql

# Replace multiple domain variations
declare -A domains=(
["http://oldsite.com"]="https://newsite.com"
["http://www.oldsite.com"]="https://www.newsite.com"
["https://oldsite.com"]="https://newsite.com"
["https://www.oldsite.com"]="https://www.newsite.com"
)

for old in "${!domains[@]}"; do
new="${domains[$old]}"
echo "Replacing: $old$new"
wp search-replace "$old" "$new" --precise --skip-columns=guid --dry-run
done

read -p "Apply all replacements? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
for old in "${!domains[@]}"; do
new="${domains[$old]}"
wp search-replace "$old" "$new" --precise --skip-columns=guid
done
echo "All domain replacements complete!"
fi

Subdirectory to Root Migration

#!/bin/bash
# subdirectory-to-root.sh

# Moving from example.com/blog to example.com

OLD_URL="https://example.com/blog"
NEW_URL="https://example.com"

echo "Migrating from subdirectory to root..."

# Backup
wp db export backup-subdir-to-root.sql

# Replace URLs
wp search-replace "$OLD_URL" "$NEW_URL" --precise --skip-columns=guid --dry-run

read -p "Continue? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
wp search-replace "$OLD_URL" "$NEW_URL" --precise --skip-columns=guid

# Update WordPress options
wp option update home "$NEW_URL"
wp option update siteurl "$NEW_URL"

# Flush permalinks
wp rewrite flush

echo "Migration complete! Update your .htaccess and web server config."
fi

Root to Subdirectory Migration

#!/bin/bash
# root-to-subdirectory.sh

# Moving from example.com to example.com/blog

OLD_URL="https://example.com"
NEW_URL="https://example.com/blog"

echo "Migrating from root to subdirectory..."

# Backup
wp db export backup-root-to-subdir.sql

# Replace URLs (be careful with trailing slashes)
wp search-replace "$OLD_URL" "$NEW_URL" --precise --skip-columns=guid --dry-run

read -p "Continue? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
wp search-replace "$OLD_URL" "$NEW_URL" --precise --skip-columns=guid

# Update options
wp option update home "$NEW_URL"
wp option update siteurl "$NEW_URL"

# Flush permalinks
wp rewrite flush

echo "Migration complete! Move WordPress files to /blog directory."
fi

Selective Data Replacement

Replace Only in Published Posts

# Get published post IDs
POST_IDS=$(wp post list --post_status=publish --field=ID --format=csv)

# Replace only in those posts
wp db query "
UPDATE wp_posts
SET post_content = REPLACE(post_content, 'old-text', 'new-text')
WHERE ID IN ($POST_IDS);
"

# Note: This doesn't handle serialized data
# For serialized data, use search-replace on specific table
wp search-replace 'old-text' 'new-text' wp_posts --precise

Replace in Specific Post Types

#!/bin/bash
# replace-in-post-type.sh

POST_TYPE="product" # WooCommerce products
OLD_TEXT="old-brand"
NEW_TEXT="new-brand"

echo "Replacing in $POST_TYPE post type..."

# Get count of affected posts
COUNT=$(wp db query "
SELECT COUNT(*)
FROM wp_posts
WHERE post_type='$POST_TYPE'
AND post_content LIKE '%$OLD_TEXT%';
" --skip-column-names)

echo "Found $COUNT posts with '$OLD_TEXT'"

# Perform replacement
wp db query "
UPDATE wp_posts
SET post_content = REPLACE(post_content, '$OLD_TEXT', '$NEW_TEXT')
WHERE post_type='$POST_TYPE';
"

echo "Replacement complete!"

Replace Only in Specific Date Range

#!/bin/bash
# replace-in-date-range.sh

START_DATE="2024-01-01"
END_DATE="2024-12-31"
OLD_TEXT="old-text"
NEW_TEXT="new-text"

echo "Replacing content in posts from $START_DATE to $END_DATE..."

wp db query "
UPDATE wp_posts
SET post_content = REPLACE(post_content, '$OLD_TEXT', '$NEW_TEXT')
WHERE post_date >= '$START_DATE'
AND post_date <= '$END_DATE'
AND post_status = 'publish';
"

echo "Replacement complete!"

Media and Upload Path Migrations

Update Upload Paths

#!/bin/bash
# update-upload-paths.sh

OLD_PATH="/wp-content/uploads"
NEW_PATH="/content/uploads"

echo "Updating upload paths..."

# Dry run
wp search-replace "$OLD_PATH" "$NEW_PATH" --dry-run

# Apply
wp search-replace "$OLD_PATH" "$NEW_PATH" --precise

# Update upload_path option if set
wp option update upload_path "$NEW_PATH"

echo "Upload paths updated!"

CDN URL Replacement

#!/bin/bash
# cdn-migration.sh

SITE_URL="https://example.com"
CDN_URL="https://cdn.example.com"

echo "Migrating media to CDN..."

# Replace image URLs
wp search-replace "$SITE_URL/wp-content/uploads" "$CDN_URL/wp-content/uploads" --dry-run

read -p "Apply CDN URLs? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
wp search-replace "$SITE_URL/wp-content/uploads" "$CDN_URL/wp-content/uploads" --precise
echo "CDN migration complete!"
fi

Reverse CDN Migration

#!/bin/bash
# reverse-cdn-migration.sh

CDN_URL="https://cdn.example.com"
SITE_URL="https://example.com"

echo "Removing CDN URLs..."

wp search-replace "$CDN_URL/wp-content/uploads" "$SITE_URL/wp-content/uploads" --precise

echo "CDN URLs removed!"

Multisite Advanced Operations

Replace on Specific Subsites

#!/bin/bash
# multisite-selective-replace.sh

# Array of subsite URLs to update
SUBSITES=(
"site1.example.com"
"site2.example.com"
"site3.example.com"
)

OLD_TEXT="old-company"
NEW_TEXT="new-company"

for site in "${SUBSITES[@]}"; do
echo "Updating $site..."
wp search-replace "$OLD_TEXT" "$NEW_TEXT" --url=$site --precise
done

echo "All subsites updated!"

Network-Wide with Exclusions

#!/bin/bash
# network-replace-with-exclusions.sh

OLD_TEXT="old-text"
NEW_TEXT="new-text"
EXCLUDE_SITES=("archive.example.com" "old.example.com")

# Get all site URLs
ALL_SITES=$(wp site list --field=url)

for site in $ALL_SITES; do
# Check if site should be excluded
if [[ " ${EXCLUDE_SITES[@]} " =~ " ${site} " ]]; then
echo "Skipping $site (excluded)"
continue
fi

echo "Updating $site..."
wp search-replace "$OLD_TEXT" "$NEW_TEXT" --url=$site --precise
done

echo "Network update complete!"

Data Transformation

Normalize URLs (Remove Trailing Slashes)

#!/bin/bash
# normalize-urls.sh

echo "Normalizing URLs (removing trailing slashes)..."

# This requires custom SQL as search-replace can't handle patterns
wp db query "
UPDATE wp_posts
SET post_content = REGEXP_REPLACE(
post_content,
'(https?://[^\"\\s]+)/',
'\\1'
)
WHERE post_content REGEXP '(https?://[^\"\\s]+)/';
"

echo "URLs normalized!"

Convert HTTP to HTTPS (Comprehensive)

#!/bin/bash
# comprehensive-https-migration.sh

DOMAIN="example.com"

echo "Converting entire site to HTTPS..."

# Backup
wp db export backup-before-https.sql

# Replace all HTTP instances
wp search-replace "http://$DOMAIN" "https://$DOMAIN" --precise --skip-columns=guid --dry-run

read -p "Apply HTTPS conversion? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
# Main replacement
wp search-replace "http://$DOMAIN" "https://$DOMAIN" --precise --skip-columns=guid

# Update options
wp option update home "https://$DOMAIN"
wp option update siteurl "https://$DOMAIN"

# Update any remaining http:// in options
wp search-replace "http://" "https://" wp_options --precise

# Clear caches
wp cache flush
wp rewrite flush

echo "HTTPS conversion complete!"
echo "Don't forget to:"
echo "1. Update your SSL certificate"
echo "2. Update .htaccess to force HTTPS"
echo "3. Test all functionality"
fi

Clean Up Shortcodes

#!/bin/bash
# clean-shortcodes.sh

# Remove specific shortcode
SHORTCODE="old_gallery"

echo "Removing [$SHORTCODE] shortcodes..."

# Simple shortcode
wp search-replace "[$SHORTCODE]" "" --include-columns=post_content

# Shortcode with any attributes (requires SQL)
wp db query "
UPDATE wp_posts
SET post_content = REGEXP_REPLACE(
post_content,
'\\[$SHORTCODE[^\\]]*\\]',
''
)
WHERE post_content REGEXP '\\[$SHORTCODE';
"

echo "Shortcodes removed!"

Performance Optimization

Large Database Replacements

#!/bin/bash
# large-db-replacement.sh

OLD_TEXT="old-text"
NEW_TEXT="new-text"

echo "Performing large database replacement..."

# For very large databases, process table by table
TABLES=$(wp db tables --format=csv)

for table in ${TABLES//,/ }; do
echo "Processing $table..."
wp search-replace "$OLD_TEXT" "$NEW_TEXT" "$table" --precise
done

echo "Large database replacement complete!"

Batch Processing

#!/bin/bash
# batch-process-replacements.sh

# Process replacements in batches to avoid memory issues

BATCH_SIZE=1000
OLD_TEXT="old"
NEW_TEXT="new"

# Get total post count
TOTAL=$(wp post list --post_type=post --format=count)
BATCHES=$(( ($TOTAL + $BATCH_SIZE - 1) / $BATCH_SIZE ))

echo "Processing $TOTAL posts in $BATCHES batches..."

for ((i=0; i<$BATCHES; i++)); do
OFFSET=$(($i * $BATCH_SIZE))
echo "Batch $((i+1))/$BATCHES (offset: $OFFSET)..."

# Get post IDs for this batch
POST_IDS=$(wp post list --post_type=post --posts_per_page=$BATCH_SIZE --offset=$OFFSET --field=ID --format=csv)

# Process this batch
wp db query "
UPDATE wp_posts
SET post_content = REPLACE(post_content, '$OLD_TEXT', '$NEW_TEXT')
WHERE ID IN ($POST_IDS);
"
done

echo "Batch processing complete!"

Safety and Verification

Pre-Flight Checks

#!/bin/bash
# preflight-checks.sh

OLD_URL="http://oldsite.com"
NEW_URL="https://newsite.com"

echo "=== Pre-Flight Checks ==="

# 1. Check if old URL exists
COUNT=$(wp db query "
SELECT COUNT(*)
FROM wp_posts
WHERE post_content LIKE '%$OLD_URL%';
" --skip-column-names)

echo "Found $COUNT posts containing old URL"

# 2. Check database size
SIZE=$(wp db query "
SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS 'Size (MB)'
FROM information_schema.TABLES
WHERE table_schema = DATABASE();
" --skip-column-names)

echo "Database size: ${SIZE}MB"

# 3. Check disk space
AVAILABLE=$(df -h . | awk 'NR==2 {print $4}')
echo "Available disk space: $AVAILABLE"

# 4. Verify backup exists
if [ -f "backup.sql" ]; then
echo "✓ Backup file exists"
else
echo "✗ No backup found - creating one..."
wp db export backup.sql
fi

# 5. Dry run
echo ""
echo "Dry run results:"
wp search-replace "$OLD_URL" "$NEW_URL" --dry-run --precise

echo ""
echo "=== Pre-Flight Checks Complete ==="

Post-Migration Verification

#!/bin/bash
# post-migration-verification.sh

NEW_URL="https://newsite.com"

echo "=== Post-Migration Verification ==="

# 1. Check home and siteurl
HOME=$(wp option get home)
SITEURL=$(wp option get siteurl)

echo "Home URL: $HOME"
echo "Site URL: $SITEURL"

if [ "$HOME" == "$NEW_URL" ] && [ "$SITEURL" == "$NEW_URL" ]; then
echo "✓ URLs correctly updated"
else
echo "✗ URL mismatch detected!"
fi

# 2. Check for remaining old URLs
OLD_URL_COUNT=$(wp db query "
SELECT COUNT(*)
FROM wp_posts
WHERE post_content LIKE '%http://oldsite.com%';
" --skip-column-names)

if [ "$OLD_URL_COUNT" -eq 0 ]; then
echo "✓ No old URLs found"
else
echo "⚠ Warning: $OLD_URL_COUNT instances of old URL still exist"
fi

# 3. Check database integrity
wp db check > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "✓ Database integrity OK"
else
echo "✗ Database integrity issues detected"
fi

# 4. Test site accessibility
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$NEW_URL")
if [ "$HTTP_CODE" == "200" ]; then
echo "✓ Site accessible (HTTP $HTTP_CODE)"
else
echo "✗ Site not accessible (HTTP $HTTP_CODE)"
fi

echo ""
echo "=== Verification Complete ==="

Quick Reference

Advanced Commands

# Multi-domain replacement
for old in old1.com old2.com; do
wp search-replace "$old" "new.com" --precise
done

# Table-by-table processing
for table in $(wp db tables); do
wp search-replace 'old' 'new' "$table"
done

# Conditional replacement (SQL)
wp db query "UPDATE wp_posts SET post_content = REPLACE(post_content, 'old', 'new') WHERE post_type='page';"

# Export changes as SQL
wp search-replace 'old' 'new' --export=changes.sql

# Network with exclusions
wp site list --field=url | grep -v "exclude.com" | xargs -I {} wp search-replace 'old' 'new' --url={}

Summary

TechniqueUse CaseComplexity
Multi-DomainComplex migrationsHigh
Selective ReplacementSpecific post types/datesMedium
Media MigrationsCDN, upload path changesMedium
Multisite OperationsNetwork-wide updatesHigh
Data TransformationURL normalization, cleanupHigh
Batch ProcessingLarge databasesHigh
VerificationEnsure migration successMedium
Key Takeaway

Advanced search-replace techniques enable complex migrations, selective updates, and large-scale transformations. Always use dry-run, backup first, and verify results.

Next Steps