Reemplazar una cadena que contenga caracteres de nueva línea

10

Con el bashshell, en un archivo con filas como las siguientes

first "line"
<second>line and so on

Me gustaría reemplazar una o más ocurrencias de "line"\n<second>con other charactersy obtener cada vez:

first other characters line and so on

Así que tengo que reemplazar una cadena ambos con caracteres especiales como "y <y con un carácter de nueva línea.

Después de buscar entre las otras respuestas, descubrí que sedpuede aceptar nuevas líneas en el lado derecho del comando (por lo tanto, la other characterscadena), pero no en el izquierdo.

¿Hay alguna manera (más simple que esto ) de obtener este resultado con sedo grep?

BowPark
fuente
¿Estás trabajando con una Mac? la \ndeclaración de ewline que haces es por eso que pregunto. las personas rara vez preguntan si pueden hacer lo mismo s//\n/que usted con GNU sed, aunque la mayoría de los demás sedrechazarán ese escape en el lado derecho. aún así, el \nescape funcionará a la izquierda en cualquier POSIX sedy puede traducirlos de forma portátil como y/c/\n/si tuviera el mismo efecto s/c/\n/gy no siempre es tan útil.
mikeserv

Respuestas:

3

Tres sedcomandos diferentes :

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'

Los tres se basan en el s///comando básico de sustitución:

s/"[^"]*"\n<[^>]*>/other characters /

También todos intentan tener cuidado en el manejo de la última línea, ya que los seds tienden a diferir en su salida en casos extremos. Este es el significado de $!una dirección que coincide con cada línea que !no es la $última.

También usan el Ncomando ext para agregar la siguiente línea de entrada al espacio de patrón siguiendo un \ncarácter de línea de flujo. Cualquiera que haya estado sedingiriendo durante un tiempo habrá aprendido a confiar en el \npersonaje ewline, porque la única forma de obtener uno es ponerlo explícitamente allí.

Los tres intentan leer con la menor cantidad de información posible antes de tomar medidas; sedactúa tan pronto como sea posible y no necesita leer un archivo de entrada completo antes de hacerlo.

Aunque lo hacen todo N, los tres difieren en sus métodos de recursión.

Primer comando

El primer comando emplea un N;P;Dbucle muy simple . Estos tres comandos están integrados en cualquier compatible con POSIX sedy se complementan muy bien entre sí.

  • N- como ya se mencionó, agrega la Nlínea de entrada ext al espacio de patrón después de un \ndelimitador de línea e insertado .
  • P- como p; que PRints patrón espacio - pero sólo hasta a la primera que ocurre \ncarácter ewline. Y así, dada la siguiente entrada / comando:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed Prints solo uno . Sin embargo, con ...

  • D- como d; que Deletes patrón-espacio y comienza otro ciclo-line. A diferencia d , Delimina solo hasta la primera línea de \nflujo que ocurre en el espacio de patrones. Si hay más en el espacio de patrón después del \ncarácter de línea ew, sedcomienza el siguiente ciclo de línea con lo que queda. Si den el ejemplo anterior se reemplazara con a D, por ejemplo, sedse Pborraría uno y dos .

Este comando solo se repite para las líneas que no coinciden con la s///instrucción de sustitución. Debido a que la s///sustitución elimina la línea de \new añadida N, nunca queda nada cuando se sed Delige el espacio de patrón.

Se podrían hacer pruebas para aplicar el Py / o Dselectivamente, pero hay otros comandos que se ajustan mejor a esa estrategia. Debido a que la recursividad se implementa para manejar líneas consecutivas que coinciden solo con una parte de la regla de reemplazo, las secuencias consecutivas de líneas que coinciden con ambos extremos de la s///sustitución no funcionan bien .:

Dada esta entrada:

first "line"
<second>"line"
<second>"line"
<second>line and so on

... imprime ...

first other characters "line"
<second>other characters line and so on

Sin embargo, maneja

first "line"
second "line"
<second>line

... bien.

Segundo comando

Este comando es muy similar al tercero. Ambos emplean una etiqueta de :brancho / test (como también se demuestra en la respuesta de Joeseph R. aquí ) y vuelven a ella dadas ciertas condiciones.

  • -e :n -e- los sedscripts portátiles delimitarán una :definición de etiqueta con un \newline o una nueva -edeclaración de ejecución en línea .
    • :n- define una etiqueta llamada n. Esto se puede devolver en cualquier momento con bno tn.
  • tn- el tcomando est regresa a una etiqueta especificada (o, si no se proporciona ninguna, abandona el script para el ciclo de línea actual) si alguna s///sustitución desde que se definió la etiqueta o desde la última vez que se llamó tests fue exitosa.

En este comando, la recursión ocurre para las líneas coincidentes. Si sedreemplaza con éxito el patrón con otros caracteres , sedvuelve a la :netiqueta e intenta nuevamente. Si s///no se realiza una sedsustitución, imprime automáticamente el espacio de patrón y comienza el siguiente ciclo de línea.

Esto tiende a manejar mejor las secuencias consecutivas. Donde falló el último, esto imprime:

first other characters other characters other characters line and so on

Tercer Comando

Como se mencionó, la lógica aquí es muy similar a la anterior, pero la prueba es más explícita.

  • /"$/bn- Esta es sedla prueba. Debido a que el bcomando ranch es una función de esta dirección, sedsolo bregresará a ranch :ndespués de que se \nagregue un ewline y el espacio de patrón todavía termine con una "comilla doble.

Se hace el menor tiempo posible entre Ny b, de esta manera, se sedpuede recopilar rápidamente la mayor cantidad de información necesaria para garantizar que la siguiente línea no coincida con su regla. La s///ubicación difiere aquí en que emplea la gbandera lobal, por lo que hará todos los reemplazos necesarios a la vez. Dada una entrada idéntica, este comando sale idénticamente al último.

mikeserv
fuente
Perdón por la pregunta trivial, pero ¿cuál es el significado DATAy cómo recibe la entrada de texto?
BowPark
@BowPark: en este ejemplo, <<\DATA\ntext input\nDATA\nestá integrado , pero ese es solo el texto entregado sedpor el shell en un documento aquí . Funcionaría tan bien como sed 'script' filenameo process that writes to stdout | sed 'script'. ¿Eso ayuda?
mikeserv
Sí, gracias. ¿Por qué sin Dcada línea modificada es doble? (Lo sed
usaste
1
@BowPark: obtienes dobles al omitir el Dporque, de lo Dcontrario, Delige de la salida lo que ahora ves duplicado. Acabo de hacer una edición, y también puedo ampliarla pronto.
mikeserv
1
@BowPark: ok, lo actualicé y proporcioné opciones. Puede ser un poco más fácil de leer / entender ahora. También me dirigí explícitamente a la Dcosa.
mikeserv
7

Bueno, puedo pensar en un par de formas simples, pero ninguna involucra grep(que de todos modos no hace sustituciones) o sed.

  1. Perl

    Para reemplazar cada aparición de "line"\n<second>con other characters, use:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

    O, para tratar múltiples ocurrencias consecutivas "line"\n<second>como una sola, y reemplazarlas por una sola other characters, use:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    

    Ejemplo:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    

    Esto -00hace que Perl lea el archivo en "modo párrafo", lo que significa que las "líneas" se definen en \n\nlugar de \n, esencialmente, que cada párrafo se trate como una línea. Por lo tanto, la sustitución coincide a través de una nueva línea.

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    

    La misma idea básica, configuramos el separador de registros ( RS) para \n\nsorber todo el archivo, luego el separador de registros de salida a nada (de lo contrario, se imprime una nueva línea adicional) y luego utilizamos la sub()función para hacer el reemplazo.

terdon
fuente
2
@mikeserv? ¿Cúal? Se supone que el segundo es, el OP dijo que quieren "reemplazar una o más ocurrencias de", por lo que comer el párrafo bien podría ser lo que esperan.
terdon
Muy buen punto. Supongo que me concentré más y obtengo cada vez , pero supongo que no está claro si eso debería ser un reemplazo por evento o un reemplazo por secuencia de eventos ... @BowPark?
mikeserv
Se necesita un reemplazo por ocurrencia.
BowPark
@BowPark OK, entonces el primer enfoque perl o awk deberían funcionar. ¿No te dan el resultado deseado?
terdon
Funciona, gracias, pero la tercera línea awkdebería ser print;}' file. Necesito evitar Perl y usarlo preferiblemente sed, de todos modos sugirió buenas alternativas.
BowPark
6

lea todo el archivo y realice un reemplazo global:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
first other characters  line followed by other characters  and last
Glenn Jackman
fuente
Si. Funciona, pero ¿qué pasa si tengo múltiples ocurrencias?
BowPark
Huh, cierto Corregido
glenn jackman
1
Lamento volver a hacer trampas, pero ${cmds}es específico de GNU: la mayoría de los otros seds requerirán un \newline o un -edescanso entre py }. Puede evitar los corchetes por completo, y de forma portátil, e incluso evitar insertar un carácter extra de línea de \ncable en la primera línea como:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv
Lo probé y parece que no es portátil. Imprime una nueva línea adicional al comienzo de la salida, pero el resultado es correcto en GNU.
BowPark
Para eliminar la nueva línea principal: sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'- sin embargo, esto se está volviendo imposible de mantener.
Glenn Jackman
3

Aquí hay una variante en la respuesta de Glenn que funcionará si tiene múltiples ocurrencias consecutivas (solo funciona con GNU sed):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

El :xes solo una etiqueta para ramificar. Básicamente, lo que esto hace es que comprueba la línea después de la sustitución y si aún coincide "line", se ramifica de nuevo a la :xetiqueta (eso es lo que bxhace) y agrega otra línea al búfer y comienza a procesarla.

Joseph R.
fuente
@mikeserv Sea específico sobre lo que quiere decir. Funcionó para mi.
Joseph R.
@mikeserv Lo siento, realmente no sé de qué estás hablando. Copié la línea de código anterior en mi terminal y funcionó correctamente.
Joseph R.
1
retraído: esto aparentemente funciona en GNU, sedque lleva su manejo de etiquetas no POSIX lo suficiente como para aceptar un espacio como delimitador para la declaración de etiquetas. Sin embargo, debe tener en cuenta que cualquier otro sedfallará allí, y fallará por N. GNU sedrompe las pautas de POSIX para imprimir el espacio de patrón antes de salir en un Nen la última línea, pero POSIX deja en claro que si Nse lee un comando en la última línea, no se debe imprimir nada .
mikeserv
Si edita la publicación para especificar GNU, revertiré mi voto y eliminaré estos comentarios. Además, puede valer la pena aprender sobre el vcomando de GNU que se interrumpe entre sí, sedpero es un no-op en GNU versiones 4 y superiores.
mikeserv
1
en ese caso voy a ofrecer una más - esto se puede hacer de forma portátil como: sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'.
mikeserv