¿Cómo usar sed para reemplazar solo la primera aparición en un archivo?
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 sed
reemplazar 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.
Un sed
script 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 0
y /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 Apple
con . Solo se reemplazará Banana
la 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.GNU
line 0
line 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 s
comando, por lo que esto también es equivalente:
sed '0,/Apple/s//Banana/' input_filename
Todos estos funcionan sed
solo en GNU.
También puedes instalar GNU sed en OS X usando homebrew brew install gnu-sed
.
# 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
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
, ksh
o zsh
se supone como el shell.
sed
Só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/
( re
aquí 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 re
sea 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 conre
las líneas siguientes inclusive; en otras palabras: esto no detectará la primera aparición de unare
coincidencia 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
.
sed
proporciona 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 sed
como BSD (macOS)sed
(también funcionará con GNU sed
):
Dado que 0,/re/
no se puede utilizar y el formulario 1,/re/
no detectará re
si 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 las
llamada; en ambos casos, la expresión regularfoo
se 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
sed
necesita 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-e
opciones es una alternativa al uso de nuevas líneas reales: finalice cada-e
fragmento del script donde normalmente debería ir una nueva línea.
1 s/foo/bar/
reemplaza foo
solo en la primera línea, si se encuentra allí. Si es así, t
se bifurca hasta el final del script (omite los comandos restantes en la línea). (La t
función se bifurca a una etiqueta sólo si la llamada más reciente s
realizó 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ínea1
coincide 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 las/foo/bar/
sustitución se realiza en ambas.sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo'
falla : consed: first RE may not be empty
(BSD/macOS) ysed: -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ínea1
que 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 sintaxissed
especial de GNU0,/re/
, cualquier rango que comience con un número de línea excluye efectivamente el uso de//
.