Cómo informar cambios in situ "sed"

23

Cuando se usa sedpara reemplazar cadenas en el lugar, ¿hay alguna manera de hacer que informe los cambios que hace (sin depender de una diferencia de archivos antiguos y nuevos)?

Por ejemplo, ¿cómo puedo cambiar la línea de comando?

find . -type f | xargs sed -i 's/abc/def/g'

para que pueda ver los cambios que se realizan sobre la marcha?

ricab
fuente

Respuestas:

15

Se podría utilizar sed's w, marca con cualquiera /dev/stderr, /dev/tty, /dev/fd/2si es compatible en su sistema. Por ejemplo, con una entrada filecomo:

foo first
second: missing
third: foo
none here

corriendo

sed -i '/foo/{
s//bar/g
w /dev/stdout
}' file

salidas:

bar first
third: bar

aunque el filecontenido se cambió a:

bar first
second: missing
third: bar
none here

Entonces, en su caso, ejecutando:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
s//bar/g
w /dev/fd/2
}' {} \;

editará los archivos en el lugar y la salida:

./file1:
cosas de bar
más bar

./file2:

./file3:
bar primero
tercero: barra

También puede imprimir algo como, original line >>> modified linepor ejemplo:

find . -type f -printf '\n%p:\n' -exec sed -i '/foo/{
h
s//bar/g
H
x
s/\n/ >>> /
w /dev/fd/2
x
}' {} \;

edita los archivos en el lugar y genera:

./file1:
foo cosas >>> cosas de bar
más foo >>> más bar

./file2:

./file3:
foo primero >>> barra primero
tercero: foo >>> tercero: bar
don_crissti
fuente
16

Puede hacerlo en dos pasadas utilizando la pacción rint en la primera pasada con:

find . -type f | xargs sed --quiet 's/abc/def/gp'

donde --quiethace que sed no muestre todas las líneas y el psufijo muestra solo las líneas donde la sustitución ha coincidido.

Esto tiene la limitación de que sed no mostrará qué archivos se están cambiando, lo que, por supuesto, podría solucionarse con cierta complejidad adicional.

msw
fuente
No hace exactamente lo que quería, ya que necesitas dos pases, pero estoy votando porque acabo de aprender esto y es muy útil. Especialmente si, como dijiste, los archivos también se imprimieron. Algo así comofor x in `find . -type f`; do echo ///File $x: ; sed --quiet 's/abc/def/gp' $x; done
ricab
@msw Tengo muchas sedexpresiones, no quiero escribir /gppara cada una. ¿Cómo configurarlo globalmente?
alhelal
8

No creo que sea posible, pero una solución podría ser usar perl en su lugar:

find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 

Esto imprimirá las líneas alteradas al error estándar. Por ejemplo:

$ cat foo
fooabcbar
$ find . -type f | xargs perl -i -ne 's/abc/def/ && print STDERR' 
foodefbar

También puede hacer esto un poco más complejo, imprimiendo el número de línea, el nombre del archivo, la línea original y la línea modificada:

$ find . -type f | 
   xargs perl -i -ne '$was=$_; chomp($was);
                      s/abc/def/ && print STDERR "$ARGV($.): $was : $_"' 
./foo(1): fooabcbar : foodefbar
terdon
fuente
+1 También puede usar $ARGVpara el nombre del archivo que se está utilizando .
Joseph R.
@JosephR. buen punto, agregado.
terdon
Gracias, probablemente sea útil (no lo probé yo mismo). No estoy seleccionando ya que no usa sed, por lo que no responde exactamente la pregunta.
ricab
@ricab está bien, no debes aceptar una respuesta a menos que realmente responda tu pregunta. Sin embargo, perltenga en cuenta que para cosas simples como esta, la sintaxis es muy similar a sedla de y no creo que sea posible con lo que está pidiendo sed.
terdon
3

Es posible usar la bandera w que escribe el patrón actual en un archivo. Por lo tanto, al agregarlo al comando sustituto podemos informar sucesivas sustituciones a un archivo e imprimirlo después de que el trabajo esté terminado. También me gusta colorear la cadena reemplazada con grep.

sed -i -e "s/From/To/gw /tmp/sed.done" file_name
grep --color -e "To" /tmp/sed.done

Tenga en cuenta que solo debe haber un espacio entre w y su nombre de archivo.

Esto es incluso mejor que diff, ya que diffing también puede mostrar cambios incluso si no fueron hechos por sed.

Jack
fuente
Simplemente lo probé y sed solo escribe líneas a las que sustituyó sed.done. Por lo tanto, las líneas con "Para" en el archivo original no se imprimen en sed.done, por lo grep "To" sed.doneque solo verá las líneas que se cambian sed. No verá la línea original en el archivo antes de que fuera sustituida, si eso es a lo que apunta ...
aairey
1

Me gusta la solución @terdon: Perl es bueno para esto.

Aquí está mi versión modificada que:

  • no intentará alterar los archivos que no tienen la cadena correspondiente
  • hará una copia de seguridad de la versión anterior de los archivos que cambian (crea una versión .bak)
  • enumerará todos los archivos / lineno modificados, y mostrará las versiones ANTIGUA y NUEVA de esa línea con sangría y debajo para facilitar la lectura

código

find /tmp/test -type f ! -name "*.bak" -exec grep -l '/opt/gridmon' {} \; | xargs -L1 perl -ni'.bak' -e'$old=$_; s/\/opt\/gridmon/~/g && print STDERR "$ARGV($.):\n\tOLD:$old\tNEW:$_"'

salida de ejemplo

/tmp/test/test4.cfg(13):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
/tmp/test/test4.cfg(24):
    OLD:    ENVFILE /opt/gridmon/server/etc/gridmonserver.cfg
    NEW:    ENVFILE ~/server/etc/gridmonserver.cfg
andy987
fuente