Pregunta

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.

Como lo consigo 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.

¿Fue útil?

Solución

 # 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

Otros consejos

Escriba un script sed que solo reemplace la primera aparición de " Apple " por " Banana "

Ejemplo de entrada: Salida:

     Apple       Banana
     Orange      Orange
     Apple       Apple

Este es el guión 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,/<Menu>/s/<Menu>/<Menu><Menu>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' con 'bar' solo en la primera línea coincidente.
Debido al uso de cadenas con comillas C ANSI ($'...') para proporcionar las líneas de entrada de muestra, se asume bash, ksh o zsh como el shell.


GNU sed solamente:

La respuesta de Ben Hoffstein nos muestra que GNU proporciona una extensión a especificación POSIX para 0,/re/ que permite el siguiente formulario de 2 direcciones: re (1,/re/ representa una expresión regular arbitraria aquí).

// permite que la expresión regular coincida con en la primera línea también . En otras palabras: dicha dirección creará un rango desde la primera línea hasta e incluyendo la línea que coincida con s/.../.../, ya sea s en la primera línea o en cualquier línea posterior.

  • Compare esto con el formulario compatible con POSIX foo , que crea un rango que coincide desde la primera línea hasta e incluye la línea que coincide con t en posterior líneas; en otras palabras: este no detectará la primera aparición de una -e coincidencia si ocurre en la primera línea y también evita el uso de la taquigrafía 1 s/foo/bar/ para reutilizar la expresión regular utilizada más recientemente (ver punto siguiente). [1]

Si combina una dirección 1,// con una llamada 2 (sustitución) que usa la expresión regular same , su comando efectivamente solo realizará la sustitución en el primero línea que coincide con s//.
sed '1,/foo/ s/foo/bar/' <<<$'1foo\n2foo' proporciona un práctico acceso directo para reutilizar la expresión regular aplicada más recientemente : un par delimitador vacío , $'1bar\n2bar' .

$ 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 1 solo con funciones POSIX como BSD (macOS) /foo/ (también funcionará con GNU s/foo/bar/):

Dado que sed '1,/foo/ s//bar/' <<<$'1foo\n2foo\n3foo' no se puede usar y el formulario sed: first RE may not be empty no detectará sed: -e expression #1, char 0: no previous regular expression si ocurre en la primera línea (ver arriba), se requiere manejo especial para la primera línea .

La respuesta de MikhailVS menciona la técnica, puesta 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 vacío regex <=> se emplea dos veces aquí: una para el punto final del rango y otra en la llamada <=>; en ambos casos, regex <=> 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 <=> 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 <=> aquí; dividir estratégicamente el script en múltiples opciones de <=> es una alternativa al uso de una nueva línea real: finalice cada <=> fragmento del script donde normalmente debería ir una nueva línea.

<=> reemplaza <=> solo en la primera línea, si se encuentra allí. Si es así, <=> se bifurca al final del script (omite los comandos restantes en la línea). (La función <=> se ramifica 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 de la secuencia de comandos se ramifica).

Cuando eso sucede, la dirección de rango <=>, 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 está <=>.

Por el contrario, si no hay una coincidencia en la primera línea, se ingresará <=> y terminarád el verdadero primer partido.

El efecto neto es el mismo que con GNU <=> 's <=>: solo se reemplaza la primera aparición, ya sea que ocurra en la primera línea o en cualquier otra.


Enfoques que no son de rango

la respuesta de potong demuestra loop técnicas que evitar la necesidad de un rango ; como usa la sintaxis 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 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 toda la entrada en la memoria, luego realice una única sustitución .

$ 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 <=>, con y sin un <=> siguiente:
- <=> produce <=>; es decir, ambas líneas se actualizaron, porque el número de línea <=> coincide con la primera línea, y la expresión regular <=>, el final del rango, solo se busca a partir del siguiente línea. Por lo tanto, se seleccionan ambas líneas en este caso, y la sustitución <=> se realiza en ambas.
- <=> falla : con <=> (BSD / macOS) y <=> (GNU), porque, en el momento en que se procesa la primera línea (debido al número de línea <=> comenzando el rango), aún no se ha aplicado expresión regular, por lo que <=> no se refiere a nada.
Con la excepción de la sintaxis especial <=> de GNU <=>, cualquier rango que comienza con un número de línea impide efectivamente el uso de <=>.

Podría 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 lo hemos procesado.

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

1;

Esto significa " imprime la línea " - una acción vacía por defecto imprime $ 0, que imprime toda la línea. Una línea y más fácil de entender que sed IMO :-)

Una colección bastante completa de respuestas en linuxtopia sed FAQ . También destaca que algunas respuestas que las personas proporcionaron no funcionarán con una versión de sed que no sea GNU, por ejemplo,

sed '0,/RE/s//to_that/' file

en una versión que no sea 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 la primera #include (después de la línea 1), si la línea comienza con sed, añada la línea especificada.

Sin embargo, si el primer 0,/^#include/ está en la línea 1, entonces la línea 1 y el siguiente 1, tendrán la línea antepuesta. Si está utilizando GNU <=>, tiene una extensión donde <=> (en lugar de <=>) hará lo correcto.

Una posible solución:

    /#include/!{p;d;}
    i\
    #include "newfile.h"
    :
    n
    b

Explicación:

  • lee las líneas hasta encontrar el #include, imprime estas líneas y luego comienza un nuevo ciclo
  • inserte la nueva línea de inclusión
  • ingrese un bucle que solo lea líneas (de forma predeterminada, sed también imprimirá estas líneas), no volveremos a la primera parte del script desde aquí

Sé que esta es una publicación antigua 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 usa grep para encontrar la primera ocurrencia y detente allí. También imprima el número de línea, es decir, 5: línea. Canalice eso en sed y elimine el: y cualquier cosa posterior para que quede un número de línea. Canalice eso en sed que agrega s /.*/ replace al final, lo que da un script de 1 línea que se canaliza en el último sed para ejecutarse como script en el archivo.

así que si regex = #include y replace = blah y la primera aparición grep encuentra en la línea 5, entonces los datos canalizados al último sed serían 5s /.*/ blah /.

Si alguien vino aquí para reemplazar un personaje por primera vez en todas las líneas (como yo), use 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

Al cambiar 1 a 2, por ejemplo, puede reemplazar todas las segundas solo en su lugar.

haría esto con un script awk:

BEGIN {i=0}
(i==0) && /#include/ {print "#include \"newfile.h\""; i=1}
{print $0}    
END {}

luego ejecútelo con awk:

awk -f awkscript headerfile.h > headerfilenew.h

puede ser descuidado, soy nuevo en esto.

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

Finalmente conseguí que esto funcionara en una secuencia de comandos Bash utilizada 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

Solo cambia la primera aparición.

${nowms} es el tiempo en milisegundos establecido por un script Perl, $counter es un contador utilizado para el control de bucle dentro del script, \ permite que el comando continúe en la siguiente línea.

El archivo se lee y stdout se redirige a un archivo de trabajo.

Según tengo entendido, 1,/====RSSpermalink====/ le dice a sed cuándo detenerse estableciendo una limitación de rango, y luego s/====RSSpermalink====/${nowms}/ es el comando familiar sed para reemplazar la primera cadena por la segunda.

En mi caso, pongo el comando entre comillas dobles porque lo estoy usando en un script Bash con variables.

Usando FreeBSD ed y evitar edEl error "no coincide" en caso de que no haya include declaración en un archivo a procesar:

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

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

Con la opción -z de GNU sed, puede procesar todo el archivo como si fuera solo una línea. De esa forma, un s/…/…/ solo reemplazaría la primera coincidencia en todo el archivo. Recuerde: sed solo reemplaza la primera coincidencia en cada línea, pero con la opción s/text.*// s/text[^\n]*// trata el archivo completo como una sola línea.

sed -z 's/#include/#include "newfile.h"\n#include'

En el caso general, debe reescribir su expresión sed ya que el espacio del patrón ahora contiene todo el archivo en lugar de solo una línea. Algunos ejemplos:

  • [^\n] puede reescribirse como [^\n]*. text coincide con todo excepto el carácter de nueva línea. s/^text// coincidirá con todos los símbolos después de s/(^|\n)text// hasta que se llegue a una nueva línea.
  • s/text$// puede reescribirse como s/text(\n|$)//.
  • <=> puede reescribirse como <=>.

El siguiente comando elimina la primera aparición de una cadena, dentro de un archivo. También elimina la línea vacía. Se presenta en un archivo xml, pero funcionaría con cualquier archivo.

Útil si trabaja con archivos xml y desea eliminar una etiqueta. En este ejemplo, elimina la primera aparición de & Quot; isTag & Quot; etiqueta.

Comando:

sed -e 0,/'<isTag>false<\/isTag>'/{s/'<isTag>false<\/isTag>'//}  -e 's/ *$//' -e  '/^$/d'  source.txt > output.txt

Archivo fuente (source.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

Nada nuevo, pero quizás 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

La extracción de la ID de la ventana con xwininfo -name unity-launcher|sed -rn '0,/^xwininfo: Window id: (0x[0-9a-fA-F]+).*/ s%%\1%p' produce:

0x2200003

POSIXly (también válido en sed), solo se usa una expresión regular, necesita memoria solo para una línea (como es habitual):

sed '/\(#include\).*/!b;//{h;s//\1 "newfile.h"/;G};:1;n;b1'

Explicado:

sed '
/\(#include\).*/!b          # Only one regex used. On lines not matching
                            # the text  `#include` **yet**,
                            # branch to end, cause the default print. Re-start.
//{                         # On first line matching previous regex.
    h                       # hold the line.
    s//\1 "newfile.h"/      # append ` "newfile.h"` to the `#include` matched.
    G                       # append a newline.
  }                         # end of replacement.
:1                          # Once **one** replacement got done (the first match)
n                           # Loop continually reading a line each time
b1                          # and printing it by default.
'                           # end of sed script.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top