archivo de edición sed en su lugar

300

Estoy tratando de averiguar si es posible editar un archivo en un solo comando sed sin transmitir manualmente el contenido editado a un nuevo archivo y luego renombrar el nuevo archivo al nombre del archivo original. Probé la -iopción pero mi sistema Solaris dijo que -ies una opción ilegal. ¿Hay una manera diferente?

anfibio
fuente
77
-ies una opción en gnu sed, pero no está en sed estándar. Sin embargo, transmite el contenido a un nuevo archivo y luego cambia el nombre del archivo para que no sea lo que desea.
William Pursell
2
en realidad, es lo que quiero, solo quiero no estar expuesto a tener que realizar la tarea mundana de cambiar el nombre del nuevo archivo al nombre original
anfibio
3
Entonces necesitas volver a plantear la pregunta.
William Pursell
3
@amphibient: ¿Le importaría prefijar el título de su pregunta con la palabra 'Solaris'? El valor de su pregunta se está perdiendo. Por favor, vea los comentarios debajo de mi respuesta. Gracias.
Steve
1
@Steve: eliminé el prefijo Solaris del título nuevamente porque esto no es exclusivo de Solaris.
tripleee

Respuestas:

477

La -iopción transmite el contenido editado a un nuevo archivo y luego lo renombra detrás de escena, de todos modos.

Ejemplo:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

y

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

en macOS .

choroba
fuente
3
al menos que lo hace por mí, así que no tengo que
amphibient
2
Puede intentar compilar el sedimento GNU en su sistema, entonces.
choroba
3
ejemplo:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
Esto es mejor que la solución perl. De alguna manera no crea ningún archivo .swap ... ¡gracias!
matemáticas
3
@maths: Sí, pero ¿tal vez en otro lugar o por un tiempo más corto?
choroba
72

En un sistema donde sedno tiene la capacidad de editar archivos en su lugar, creo que la mejor solución sería usar perl:

perl -pi -e 's/foo/bar/g' file.txt

Aunque esto crea un archivo temporal, reemplaza el original porque se ha proporcionado un sufijo / extensión vacío.

Steve
fuente
14
No solo crea un archivo temporal, sino que también rompe enlaces duros (por ejemplo, en lugar de cambiar el contenido del archivo, reemplaza el archivo con un nuevo archivo con el mismo nombre). A veces este es el comportamiento deseado, a veces es aceptable, pero cuando hay múltiples enlaces a un archivo, casi siempre es un comportamiento incorrecto.
William Pursell el
3
@DwightSpencer: Creo que todos los sistemas sin Perl están rotos. Un solo comando triunfa sobre una cadena de comandos cuando uno quiere permisos elevados y debe usar sudo. En mi opinión, la pregunta es sobre cómo evitar crear y renombrar manualmente un archivo temporal sin tener que instalar el último GNU o BSD sed. Solo usa Perl.
Steve
44
@PetrPeller: ¿En serio? Si usted lee la pregunta con cuidado, usted entendería que el PO está tratando de evitar algo así como: sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt. El hecho de que la edición in situ cambie el nombre utilizando un archivo temporal, no significa que deba realizar un cambio de nombre manual cuando la opción no está disponible. Existen otras herramientas que pueden hacer lo que él / ella quiere, y Perl es la opción obvia. ¿Cómo es que esto no es una respuesta a la pregunta original?
Steve
2
@a_arias: Tienes razón; Él hizo. Pero no puede lograr lo que quiere con Solaris sed. La pregunta fue realmente muy buena, pero su valor ha sido disminuido por personas que no pueden leer las páginas de manual o no pueden molestarse en leer las preguntas aquí en SO.
Steve
44
@EdwardGarson: IIRC, Solaris se envía con Perl pero no con Python o Ruby. Y como he dicho antes, el OP no puede lograr el resultado deseado con Solaris sed.
Steve
56

Tenga en cuenta que en OS X puede obtener errores extraños como "código de comando no válido" u otros errores extraños al ejecutar este comando. Para solucionar este problema, intente

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

Esto se debe a que en la versión OSX de sed, la -iopción espera un extensionargumento, por lo que su comando se analiza realmente como el extensionargumento y la ruta del archivo se interpreta como el código del comando. Fuente: https://stackoverflow.com/a/19457213

diadyne
fuente
1
Esta es otra rareza de OS X. Afortunadamente, brew install gnu-sedjunto con a PATHle permitirá usar la versión GNU de sed. Gracias por mencionarlo; Un definitivo rascador de cabeza.
Doug
23

Lo siguiente funciona bien en mi mac

sed -i.bak 's/foo/bar/g' sample

Estamos reemplazando foo con barra en el archivo de muestra. La copia de seguridad del archivo original se guardará en sample.bak

Para editar en línea sin respaldo, use el siguiente comando

sed -i'' 's/foo/bar/g' sample
minhas23
fuente
¿De alguna manera cómo evitar mantener archivos de copia de seguridad?
Petr Peller
@PetrPeller, puede usar el comando mencionado para el mismo ... sed -i '' 's / foo / bar / g' sample
minhas23
18

Una cosa a tener en cuenta sedes que no puede escribir archivos por sí solo, ya que el único propósito de sed es actuar como editor en la "secuencia" (es decir, las tuberías de stdin, stdout, stderr y otros >&nbuffers, sockets y similares). Con esto en mente, puede usar otro comando teepara volver a escribir la salida en el archivo. Otra opción es crear un parche para canalizar el contenido diff.

Método de la camiseta

sed '/regex/' <file> | tee <file>

Método de parche

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

ACTUALIZAR:

Además, tenga en cuenta que el parche hará que el archivo cambie desde la línea 1 de la salida diff:

Patch no necesita saber a qué archivo acceder, ya que se encuentra en la primera línea de la salida de diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Dwight Spencer
fuente
2
/dev/stdin 2014-03-15, respondió el 7 de enero - ¿eres un viajero del tiempo?
Adrian Frühwirth
En Windows, el uso de msysgit /dev/stdinno existe, por lo que debe reemplazarlo /dev/stdincon '-', un solo guión sin las comillas, por lo que los siguientes comandos deberían funcionar: $ sed 's/oo/u/' fubar | diff -p fubar -y$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth Tengo una caja de policía azul en algún lugar y por alguna razón me llaman el Doctor en el trabajo. Pero honestamente, parece que hay una mala deriva del reloj en el sistema en ese momento.
Dwight Spencer
Estas soluciones me miran confiando en un comportamiento de amortiguación particular. Sospecho que para archivos más grandes, estos pueden romperse ya que mientras sed está leyendo, el archivo puede comenzar a cambiar debajo por el comando parche, o peor aún por el comando tee que truncará el archivo. En realidad no estoy seguro si patchno se truncará también. Creo que guardar la salida en otro archivo y luego catconvertirlo en original sería más seguro.
akostadinov
respuesta real en el lugar de @ william-pursell
akostadinov
11

No es posible hacer lo que quieres con sed. Incluso las versiones sedque admiten la -iopción de editar un archivo en el lugar hacen exactamente lo que usted ha declarado explícitamente que no desea: escriben en un archivo temporal y luego cambian el nombre del archivo. Pero tal vez solo puedas usar ed. Por ejemplo, para cambiar todas las apariciones de fooabar en el archivo file.txt, puede hacer lo siguiente:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

La sintaxis es similar sed, pero ciertamente no es exactamente la misma.

Pero quizás debería considerar por qué no desea utilizar un archivo temporal renombrado. Incluso si no tiene un -isoporte sed, puede escribir fácilmente un script para hacer el trabajo por usted. En vez desed -i 's/foo/bar/g' file , podrías hacer inline file sed 's/foo/bar/g'. Tal guión es trivial de escribir. Por ejemplo:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

debe ser adecuado para la mayoría de los usos.

William Pursell
fuente
1
Entonces, por ejemplo, ¿cómo llamarías a este script y cómo lo llamarías?
anfibio
2
Lo llamaría inlinee invocaría como se describe arriba:inline inputfile sed 's/foo/bar/g'
William Pursell
1
wow, edsolo responde que realmente funciona en el lugar. Primera vez que necesito ed. Gracias. Se debe utilizar una pequeña optimización echo -e ",s/foo/bar/g\012 w"o echo $',s/foo/bar/g\012 w'donde shell le permite evitar trllamadas adicionales .
akostadinov
2
edrealmente no hace las modificaciones en el lugar. Desde la stracesalida, GNU edcrea un archivo temporal, escribe los cambios en el archivo temporal, luego reescribe todo el archivo original, conservando los enlaces duros. Desde la trusssalida, Solaris 11 edtambién usa un archivo temporal, pero cambia el nombre del archivo temporal al nombre del archivo original al guardar con el wcomando, destruyendo los enlaces duros.
Andrew Henle
@ AndrewHenle Eso es desalentador. El edque se envía con los macos actuales (Mojave, sea lo que sea que signifique esa jerga de marketing) aún conserva los enlaces duros. YMMV
William Pursell
10

Podrías usar vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
Mench
fuente
9

sed admite la edición en el lugar. De man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Ejemplo :

Digamos que tiene un archivo hello.txtcon el texto:

hello world!

Si desea mantener una copia de seguridad del archivo anterior, use:

sed -i.bak 's/hello/bonjour' hello.txt

Terminarás con dos archivos: hello.txtcon el contenido:

bonjour world!

y hello.txt.bakcon el viejo contenido.

Si no desea conservar una copia, simplemente no pase el parámetro de extensión.

Sergio A.
fuente
3
El OP menciona específicamente que su plataforma no admite esta opción (popular pero no estándar).
tripleee
3

No especificó qué shell está utilizando, pero con zsh podría usar la =( )construcción para lograr esto. Algo en la línea de:

cp =(sed ... file; sync) file

=( )es similar >( )pero crea un archivo temporal que se elimina automáticamente cuando cpfinaliza.

Thor
fuente
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Debe preservar todos los enlaces duros, ya que la salida se dirige hacia atrás para sobrescribir el contenido del archivo original, y evita la necesidad de una versión especial de sed.

JJM
fuente
3

Si está reemplazando la misma cantidad de caracteres y después de leer detenidamente la edición "in situ" de los archivos ...

También puede usar el operador de redirección <>para abrir el archivo para leer y escribir:

sed 's/foo/bar/g' file 1<> file

Véalo en vivo:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Del Manual de referencia de Bash → 3.6.10 Abrir descriptores de archivos para leer y escribir :

El operador de redireccionamiento

[n]<>word

hace que el archivo cuyo nombre es la expansión de la palabra se abra para leer y escribir en el descriptor de archivo n, o en el descriptor de archivo 0 si no se especifica n. Si el archivo no existe, se crea.

fedorqui 'así que deja de dañar'
fuente
Esta corrompido por archivo. No estoy seguro de la razón detrás de esto. paste.fedoraproject.org/462017
Nehal J Wani
Ver líneas [43-44], [88-89], [135-136]
Nehal J Wani
2
Este blog explica por qué esta respuesta no debe usarse. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani
@NehalJWani Leeré mucho el artículo, ¡gracias por el aviso! Lo que no mencioné en la respuesta es que esto funciona si cambia la misma cantidad de caracteres.
fedorqui 'así que deja de dañar'
@NehalJWani, dicho esto, lamento que mi respuesta haya causado daño a sus archivos. Lo actualizaré con correcciones (o lo eliminaré si es necesario) después de leer los enlaces que proporcionó.
fedorqui 'SO deja de dañar'
2

Como dijo Moneypenny en Skyfall: "A veces las viejas formas son las mejores". Kincade dijo algo similar más tarde.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Feliz edición en su lugar. Se agregó '\ nw \ n' para escribir el archivo. Disculpas por retrasar la solicitud de respuesta.

jlettvin
fuente
¿Puedes explicar qué hace esto?
Matt Montag
1
Invoca ed (1), el editor de texto con algunos caracteres escritos en stdin. Como alguien menor de 30 años, creo que está bien para mí decir que ed (1) es para dinosaurios como lo son los dinosaurios para nosotros. De todos modos, en lugar de abrir ed y escribir esas cosas, jlettvin está canalizando los caracteres en el uso |. @MattMontag
Ari Sweedler
Si está preguntando qué hacen los comandos, hay dos de ellos, terminados en a \n. [rango] s / <old> / <new> / <flag> es la forma del comando sustituto, un paradigma que todavía se ve en muchos otros editores de texto (bueno, vim, un descendiente directo de ed) De todos modos, la gbandera significa "global" y significa "Cada instancia en cada línea que miras". El rango 3,5mira las líneas 3-5. ,5mira StartOfFile-5, 3,mira las líneas 3-EndOfFile y ,mira StartOfFile-EndOfFile. Un comando de sustitución global en cada línea que reemplaza "falso" por "verdadero". Luego, wse ingresa el comando de escritura,.
Ari Sweedler
1

Muy buenos ejemplos. Tuve el desafío de editar muchos archivos y la opción -i parece ser la única solución razonable al usarla dentro del comando find. Aquí el script para agregar "versión:" delante de la primera línea de cada archivo:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
fuente