command line - sustituir - ¿Cómo usar sed para reemplazar solo la primera ocurrencia en un archivo?
sed reemplazar linea completa (19)
Quiero actualizar una gran cantidad de archivos fuente C ++ con una directiva adicional de inclusión antes de incluir #includes. Para este tipo de tarea, normalmente uso un pequeño script bash con sed para volver a escribir el archivo.
¿Cómo puedo obtener sed para reemplazar solo la primera aparición de una cadena en un archivo en lugar de reemplazar cada ocurrencia?
Si uso
sed s/#include/#include "newfile.h"/n#include/
reemplaza todos los #includes.
Sugerencias alternativas para lograr lo mismo también son bienvenidas.
Como sugerencia alternativa, puede consultar el comando ed
.
man 1 ed
teststr=''
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
''
# for in-place file editing use "ed -s file" and replace ",p" with "w"
# cf. http://wiki.bash-hackers.org/howto/edit-ed
cat <<-''EOF'' | sed -e ''s/^ *//'' -e ''s/ *$//'' | ed -s <(echo "$teststr")
H
/# *include/i
#include "newfile.h"
.
,p
q
EOF
El siguiente comando elimina la primera aparición de una cadena dentro de un archivo. Elimina la línea vacía también. Se presenta en un archivo xml, pero funcionaría con cualquier archivo.
Es útil si trabaja con archivos xml y desea eliminar una etiqueta. En este ejemplo, elimina la primera aparición de la etiqueta "isTag".
Mando:
sed -e 0,/''<isTag>false<//isTag>''/{s/''<isTag>false<//isTag>''//} -e ''s/ *$//'' -e ''/^$/d'' source.txt > output.txt
Archivo fuente (fuente.txt)
<xml>
<testdata>
<canUseUpdate>true</canUseUpdate>
<isTag>false</isTag>
<moduleLocations>
<module>esa_jee6</module>
<isTag>false</isTag>
</moduleLocations>
<node>
<isTag>false</isTag>
</node>
</testdata>
</xml>
Archivo de resultados (output.txt)
<xml>
<testdata>
<canUseUpdate>true</canUseUpdate>
<moduleLocations>
<module>esa_jee6</module>
<isTag>false</isTag>
</moduleLocations>
<node>
<isTag>false</isTag>
</node>
</testdata>
</xml>
ps: no funcionó para mí en Solaris SunOS 5.10 (bastante antiguo), pero funciona en Linux 2.6, sed versión 4.1.5
Escribe un script sed que solo reemplazará la primera aparición de "Apple" por "Banana"
Ejemplo de entrada: Salida:
Apple Banana
Orange Orange
Apple Apple
Esta es la secuencia de comandos simple: Nota del editor: funciona solo con GNU sed
.
sed ''0,/Apple/{s/Apple/Banana/}'' filename
Esto podría funcionar para usted (GNU sed):
sed -si ''/#include/{s//& "newfile.h/n&/;:a;$!{n;ba}}'' file1 file2 file....
o si la memoria no es un problema:
sed -si '':a;$!{N;ba};s/#include/& "newfile.h/n&/'' file1 file2 file...
Finalmente conseguí que esto funcionara en un script Bash utilizado para insertar una marca de tiempo única en cada elemento en una fuente RSS:
sed "1,/====RSSpermalink====/s/====RSSpermalink====/${nowms}/" /
production-feed2.xml.tmp2 > production-feed2.xml.tmp.$counter
Cambia la primera ocurrencia solamente.
${nowms}
es el tiempo en milisegundos establecido por un script de Perl, $counter
es un contador utilizado para el control de bucle dentro del script, /
permite que el comando continúe en la línea siguiente.
El archivo se lee y stdout se redirige a un archivo de trabajo.
De la forma en que lo entiendo, 1,/====RSSpermalink====/
le dice a sed cuándo parar al establecer una limitación de rango, y luego s/====RSSpermalink====/${nowms}/
is el comando sed familiar para reemplazar la primera cuerda con la segunda.
En mi caso, puse el comando entre comillas dobles porque lo estoy usando en un script Bash con variables.
Haría esto con un script awk:
BEGIN {i=0}
(i==0) && /#include/ {print "#include /"newfile.h/""; i=1}
{print $0}
END {}
luego ejecutarlo con awk:
awk -f awkscript headerfile.h > headerfilenew.h
podría ser descuidado, soy nuevo en esto.
Nada nuevo, pero tal vez una respuesta un poco más concreta: sed -rn ''0,/foo(bar).*/ s%%/1%p''
Ejemplo: xwininfo -name unity-launcher
produce resultados como:
xwininfo: Window id: 0x2200003 "unity-launcher"
Absolute upper-left X: -2980
Absolute upper-left Y: -198
Relative upper-left X: 0
Relative upper-left Y: 0
Width: 2880
Height: 98
Depth: 24
Visual: 0x21
Visual Class: TrueColor
Border width: 0
Class: InputOutput
Colormap: 0x20 (installed)
Bit Gravity State: ForgetGravity
Window Gravity State: NorthWestGravity
Backing Store State: NotUseful
Save Under State: no
Map State: IsViewable
Override Redirect State: no
Corners: +-2980+-198 -2980+-198 -2980-1900 +-2980-1900
-geometry 2880x98+-2980+-198
Extracción de ID de ventana con xwininfo -name unity-launcher|sed -rn ''0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%/1%p''
produce:
0x2200003
Podrías usar awk para hacer algo similar ...
awk ''/#include/ && !done { print "#include /"newfile.h/""; done=1;}; 1;'' file.c
Explicación:
/#include/ && !done
Ejecuta la instrucción de acción entre {} cuando la línea coincide con "#include" y aún no la procesamos.
{print "#include /"newfile.h/""; done=1;}
Esto imprime #include "newfile.h", necesitamos escapar de las comillas. Luego establecemos la variable done en 1, por lo que no agregamos más includes.
1;
Esto significa "imprimir la línea": una acción vacía se imprime por defecto $ 0, que imprime toda la línea. Un trazador de líneas y más fácil de entender que sed IMO :-)
Sé que esta es una publicación anterior, pero tenía una solución que solía usar:
grep -E -m 1 -n ''old'' file | sed ''s/:.*$//'' - | sed ''s/$/s//old//new///'' - | sed -f - file
Básicamente use grep para encontrar la primera ocurrencia y detenerse ahí. También imprima el número de línea, es decir, 5: línea. Tipee eso en sed y elimine: y cualquier cosa después, de modo que quede un número de línea. Canaliza eso en sed, lo que agrega s /.*/ reemplaza al final, lo que da un script de 1 línea que se canaliza al último sed para que se ejecute como un script en el archivo.
así que si regex = #include y replace = blah y la primera aparición que grep encuentra está en la línea 5, entonces los datos transmitidos a la última sed serían 5s /.*/ blah /.
Si alguien vino aquí para reemplazar un personaje por la primera vez que aparece en todas las líneas (como yo), usa esto:
sed ''/old/s/old/new/1'' file
-bash-4.2$ cat file
123a456a789a
12a34a56
a12
-bash-4.2$ sed ''/a/s/a/b/1'' file
123b456a789a
12b34a56
b12
Cambiando 1 a 2, por ejemplo, puede reemplazar todos los segundos a solo en su lugar.
Simplemente agregue el número de ocurrencias al final:
sed s/#include/#include "newfile.h"/n#include/1
Una descripción general de las muchas respuestas existentes útiles, complementadas con explicaciones :
Los ejemplos aquí usan un caso de uso simplificado: reemplace la palabra ''foo'' por ''bar'' en la primera línea coincidente solamente.
Debido al uso de las cadenas entre comillas ANSI C ( $''...''
) para proporcionar las líneas de entrada de muestra, bash
, ksh
o zsh
se asume como el shell.
GNU sed
solo:
La respuesta de Ben Hoffstein nos muestra que GNU proporciona una extensión a la especificación POSIX para sed
que permite la siguiente forma de 2 direcciones: 0,/re/
( re
representa una expresión regular arbitraria aquí).
0,/re/
permite que la expresión regular coincida también en la primera línea . En otras palabras: una dirección de este tipo creará un rango desde la 1ra línea hasta e incluyendo la línea que coincida con si ocurre re
en la 1ra línea o en cualquier línea subsiguiente.
- Contraste esto con la forma
1,/re/
compatible con POSIX1,/re/
, que crea un rango que coincide desde la primera línea hasta e incluyendo la línea que coincide conre
en las líneas siguientes ; en otras palabras: esto no detectará la primera aparición de una coincidencia si ocurre en la 1ª línea y también evita el uso de taquigrafía//
para reutilizar la expresión regular utilizada más recientemente (consulte el siguiente punto). [1]
Si combina una dirección 0,/re/
con una s/.../.../
(sustitución) que usa la misma expresión regular, su comando solo realizará la sustitución en la primera línea que coincida con re
.
sed
proporciona un atajo conveniente para reutilizar la expresión regular aplicada más recientemente : un par delimitador vacío , //
.
$ 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 sedimento con características POSIX como BSD (OS X) sed
(también funcionará con GNU sed
):
Como 0,/re/
no se puede usar y la forma 1,/re/
no detectará re
si ocurre en la primera línea (ver arriba), se requiere un manejo especial para la 1ra línea .
La respuesta de MikhailVS menciona la técnica, poniendo 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 acceso directo a la expresión regular
//
vacía se emplea dos veces aquí: una vez para el punto final del rango, y una vez en la llamadas
; en ambos casos, regexfoo
se reutiliza implícitamente, lo que nos permite no tener que duplicarlo, 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 cont
aquí; dividir estratégicamente la secuencia de comandos en varias opciones-e
es una alternativa al uso de una nueva línea real: finalizar cada fragmento de script-e
donde una línea nueva normalmente debería ir.
1 s/foo/bar/
reemplaza a foo
en la primera línea solamente, si se encuentra allí. Si es así, t
ramifica al final de la secuencia de comandos (omite los comandos restantes en la línea). (La función t
bifurca a una etiqueta solo si la llamada más reciente realizó una sustitución real, en ausencia de una etiqueta, como es el caso aquí, el final del script se ramifica a).
Cuando eso sucede, la dirección de rango 1,//
, que normalmente encuentra la primera ocurrencia 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 coincidencia en la 1ª línea, se ingresará 1,//
y se encontrará la primera coincidencia verdadera.
El efecto neto es el mismo que con 0,/re/
GNU sed
: solo se reemplaza la primera ocurrencia, ya sea que ocurra en la 1.ª línea o en cualquier otra.
Enfoques fuera de rango
la respuesta de potong demuestra técnicas de bucle que eluden la necesidad de un rango ; ya que utiliza la sintaxis sed
GNU , aquí están los equivalentes que cumplen con POSIX :
Técnica de bucle 1: en la primera coincidencia, realice la sustitución, luego ingrese un bucle que simplemente imprime 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, 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 una subsiguiente s//
:
- sed ''1,/foo/ s/foo/bar/'' <<<$''1foo/n2foo''
produce $''1bar/n2bar''
; es decir, se actualizaron ambas líneas, porque la línea número 1
coincide con la 1.ª línea, y regex /foo/
- el final del rango - solo se busca a partir de la siguiente línea. Por lo tanto, ambas líneas se seleccionan en este caso, y la s/foo/bar/
sustitución se realiza en ambos.
- 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 1ª línea (debido a que la línea número 1
inicia el rango), aún no se ha aplicado ninguna expresión regular, por lo que //
no hace referencia a nada.
Con la excepción de 0,/re/
syntax especial de GNU sed
, cualquier rango que comience con un número de línea efectivamente impide el uso de //
.
Una colección completa de respuestas sobre Source . También resalta que algunas respuestas proporcionadas por las personas no funcionarán con la versión no sed de GNU, por ej.
sed ''0,/RE/s//to_that/'' file
en versión no GNU tendrá que ser
sed -e ''1s/RE/to_that/;t'' -e ''1,/RE/s//to_that/''
Sin embargo, esta versión no funcionará con gnu sed.
Aquí hay una versión que funciona con ambos:
-e ''/RE/{s//to_that/;:a'' -e ''$!N;$!ba'' -e ''}''
ex:
sed -e ''/Apple/{s//Banana/;:a'' -e ''$!N;$!ba'' -e ''}'' filename
Una posible solución:
/#include/!{p;d;}
i/
#include "newfile.h"
:
n
b
Explicación:
- lea las líneas hasta que encuentre el #include, imprima estas líneas y luego comience un nuevo ciclo
- inserte la nueva línea de incluir
- ingrese un bucle que solo lea líneas (por defecto sed también imprimirá estas líneas), no volveremos a la primera parte del script desde aquí
Usando FreeBSD ed
y evite el error de "no coincidencia" de ed
en caso de que no haya ninguna instrucción de include
en un archivo para ser procesada:
teststr=''
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
''
# using FreeBSD ed
# to avoid ed''s "no match" error, see
# *emphasized text*http://codesnippets.joyent.com/posts/show/11917
cat <<-''EOF'' | sed -e ''s/^ *//'' -e ''s/ *$//'' | ed -s <(echo "$teststr")
H
,g/# *include/u/
u/
i/
#include "newfile.h"/
.
,p
q
EOF
sed tiene una sintaxis muy simple para esto, ''-i'' es interactivo (no hay necesidad de newfile). Para reemplazar solo la primera instancia:
sed -i ''s/foo/bar/'' file
para reemplazar a nivel mundial que usaría
sed -i ''s/foo/bar/g'' file
En su ejemplo yo usaría (^ y $ son inicio y fin de línea, respectivamente)
sed -i ''s/^#include/#include/n#include/'' file
# 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 solo con GNU sed
.
sed ''0,/RE/s//to_that/'' file
#!/bin/sed -f
1,/^#include/ {
/^#include/i/
#include "newfile.h"
}
Cómo funciona este script: para las líneas entre 1 y el primer #include
(después de la línea 1), si la línea comienza con #include
, entonces anteponga la línea especificada.
Sin embargo, si el primer #include
está en la línea 1, entonces tanto la línea 1 como el siguiente #include
tendrán la línea al principio. Si está utilizando GNU sed
, tiene una extensión donde 0,/^#include/
(en lugar de 1,
) hará lo correcto.
sed ''0,/pattern/s/pattern/replacement/'' filename
esto funcionó para mí.
ejemplo
sed ''0,/<Menu>/s/<Menu>/<Menu><Menu>Sub menu<//Menu>/'' try.txt > abc.txt
Nota del editor: ambos funcionan solo con GNU sed
.