¿Cómo usar sed para reemplazar solo la primera aparición en un archivo?

Resuelto David Dibben asked hace 15 años • 25 respuestas

Me gustaría actualizar una gran cantidad de archivos fuente de C++ con una directiva de inclusión adicional antes de cualquier #include existente. Para este tipo de tarea, normalmente uso un pequeño script bash con sed para reescribir el archivo.

¿Cómo puedo sedreemplazar solo la primera aparición de una cadena en un archivo en lugar de reemplazar cada aparición?

si uso

sed s/#include/#include "newfile.h"\n#include/

reemplaza todos los #incluye.

También se aceptan sugerencias alternativas para lograr el mismo objetivo.

David Dibben avatar Sep 29 '08 19:09 David Dibben
Aceptado

Un sedscript que sólo sustituirá la primera aparición de "Apple" por "Banana"

Ejemplo

     Input:      Output:

     Apple       Banana
     Apple       Apple
     Orange      Orange
     Apple       Apple

Este es el script simple: Nota del editor: funciona solo con GNU sed .

sed '0,/Apple/{s/Apple/Banana/}' input_filename

Los dos primeros parámetros 0y /Apple/son el especificador de rango. Es lo s/Apple/Banana/que se ejecuta dentro de ese rango. Entonces, en este caso "dentro del rango desde el principio ( 0) hasta la primera instancia de Apple, reemplace Applecon . Solo se reemplazará Bananala primera .Apple

Antecedentes: en la versión tradicional, el especificador de rango tambiénsed es "comenzar aquí" y "terminar aquí" (inclusive). Sin embargo, el "comienzo" más bajo es la primera línea (línea 1), y si el "final aquí" es una expresión regular, entonces solo se intenta hacer coincidir la línea siguiente después de "comenzar", por lo que el final más temprano posible es la línea 2. Entonces, dado que el rango es inclusivo, el rango más pequeño posible es "2 líneas" y el rango inicial más pequeño son las líneas 1 y 2 (es decir, si hay una ocurrencia en la línea 1, las ocurrencias en la línea 2 también se cambiarán, lo que no es deseado en este caso). ). sed agrega su propia extensión para permitir especificar el inicio como "pseudo" para que el final del rango pueda ser , permitiéndole un rango de "solo la primera línea" si la expresión regular coincide con la primera línea.GNUline 0line 1

O una versión simplificada (un RE vacío //significa reutilizar el especificado antes, por lo que esto es equivalente):

sed '0,/Apple/{s//Banana/}' input_filename

Y las llaves son opcionales para el scomando, por lo que esto también es equivalente:

sed '0,/Apple/s//Banana/' input_filename

Todos estos funcionan sedsolo en GNU.

También puedes instalar GNU sed en OS X usando homebrew brew install gnu-sed.

tim avatar Feb 26 '2012 13:02 tim
 # sed script to change "foo" to "bar" only on the first occurrence
 1{x;s/^/first/;x;}
 1,/foo/{x;/first/s///;x;s/foo/bar/;}
 #---end of script---

o, si lo prefiere: Nota del editor: funciona sólo con GNU sed .

sed '0,/foo/s//bar/' file 

Fuente

Ben Hoffstein avatar Sep 29 '2008 12:09 Ben Hoffstein

Una descripción general de las muchas respuestas útiles existentes , complementadas con explicaciones :

Los ejemplos aquí utilizan un caso de uso simplificado: reemplace la palabra 'foo' con 'bar' solo en la primera línea coincidente.
Debido al uso de cadenas ANSI C entrecomilladas ( $'...') para proporcionar líneas de entrada de muestra, bash, ksho zshse supone como el shell.


sedSólo GNU :

La respuesta de Ben Hoffstein nos muestra que GNU proporciona una extensión a la especificación POSIXsed que permite la siguiente forma de 2 direcciones : 0,/re/( reaquí representa una expresión regular arbitraria).

0,/re/también permite que la expresión regular coincida en la primera línea . En otras palabras: dicha dirección creará un rango desde la primera línea hasta la línea que coincide, incluida la primera re, ya resea en la primera línea o en cualquier línea posterior.

  • Compare esto con el formulario compatible con POSIX 1,/re/, que crea un rango que coincide desde la primera línea hasta la línea que coincide con relas líneas siguientes inclusive; en otras palabras: esto no detectará la primera aparición de una recoincidencia si ocurre en la primera línea y también evita el uso de taquigrafía// para la reutilización de la expresión regular utilizada más recientemente (consulte el siguiente punto). 1

Si combina una 0,/re/dirección con una s/.../.../llamada (de sustitución) que utiliza la misma expresión regular, su comando efectivamente solo realizará la sustitución en la primera línea que coincida re.
sedproporciona un atajo conveniente para reutilizar la expresión regular aplicada más recientemente : un par de delimitadores vacíos// .

$ sed '0,/foo/ s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo' 
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Un POSIX solo con funciones sedcomo BSD (macOS)sed (también funcionará con GNU sed ):

Dado que 0,/re/no se puede utilizar y el formulario 1,/re/no detectará resi ocurre en la primera línea (ver arriba), se requiere un manejo especial para la primera línea .

La respuesta de MikhailVS menciona la técnica, presentada en un ejemplo concreto aquí:

$ sed -e '1 s/foo/bar/; t' -e '1,// s//bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar         # only 1st match of 'foo' replaced
Unrelated
2nd foo
3rd foo

Nota:

  • El //atajo de expresiones regulares vacías se emplea dos veces aquí: una para el punto final del rango y otra en la sllamada; en ambos casos, la expresión regular foose reutiliza implícitamente, lo que nos permite no tener que duplicarla, lo que hace que el código sea más corto y más fácil de mantener.

  • POSIX sednecesita nuevas líneas reales después de ciertas funciones, como después del nombre de una etiqueta o incluso su omisión, como es el caso aquí t; dividir estratégicamente el script en múltiples -eopciones es una alternativa al uso de nuevas líneas reales: finalice cada -efragmento del script donde normalmente debería ir una nueva línea.

1 s/foo/bar/reemplaza foosolo en la primera línea, si se encuentra allí. Si es así, tse bifurca hasta el final del script (omite los comandos restantes en la línea). (La tfunción se bifurca a una etiqueta sólo si la llamada más reciente srealizó una sustitución real; en ausencia de una etiqueta, como es el caso aquí, se bifurca al final del script).

Cuando eso sucede, la dirección de rango 1,//, que normalmente encuentra la primera aparición a partir de la línea 2 , no coincidirá y el rango no se procesará, porque la dirección se evalúa cuando la línea actual ya es 2.

Por el contrario, si no hay ninguna coincidencia en la primera línea, 1,// se ingresará y se encontrará la primera coincidencia verdadera.

El efecto neto es el mismo que con GNU sed: 0,/re/sólo se reemplaza la primera aparición, ya sea en la primera línea o en cualquier otra.


Aproximaciones SIN alcance

La respuesta de potong demuestra técnicas de bucle que evitan la necesidad de un rango ; Dado que utiliza la sintaxis GNU sed , aquí están los equivalentes compatibles con POSIX :

Técnica de bucle 1: en la primera coincidencia, realice la sustitución, luego ingrese un bucle que simplemente imprima las líneas restantes tal como están :

$ sed -e '/foo/ {s//bar/; ' -e ':a' -e '$!{n;ba' -e '};}' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

Técnica de bucle 2, solo para archivos pequeños : lea la entrada completa en la memoria y luego realice una única sustitución en ella .

$ sed -e ':a' -e '$!{N;ba' -e '}; s/foo/bar/' <<<$'1st foo\nUnrelated\n2nd foo\n3rd foo'
1st bar
Unrelated
2nd foo
3rd foo

1 1.61803 proporciona ejemplos de lo que sucede con 1,/re/, con y sin posterior s//:

  • sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo'rendimientos $'1bar\n2bar'; es decir, ambas líneas se actualizaron, porque el número de línea 1coincide con la primera línea, y la expresión regular /foo/(el final del rango) solo se busca a partir de la siguiente línea. Por lo tanto, en este caso se seleccionan ambas líneas y la s/foo/bar/sustitución se realiza en ambas.
  • sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' falla : con sed: first RE may not be empty(BSD/macOS) y sed: -e expression #1, char 0: no previous regular expression(GNU), porque, en el momento en que se procesa la primera línea (debido al número de línea 1que comienza el rango), aún no se ha aplicado ninguna expresión regular, por lo que //no se refiere a nada.
    Con la excepción de la sintaxis sedespecial de GNU 0,/re/, cualquier rango que comience con un número de línea excluye efectivamente el uso de //.
mklement0 avatar Oct 29 '2015 14:10 mklement0