# Búsqueda y reemplazo recursivo con sed en directorios

## Contexto

Estrategias para localizar y modificar cadenas de texto en múltiples archivos de forma recursiva, con capacidad de filtrar por tipo de archivo y excluir líneas comentadas.

## Escenario típico

- Necesidad de cambiar nombres de variables, funciones o constantes en toda una base de código
- Refactorización de nombres de configuración
- Migración de valores legacy a nuevas nomenclaturas
- Requiere precisión para no modificar comentarios ni código inactivo

## Estrategias de búsqueda

### 1. Localización básica (análisis previo)

```bash
# Buscar en todos los archivos
find . -type f -exec grep -l "PATRON_BUSQUEDA" {} \;

# Con contexto (3 líneas antes y después)
find . -type f -exec grep -n -B3 -A3 "PATRON_BUSQUEDA" {} + | less

# Múltiples patrones (OR lógico)
find . -type f -exec grep -l "PATRON_1\|PATRON_2\|PATRON_3" {} \;
```

### 2. Filtrado por tipo de archivo

```bash
# Solo archivos con extensión específica
find . -type f -name "*.php" -exec grep -l "PATRON" {} \;
find . -type f -name "*.js" -exec grep -l "PATRON" {} \;
find . -type f -name "*.py" -exec grep -l "PATRON" {} \;

# Múltiples extensiones
find . -type f \( -name "*.php" -o -name "*.inc" \) -exec grep -l "PATRON" {} \;

# Excluir directorios específicos
find . -type f -name "*.php" -not -path "*/vendor/*" -not -path "*/node_modules/*" \
  -exec grep -l "PATRON" {} \;
```

### 3. Análisis con numeración de líneas

```bash
# Ver archivo:línea:contenido
find . -type f -name "*.php" -exec grep -Hn "PATRON" {} \;

# Con AWK para formato personalizado
find . -type f -name "*.php" -exec awk '/PATRON/ {print FILENAME ":" NR ":" $0}' {} +
```

## Estrategias de reemplazo

### 1. Reemplazo básico (SIN backup automático)

```bash
# Reemplazar en todos los archivos de un tipo
find . -type f -name "*.php" -exec sed -i 's/PATRON_VIEJO/PATRON_NUEVO/g' {} \;

# Múltiples reemplazos en una pasada
find . -type f -name "*.php" -exec sed -i '
  s/PATRON_1/NUEVO_1/g
  s/PATRON_2/NUEVO_2/g
  s/PATRON_3/NUEVO_3/g
' {} \;
```

**Nota**: `-i` modifica archivos en sitio. En BSD/macOS usar `sed -i ''`

### 2. Reemplazo CON backup automático

```bash
# Crea archivo.php.bak antes de modificar
find . -type f -name "*.php" -exec sed -i.bak 's/PATRON_VIEJO/PATRON_NUEVO/g' {} \;

# Limpiar backups después de verificar
find . -type f -name "*.php.bak" -delete
```

### 3. Excluir líneas comentadas

#### PHP/JavaScript/C-style

```bash
# Excluir líneas que empiezan por #, //, /* o *
find . -type f -name "*.php" -exec sed -i.bak '/^\s*[#*\/]/! s/PATRON/NUEVO/g' {} \;
```

**Desglose del patrón**:

- `/^\s*[#*\/]/!` → Negación: aplica solo si NO cumple
- `^\s*` → Inicio de línea con posibles espacios/tabs
- `[#*\/]` → Caracteres de comentario: `#`, `*`, `/`
- `!` → Negación lógica

#### Versión robusta (múltiples tipos de comentario)

```bash
find . -type f -name "*.php" -exec sed -i.bak '
/^\s*#/! {
  /^\s*\/\//! {
    /^\s*\/\*/! {
      /^\s*\*/! {
        s/PATRON_1/NUEVO_1/g
        s/PATRON_2/NUEVO_2/g
      }
    }
  }
}
' {} \;
```

Esto excluye:

- `# comentario estilo shell/Python`
- `// comentario estilo C++/JavaScript`
- `/* inicio de bloque comentario */`
- `* líneas dentro de bloque comentario`

#### Python

```bash
# Excluir líneas que empiezan por #
find . -type f -name "*.py" -exec sed -i.bak '/^\s*#/! s/PATRON/NUEVO/g' {} \;
```

#### Shell scripts

```bash
# Excluir líneas que empiezan por #
find . -type f -name "*.sh" -exec sed -i.bak '/^\s*#/! s/PATRON/NUEVO/g' {} \;
```

### 4. Alternativa con Perl (más flexible)

```bash
# Perl permite regex más complejas
find . -type f -name "*.php" -exec perl -i.bak -pe '
  s/PATRON_VIEJO/PATRON_NUEVO/g unless /^\s*[#*\/]/
' {} \;

# Múltiples patrones con hash de sustituciones
find . -type f -name "*.php" -exec perl -i.bak -pe '
  BEGIN { 
    %replace = (
      "PATRON_1" => "NUEVO_1",
      "PATRON_2" => "NUEVO_2",
      "PATRON_3" => "NUEVO_3"
    );
  }
  unless (/^\s*[#*\/]/) {
    s/$_/$replace{$_}/g for keys %replace;
  }
' {} \;
```

## Flujo de trabajo recomendado

```bash
# PASO 1: Análisis inicial (¿qué se va a cambiar?)
find . -type f -name "*.php" -exec grep -Hn "PATRON_VIEJO" {} \; | less

# PASO 2: Análisis excluyendo comentarios
find . -type f -name "*.php" -exec awk '
  !/^\s*[#*\/]/ && /PATRON_VIEJO/ {
    print FILENAME ":" NR ":" $0
  }
' {} + | less

# PASO 3: Prueba en un solo archivo
sed -i.test '/^\s*[#*\/]/! s/PATRON_VIEJO/PATRON_NUEVO/g' ruta/archivo_prueba.php
diff ruta/archivo_prueba.php ruta/archivo_prueba.php.test

# PASO 4: Aplicar con backup (EJECUTAR)
find . -type f -name "*.php" -exec sed -i.bak '/^\s*[#*\/]/! s/PATRON_VIEJO/PATRON_NUEVO/g' {} \;

# PASO 5: Revisar cambios (si hay control de versiones)
git diff
git status

# PASO 6: Validar funcionamiento
# [Ejecutar tests, verificar aplicación]

# PASO 7: Limpiar backups si todo OK
find . -name "*.php.bak" -delete
```

## Casos de uso comunes

### Cambio de prefijo de base de datos

```bash
# MySQL/MariaDB: cambiar prefijo de tablas en dumps
find . -type f -name "*.sql" -exec sed -i.bak 's/prefijo_viejo_/prefijo_nuevo_/g' {} \;
```

### Refactorización de nombres de clase

```bash
# PHP: cambiar nombre de clase
find . -type f -name "*.php" -exec sed -i.bak '/^\s*[#*\/]/! {
  s/class OldClassName/class NewClassName/g
  s/OldClassName::/NewClassName::/g
  s/new OldClassName/new NewClassName/g
}' {} \;
```

### Migración de configuración

```bash
# Cambiar nombres de constantes de configuración
find . -type f \( -name "*.php" -o -name "*.inc" \) -exec sed -i.bak '/^\s*[#*\/]/! {
  s/OLD_CONFIG_NAME/NEW_CONFIG_NAME/g
  s/OLD_DB_HOST/NEW_DB_HOST/g
  s/OLD_API_KEY/NEW_API_KEY/g
}' {} \;
```

### Actualización de rutas

```bash
# Cambiar rutas legacy
find . -type f -name "*.php" -exec sed -i.bak '/^\s*[#*\/]/! {
  s|/var/www/old_path/|/var/www/new_path/|g
  s|old-domain\.com|new-domain.com|g
}' {} \;
```

## Limitaciones conocidas

### 1. Comentarios multilínea

El método basado en `^\s*` solo detecta comentarios al **inicio de línea**. No detecta:

```php
$var = "value"; /* comentario inline */ $otra = "PATRON"; // no se excluirá
```

Para estos casos, considerar un parser específico del lenguaje.

### 2. Cadenas literales

Si `PATRON` aparece dentro de strings, será reemplazado:

```php
$sql = "SELECT * FROM old_table"; // se cambiará "old_table"
```

Para evitarlo se requiere análisis sintáctico completo.

### 3. Bloques multilínea

Comentarios que abarcan múltiples líneas:

```php
/*
 * Este es un comentario
 * con PATRON_VIEJO que
 * no será detectado en líneas 2-3
 */
```

Solo la primera línea (`/*`) se excluirá.

## Alternativas avanzadas

### Ripgrep (rg) + sd

```bash
# Búsqueda ultrarrápida
rg "PATRON" --type php

# Reemplazo con sd (más intuitivo que sed)
find . -name "*.php" -exec sd "PATRON_VIEJO" "PATRON_NUEVO" {} \;
```

### Usando AG (The Silver Searcher)

```bash
# Listar archivos
ag -l "PATRON" --php

# Con contexto
ag "PATRON" --php -C 3
```

### Scripts dedicados por lenguaje

Para refactorizaciones complejas, usar herramientas especializadas:

- **PHP**: PHP-CS-Fixer, Rector
- **JavaScript**: jscodeshift, lebab
- **Python**: rope, bowler

## Verificación post-cambio

```bash
# Contar ocurrencias antes y después
echo "Antes:"
find . -name "*.php" -exec grep -o "PATRON_VIEJO" {} \; | wc -l

echo "Después:"
find . -name "*.php" -exec grep -o "PATRON_NUEVO" {} \; | wc -l

# Verificar que no quedan ocurrencias del patrón viejo
find . -name "*.php" -exec grep -H "PATRON_VIEJO" {} \;

# Si hay control de versiones
git diff --stat
git diff | grep -E "^\+.*PATRON_NUEVO" | wc -l
```

## Consejos de seguridad

1. **Siempre hacer backup** antes de modificaciones masivas
2. **Usar control de versiones** (git commit antes de ejecutar)
3. **Probar en un archivo** antes de aplicar a todo el proyecto
4. **Revisar diff completo** antes de commit final
5. **Ejecutar suite de tests** después de cambios
6. **Considerar crear rama temporal** para refactorizaciones grandes

## Troubleshooting

### "Device or resource busy"
```bash
# Puede ocurrir con archivos abiertos. Solución: cerrar editores
lsof | grep nombre_archivo
```

### "Permission denied"
```bash
# Verificar permisos
find . -type f -name "*.php" ! -writable

# Corregir si es necesario
find . -type f -name "*.php" -exec chmod u+w {} \;
```

### Sed no modifica archivos
```bash
# Verificar sintaxis específica del sistema
sed --version  # GNU sed
sed -i.bak ...  # funciona en GNU

# En macOS/BSD
sed -i '' ...   # requiere argumento vacío
```

---

**Última actualización**: Octubre 2025  
**Compatibilidad**: Linux (GNU sed), *BSD/macOS (requiere ajustes en `-i`)  
**Herramientas**: find, sed, grep, awk, perl