Insertar línea después del primer partido usando sed

245

Por alguna razón, parece que no puedo encontrar una respuesta directa a esto y en este momento estoy un poco apretado. ¿Cómo haré para insertar una línea de texto de elección después de que la primera línea coincida con una cadena específica usando el sedcomando? Yo tengo ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

Y quiero insertar una línea después de la CLIENTSCRIPT=línea que resulta en ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
usuario2150250
fuente

Respuestas:

379

Intenta hacer esto usando GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

si desea sustituir en el lugar , use

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Salida

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

Doc

Gilles Quenot
fuente
¡Gracias! ¿Y para que vuelva a escribir en el archivo sin imprimir el contenido del archivo?
user2150250
11
Tenga en cuenta que ambos asumen GNU sed. Esa no es la sedsintaxis estándar y no funcionará con ninguna otra sedimplementación.
Stephane Chazelas
Tuve problemas para hacer que esto funcionara para mí porque estaba usando un delimitador diferente. Después de RTFM, me di cuenta de que tenía que comenzar la expresión regular con una \, luego el delimitador.
Christy
10
¿Cómo se aplica SOLO al primer partido? está claro que agrega texto después del partido, pero ¿cómo se sabe solo en el primer partido?
Mohammed Noureldin
77
Esto agrega el texto después de cada partido, para mí.
schoppenhauer
49

Tenga en cuenta la sedsintaxis estándar (como en POSIX, compatible con todas las sedimplementaciones conformes (GNU, OS / X, BSD, Solaris ...)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

O en una línea:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

( -expressions (y el contenido de -files) se unen con nuevas líneas para formar las sedinterpretaciones del script sed ).

La -iopción para la edición en el lugar también es una extensión de GNU, algunas otras implementaciones (como FreeBSD) son compatibles-i '' con eso.

Alternativamente, para la portabilidad, puede usar perlen su lugar:

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

O podrías usar edo ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Stephane Chazelas
fuente
2
Puedo estar equivocado, pero la corriente sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' fileescapa a la cita al final del primer parámetro y rompe el comando.
Carrito abandonado el
1
@AbandonedCart, en conchas del Bourne, csho rcfamilia, '...'son citas fuertes dentro de las cuales la barra invertida no es especial. La única excepción que sé es el fishshell.
Stephane Chazelas
1
Terminal en MacOS (y posteriormente un script ejecutado en terminal) también es una excepción, aparentemente. Encontré una sintaxis alternativa, pero gracias de todos modos.
Carrito abandonado el
1
@AbandonedCart, eso es otra cosa. Eso es macOS que sedno cumple con POSIX aquí. Eso hace que mi declaración sobre que sea portátil sea incorrecta (para la variante de una línea). Le pediré confirmación al opengroup si realmente es una no conformidad o una mala interpretación del estándar de mi parte.
Stephane Chazelas
19

Una POSIX compatible con el scomando:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
fuente
1
... que incluso admite la inserción de nuevas líneas. ¡Me encanta!
Stephan Henningsen
1
Vea también sed -e '/.../s/$/\' -e 'CLI.../'qué evitaría problemas con líneas que contienen secuencias de bytes que no forman caracteres válidos.
Stephane Chazelas
14

Comando Sed que funciona en MacOS (al menos, OS 10) y Unix por igual (es decir, no requiere gnu sed como lo hace Gilles (actualmente aceptado)):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

Esto funciona en bash y quizás también en otros shells que conocen el estilo de cotización de evaluación $ '\ n'. Todo puede estar en una línea y funcionar en comandos antiguos / POSIX sed. Si puede haber varias líneas que coincidan con CLIENTSCRIPT = "foo" (o su equivalente) y solo desea agregar la línea adicional la primera vez, puede volver a trabajarla de la siguiente manera:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(esto crea un bucle después del código de inserción de línea que simplemente pasa por el resto del archivo, sin volver nunca más al primer comando sed).

Puede notar que agregué un '^ *' al patrón coincidente en caso de que la línea aparezca en un comentario, digamos o esté sangrada. No es 100% perfecto, pero cubre algunas otras situaciones que probablemente sean comunes. Ajuste según sea necesario ...

Estas dos soluciones también resuelven el problema (para la solución genérica para agregar una línea) de que si su nueva línea insertada contiene barras invertidas o símbolos sin escape, serán interpretados por sed y probablemente no saldrán de la misma manera, tal como \nes, por ejemplo. \0Sería la primera línea coincidente. Especialmente útil si está agregando una línea que proviene de una variable donde de lo contrario tendría que escapar de todo primero usando $ {var //} antes, u otra declaración sed, etc.

Esta solución es un poco menos desordenada en los scripts (sin embargo, citar y \ n no es fácil de leer), cuando no desea colocar el texto de reemplazo para un comando al comienzo de una línea, por ejemplo, en una función Con líneas dentadas. Aproveché que $ '\ n' es evaluado a una nueva línea por el shell, no está en valores regulares '\ n' entre comillas simples.

Se está haciendo lo suficientemente largo, aunque creo que perl / incluso awk podría ganar debido a que es más legible.

Breezer
fuente
1
¡Gracias por la respuesta! El primero también funciona como un encanto en el sistema operativo AIX.
abhishek
¿Cómo se hace esto sino para la inserción de varias líneas?
tatsu
1
POSIX sed admite nuevas líneas literales en la cadena de reemplazo si las "escapa" con una barra diagonal inversa ( fuente ). Entonces: s/CLIENTSCRIPT="foo"/&\ [Entrar] CLIENTSCRIPT2="hello"/ . La barra diagonal inversa debe ser el último carácter de la línea (sin espacios en blanco después), al contrario de cómo se ve en este comentario.
TheDudeAbides
1
@tatsu Las líneas nuevas en la cadena de reemplazo de s///, así como las de los comandos ay ise pueden "escapar", utilizando el mismo método que describo en mi comentario anterior.
TheDudeAbides
4

Tal vez sea un poco tarde para publicar una respuesta para esto, pero encontré algunas de las soluciones anteriores un poco engorrosas.

Intenté el reemplazo simple de cadenas en sed y funcionó:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

El signo & refleja la cadena coincidente, y luego agrega \ ny la nueva línea.

Como se mencionó, si desea hacerlo en el lugar:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Otra cosa. Puedes unir usando una expresión:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

Espero que esto ayude a alguien

siwesam
fuente
Esto funciona bien en Linux, donde GNU sed es el valor predeterminado, porque comprende \nla cadena de reemplazo. Llano-vainilla (POSIX) sedno no entiende \nen la cadena de reemplazo, sin embargo, por lo que este no funcionará en BSD o MacOS, a menos que haya instalado GNU sed de alguna manera. Con vanilla sed puede incrustar nuevas líneas "escapando" de ellas, es decir, terminando la línea con una barra diagonal inversa, luego presionando [Enter] ( referencia , debajo del encabezado para [2addr]s/BRE/replacement/flags). Esto significa que su comando sed tiene que abarcar varias líneas en la terminal.
TheDudeAbides
Otra opción para obtener una nueva línea literal en la cadena de reemplazo es usar la función de cotización ANSI-C en Bash, como se menciona en una de las otras respuestas .
TheDudeAbides
1

Tuve una tarea similar y no pude hacer funcionar la solución perl anterior.

Aquí está mi solución:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Explicación:

Utiliza una expresión regular para buscar una línea en mi archivo /etc/mysql/my.cnf que solo contenía [mysqld]y lo reemplazó por

[mysqld] collation-server = utf8_unicode_ci

agregando efectivamente la collation-server = utf8_unicode_cilínea después de la línea que contiene [mysqld].

Caleb
fuente
0

Tuve que hacer esto recientemente también para Mac y Linux OS y después de navegar por muchas publicaciones y probar muchas cosas, en mi opinión particular, nunca llegué a donde quería, que es: una solución lo suficientemente simple como para comprender usando y comandos estándar con patrones simples, un revestimiento, portátil, expandible para agregar más restricciones. Luego traté de mirarlo con una perspectiva diferente, fue entonces cuando me di cuenta de que podía prescindir de la opción "un trazador de líneas" si un "trazador de líneas 2" cumplía con el resto de mis criterios. Al final se me ocurrió esta solución, me gusta, funciona tanto en Ubuntu como en Mac, que quería compartir con todos:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

En el primer comando, grep busca números de línea que contengan "foo", cut / head selecciona la primera aparición, y la operación aritmética incrementa ese primer número de línea de aparición en 1 ya que quiero insertar después de la aparición. En el segundo comando, es una edición de archivo en el lugar, "i" para insertar: una nueva línea que cita ansi-c, "barra", luego otra nueva línea. El resultado es agregar una nueva línea que contiene "barra" después de la línea "foo". Cada uno de estos 2 comandos se puede ampliar a operaciones más complejas y coincidencia.

momonari8
fuente