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

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.

      # 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 

    Fuente

    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 
     sed '0,/pattern/s/pattern/replacement/' filename 

    esto funcionó para mí.

    ejemplo

     sed '0,//s//Sub menu< \/Menu>/' try.txt > abc.txt 

    Nota del editor: ambos funcionan solo con GNU sed .

    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 POSIX 1,/re/ , que crea un rango que coincide desde la primera línea hasta e incluyendo la línea que coincide con re en las líneas siguientes ; en otras palabras: esto no detectará la primera aparición de una coincidencia si ocurre en la 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 (macOS) 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 llamada s ; en ambos casos, regex foo 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 con t 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 syntax 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 // .

    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 🙂

    Una colección completa de respuestas sobre linuxtopia sed . 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 

    Simplemente agregue el número de ocurrencias al final:

     sed s/#include/#include "newfile.h"\n#include/1 
     #!/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.

    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í

    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.

    Como sugerencia alternativa, puede consultar el comando ed .

     man 1 ed teststr=' #include  #include  #include  ' # 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 

    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.

    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  #include  #include  ' # 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 

    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... 

    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.

    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,/'false< \/isTag>'/{s/'false< \/isTag>'//} -e 's/ *$//' -e '/^$/d' source.txt > output.txt 

    Archivo fuente (fuente.txt)

       true false  esa_jee6 false   false    

    Archivo de resultados (output.txt)

       true  esa_jee6 false   false    

    ps: no funcionó para mí en Solaris SunOS 5.10 (bastante antiguo), pero funciona en Linux 2.6, sed versión 4.1.5

    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 

    sed tiene una syntax 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