Quiere sustituir solo la primera ocurrencia con sed

26

Archivo original

claudio
antonio
claudio
michele

Quiero cambiar solo la primera aparición de "claudio" con "claudia" para que el resultado del archivo

claudia
antonio
claudio
michele

Yo he tratado

sed -e '1,/claudio/s/claudio/claudia/' nomi

Pero realice una sustitución global. ¿Por qué?

elbarna
fuente
Mire aquí linuxtopia.org/online_books/linux_tool_guides/the_sed_faq/… y también info sed: ( 0,/REGEXP/: Se puede usar un número de línea de 0 en una especificación de dirección como 0,/REGEXP/para que sedtambién intente hacer coincidir REGEXP en la primera línea de entrada. En otras palabras, 0,/REGEXP/es similar a 1,/REGEXP/, excepto que si ADDR2 coincide con la primera línea de entrada, el formulario 0, / REGEXP / considerará que finaliza el rango, mientras que el formulario 1, / REGEXP / coincidirá con el comienzo de su rango y, por lo tanto, hará que el rango abarque hasta la segunda aparición de la expresión regular)
jimmij
awk '/claudio/ && !ok { sub(/claudio/,"claudia"); ok=1 } 1' nomidebería hacer
Adam Katz
stackoverflow.com/questions/148451/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

23

Si está utilizando GNU sed, intente:

sed -e '0,/claudio/ s/claudio/claudia/' nomi

sedno comienza a verificar la expresión regular que termina un rango hasta después de la línea que comienza ese rango.

De man sed(página de manual de POSIX, énfasis mío):

Un comando de edición con dos direcciones seleccionará el rango inclusivo
desde el primer espacio patrón que coincide con la primera dirección a través de la
siguiente espacio de patrón que coincide con el segundo. 

Utilizando awk

Los rangos en el awktrabajo son más esperados:

$ awk 'NR==1,/claudio/{sub(/claudio/, "claudia")} 1' nomi
claudia
antonio
claudio
michele

Explicación:

  • NR==1,/claudio/

    Este es un rango que comienza con la línea 1 y termina con la primera aparición de claudio.

  • sub(/claudio/, "claudia")

    Mientras estamos en el rango, se ejecuta este comando sustituto.

  • 1

    La abreviatura críptica de este awk para imprimir la línea.

John1024
fuente
1
Sin sedembargo, eso supone GNU .
Stéphane Chazelas
@ StéphaneChazelas También funciona si POSIXLY_CORRECT está configurado, pero supongo que eso no significa tanto como me gustaría. Respuesta actualizada (me faltan máquinas de prueba BSD).
John1024
El awk puede, en mi opinión, ser más simple con una variable de estado booleana:awk '!r && /claudio/ {sub(/claudio/,"claudia"); r=1} 1'
glenn jackman
@glennjackman oawk !x{x=sub(/claudio/,"claudia")}1
Tampoco pude usar con éxito un delimitador diferente en la primera parte:0,/claudio/
Pat Myron
4

Aquí hay 2 esfuerzos programáticos más con sed: ambos leen todo el archivo en una sola cadena, luego la búsqueda solo reemplazará al primero.

sed -n ':a;N;$bb;ba;:b;s/\(claudi\)o/\1a/;p' file
sed -n '1h;1!H;${g;s/\(claudi\)o/\1a/;p;}' file

Con comentario:

sed -n '                # don't implicitly print input
  :a                    # label "a"
  N                     # append next line to pattern space
  $bb                   # at the last line, goto "b"
  ba                    # goto "a"
  :b                    # label "b"
  s/\(claudi\)o/\1a/    # replace
  p                     # and print
' file
sed -n '                # don't implicitly print input
  1h                    # put line 1 in the hold space
  1!H                   # for subsequent lines, append to hold space
  ${                    # on the last line
    g                     # put the hold space in pattern space
    s/\(claudi\)o/\1a/    # replace
    p                     # print
  }
' file
Glenn Jackman
fuente
3

Una nueva versión de GNU sedadmite la -zopción.

Normalmente, sed lee una línea leyendo una cadena de caracteres hasta el carácter de fin de línea (nueva línea o retorno de carro).
La versión GNU de sed agregó una característica en la versión 4.2.2 para usar el carácter "NULL" en su lugar. Esto puede ser útil si tiene archivos que usan NULL como separador de registros. Algunas utilidades de GNU pueden generar resultados que utilizan un NULL en lugar de una nueva línea, como "find. -Print0" o "grep -lZ".

Puede usar esta opción cuando desee sedtrabajar en diferentes líneas.

echo 'claudio
antonio
claudio
michele' | sed -z 's/claudio/claudia/'

devoluciones

claudia
antonio
claudio
michele
Walter A
fuente
1

Puede usar awkuna bandera para saber si el reemplazo ya se realizó. Si no, proceda:

$ awk '!f && /claudio/ {$0="claudia"; f=1}1' file
claudia
antonio
claudio
michele
fedorqui
fuente
1

En realidad, es realmente fácil si solo configura un pequeño retraso: no hay necesidad de buscar extensiones poco confiables:

sed '$H;x;1,/claudio/s/claudio/claudia/;1d' <<\IN
claudio
antonio
claudio
michele
IN

Eso solo difiere la primera línea a la segunda y la segunda a la tercera y etc.

Imprime:

claudia
antonio
claudio
michele
mikeserv
fuente
1

Y una opción más

sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/claudio/claudia/;}" -- nomi

La ventaja es que usa comillas dobles, por lo que puede usar variables dentro, es decir.

export chngFrom=claudio
export chngTo=claudia
sed --in-place=*.bak -e "1 h;1! H;\$! d;$ {g;s/${chngFrom}/${chngTo}/;}" -- nomi
utom
fuente
1
Si, tienes razón. La idea general es la misma. Pero, por favor, intente sustituir las comillas simples por comillas dobles directamente, y vea si funciona. El diablo yace en los detalles. En este ejemplo, estos son espacios y un escape. Creo que esta continuación de las respuestas anteriores puede ahorrarle tiempo a alguien. Y esa es la razón por la que decidí publicar la publicación.
utom
1

Esto también se puede hacer sin el espacio de espera y sin concatenar todas las líneas en el espacio del patrón:

sed -n '/claudio/{s/o/a/;bx};p;b;:x;p;n;bx' nomi

Explicación: Intentamos encontrar "claudio" y si lo hacemos saltamos al pequeño ciclo print-load-loop entre :xy bx. De lo contrario, imprimimos y reiniciamos el script con la siguiente línea.

sed -n '      # do not print lines by default
  /claudio/ { # on lines that match "claudio" do ...
    s/o/a/    # replace "o" with "a"
    bx        # goto label x
  }           # end of do block
  p           # print the pattern space
  b           # go to the end of the script, continue with next line
  :x          # the label x for goto commands
  p           # print the pattern space
  n           # load the next line in the pattern space (clearing old contents)
  bx          # goto the label x
  ' nomi
Lucas
fuente
1
sed -n '/claudia/{p;Q}'

sed -n '           # don't print input
    /claudia/      # regex search
    {              # when match is found do
    p;             # print line
    Q              # quit sed, don't print last buffered line
    {              # end do block
jgshawkey
fuente
1
¿Te has molestado en leer la pregunta?
don_crissti
1

Resumen

Sintaxis de GNU:

sed '/claudio/{s//claudia/;:p;n;bp}' file

O incluso (para usar solo una vez la palabra a reemplazar:

sed '/\(claudi\)o/{s//\1a/;:p;n;bp}' file

O, en la sintaxis POSIX:

sed -e '/claudio/{s//claudia/;:p' -e 'n;bp' -e '}' file

funciona en cualquier sed, procesa solo tantas líneas como sea necesario para encontrar el primero claudio, funciona incluso si claudioestá en la primera línea y es más corto ya que usa solo una cadena de expresiones regulares.

Detalle

Para cambiar solo una línea , debe seleccionar solo una línea.

Usando un 1,/claudio/(de su pregunta) selecciona:

  • desde la primera línea (incondicionalmente)
  • a la siguiente línea que contiene la cadena claudio.
$ cat file
claudio 1
antonio 2
claudio 3
michele 4

$ sed -n '1,/claudio/{p}' file
claudio 1
antonio 2
claudio 3

Para seleccionar cualquier línea que contenga claudio, use:

$ sed -n `/claudio/{p}` file
claudio 1
claudio 3

Y para seleccionar solo el primero claudio en el archivo, use:

sed -n '/claudio/{p;q}' file
claudio 1

Luego, puede hacer una sustitución solo en esa línea:

sed '/claudio/{s/claudio/claudia/;q}' file
claudia 1

Lo que cambiará solo la primera aparición de la coincidencia de expresiones regulares en la línea, incluso si puede haber más de una, en la primera línea que coincida con la expresión regular.

Por supuesto, la /claudio/expresión regular podría simplificarse para:

$ sed '/claudio/{s//claudia/;q}' file
claudia 1

Y, entonces, lo único que falta es imprimir todas las demás líneas sin modificar:

sed '/claudio/{s//claudia/;:p;n;bp}' file
Isaac
fuente