¿Cómo realizar una sustitución sed in situ que solo crea copias de seguridad de los archivos que se modificaron?

13

Ejecuté lo siguiente para reemplazar un término utilizado en todos los archivos en el directorio de trabajo actual:

$ find . -type f -print0 | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Esto realizó la sustitución de palabras pero también creó .buparchivos de archivos que nunca tuvieron la Ms. Johnsoncadena.

¿Cómo realizo la sustitución sin crear todas estas copias de seguridad innecesarias?

Belmin Fernandez
fuente
me parece un error de sed ver
Hotschke
Probablemente haya un mejor enfoque posible usando exy ejecutando condicionalmente (solo si se cambia el archivo) :!cp '%' '%.bup'antes de guardar y salir. Puede que valga la pena mirar más a fondo.
Comodín el
Escribí una pregunta sobre mi idea anterior en el viintercambio de pila.
Comodín el

Respuestas:

6

Puede verificar el contenido de los archivos para asegurarse de que se realizará una sustitución cuando los sedopere:

find . \
    -type f \
    -exec grep -q 'Ms. Johnson' {} \; \
    -print0 |
xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

Si quieres ser realmente inteligente al respecto, puedes renunciar a usar find:

grep -Z -l -r 'Ms. Johnson' |
    xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'
anfetamaquina
fuente
1
Creo que necesitas un '{}'poco antes \;de eso -exec.
Jander
8

Find tiene un -execoperador que ejecuta un comando arbitrario. Aún mejor, -execes un prueba , por lo que puede encadenar varios -execs juntos, y si los comandos anteriores fallan, los posteriores no se ejecutarán. La cadena {}se reemplaza con el nombre de archivo actual y ;marca el final del comando. Debe citar ambos para evitar la interferencia del shell.

Entonces:

find . -type f \
    -exec grep -q 'Ms. Johnson' '{}' \; \
    -exec sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g' '{}' \;
Jander
fuente
Nunca he tenido un problema con los {} 'desnudos.
anfetamáquinas
1
@amphetamachine: Yo tampoco, pero la página del manual lo sugiere. Podría ser una cosa csh, o podría haber shells (no Bash y no POSIX) que se expanden {}a la cadena nula cuando se hace la expansión de llaves.
Jander
4

Miré la página de manual y no vi ninguna forma de hacerlo directamente sed, como estoy seguro de que lo hiciste antes de preguntar. Veo varias formas de evitar esto usando grep, pero creo que la más fácil es esta:

grep -rlZ "Ms. Johnson" . | xargs -0 sed -i'.bup' -e's/Ms. Johnson/Mrs. Melbin/g'

-r
-lnombre de archivo de impresión recurse solo
-Znombres finales con nulo

Kevin
fuente
-1

Parece que primero debe crear una lista de archivos que coincidan con sus términos de búsqueda.

search="test"
for match in $(find -type f -exec grep -l $search "{}" \;)
do sed -i'.bup' -e "/$search/ s/$search/Mrs. Melbin/g" "$match"
done
sombreador
fuente
Olvidó el .como el primer argumento para find. Además, como me informaron hace poco, en realidad no es necesario citar o escapar del{}
Kevin
No genere una lista de archivos y realice la sustitución de comandos en eso. Primero findgenera una lista de archivos separados por una nueva línea, luego el shell divide esta lista en cualquier espacio en blanco (no solo en las nuevas líneas) y luego el shell trata cada palabra como un patrón global. Esto solo funciona si sus nombres de archivo no contienen espacios en blanco o \[?*. Otras respuestas en esta página muestran cómo hacer esto correctamente.
Gilles 'SO- deja de ser malvado'
@Kevin The .es innecesario con find (al menos find found en linux) porque se asume cuando no se pasa una ruta a un argumento.
laebshade