2011-11-25 19 views
9

Tengo este archivo de prueba.use sed para reemplazar el texto entre comillas

[[email protected] ~]# cat f.txt 
"a aa" MM "bbb b" 
MM MM 
MM"b b " 
[[email protected] ~]#

Quiero reemplazar todos los caracteres de espacio en las comillas, nota, solo en las comillas. Todos los personajes fuera de las comillas no deben ser tocados. Es decir, lo que quiero es algo similar a:

"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_"

Puede esto ser implementado usando sed?

Gracias,

+2

BTW: Buena pregunta, especialmente con la buena entrada de ejemplo y la salida requerida. –

Respuesta

8

Ésta es una cuestión totalmente no trivial.

Esto funciona reemplazando el primer espacio dentro de las citas con subrayado:

$ sed 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa" MM "bbb_ b" 
MM MM 
MM"b_b " 
$ 

Para este ejemplo, donde no hay más de dos espacios dentro de cualquiera de las cotizaciones, es tentador simplemente repetir el comando, pero que da un resultado incorrecto:

$ sed -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' \ 
>  -e 's/\("[^ "]*\) \([^"]*"\)/\1_\2/g' f.txt 
"a_aa"_ MM "bbb_ b" 
MM MM 
MM"b_b_" 
$ 

Si su versión de sed apoya '' expresiones regulares extendidas, entonces esto funciona para los datos de ejemplo:

$ sed -E \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> -e 's/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/' \ 
> f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

Tiene que repetir esa horrible expresión regular para cada espacio entre comillas dobles, de ahí tres veces para la primera línea de datos.

la expresión regular puede ser explicado como:

  • Comenzando por el principio de una línea,
  • Buscar las secuencias de 'cero o más no-citas, opcionalmente seguido de una cita, no hay espacios o las cotizaciones , y una cita ', toda la asamblea repite cero o más veces,
  • Seguido de una cita, cero o más citas, espacios, un espacio y cero o más citas, y una cita.
  • Reemplace el material coincidente con la parte delantera, el material al comienzo del pasaje citado actual, un guión bajo y el material posterior del pasaje citado actual.

Debido al inicio de anclaje, esto tiene que ser repetida una vez al blanco ... pero sed tiene una estructura iterativa, por lo que se puede hacer con:

$ sed -E -e ':redo 
>   s/^(([^"]*("[^ "]*")?)*)("[^ "]*) ([^"]*")/\1\4_\5/ 
>   t redo' f.txt 
"a_aa" MM "bbb__b" 
MM MM 
MM"b_b_" 
$ 

El :redo define una etiqueta; el comando s/// es como antes; El comando t redo salta a la etiqueta si hubo alguna sustitución desde la última lectura de una línea o salta a una etiqueta.


Dada la discusión en los comentarios, hay un par de puntos vale la pena mencionar:

  1. La opción se aplica a -Esed en MacOS X (10.7.2 probado).La opción correspondiente para la versión GNU de sed es -r (o --regex-extended). La opción -E es consistente con grep -E (que también usa expresiones regulares extendidas). Los 'sistemas Unix clásicos' no son compatibles con ERE con sed (Solaris 10, AIX 6, HP-UX 11).

  2. Usted puede substituir la ? utilicé (que es el único personaje que obliga al uso de un ERE en lugar de un BRE) con *, y luego lidiar con los paréntesis (que requieren barras invertidas delante de ellos en una BRE para convertirlos en paréntesis de captura), dejando el guión:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)*\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    Esto produce el mismo resultado en la misma entrada - probé algunos patrones ligeramente más complejos en la entrada:

    "a aa" MM "bbb b" 
    MM MM 
    MM"b b " 
    "c c""d d""e e" X " f "" g " 
    "C C" "D D" "E E" x " F " " G " 
    

    Thi s da la salida:

    "a_aa" MM "bbb__b" 
    MM MM 
    MM"b_b_" 
    "c_c""d_d""e__e" X "_f_""_g_" 
    "C_C" "D_D" "E__E" x "_F_" "_G_" 
    
  3. Incluso con la notación BRE, sed apoyaron la \{0,1\} notación para especificar 0 o 1 apariciones del término RE anterior, por lo que la versión ? podría traducirse a un BRE usando:

    sed -e ':redo 
         s/^\(\([^"]*\("[^ "]*"\)\{0,1\}\)*\)\("[^ "]*\) \([^"]*"\)/\1\4_\5/g 
         t redo' f.txt 
    

    Esto produce la misma salida que las otras alternativas.

+0

Gracias. Excelente solucion Pero el interruptor de expresión regular extendida es *** - r *** en mi sistema. –

+0

@JonathanLeffler excelente uso de expresiones regulares, especialmente '(" [^ "] *")? 'Para toparse con la sustitución, pero ¿por qué'? 'Y no' * '? – potong

+0

Creo que puedes usar'? 'O' * 'successfully (' * 'funciona en los datos de muestra). Usé' '' porque podría ayudar a limitar la cantidad de retroceso en la expresión regular, que es bastante complejo. (No es una expresión regular que quisiera tener que descifrar a toda prisa!). –

0

Una respuesta alguna manera inusual en XSLT 2.0:

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    version="2.0"> 
    <xsl:output method="text"></xsl:output> 
    <xsl:template name="init"> 
     <xsl:for-each select="tokenize(unparsed-text('f.txt'),'&#10;')"> 
      <xsl:for-each select="tokenize(.,'&quot;')"> 
       <xsl:value-of select="if (position() mod 2 = 0) 
        then concat('&quot;',translate(.,' ','_'),'&quot;') else ."></xsl:value-of> 
      </xsl:for-each> 
      <xsl:text>&#10;</xsl:text> 
     </xsl:for-each> 
    </xsl:template>  
</xsl:stylesheet> 

Para probar si, acaba de obtener saxon.jar en sourceforge y utilizar la línea de comandos:

java -jar saxon9.jar -it:init regexp.xsl 

El archivo XSLT incluya la referencia a f.txt, el archivo de texto debe estar en el mismo directorio que el archivo xslt. Eso se puede cambiar fácilmente dando un parámetro a la hoja de estilo.

Funciona en una sola pasada.

0

Esto sería realmente fácil si el texto citado estuviera en líneas separadas. Entonces, un enfoque es dividir el texto para que así lo tenga, haga la transformación fácil y luego reconstruya las líneas.

Dividir el texto es fácil, pero tendremos que distinguir entre las nuevas líneas que eran

  1. ya presente en el archivo
  2. añadido por nosotros

Para hacer eso, podemos termine cada línea con un símbolo que indique a qué clase pertenece. Solo usaré 1 y 2, que corresponden directamente a lo anterior.En SED, tenemos:

sed -e 's/$/1/' -e 's/"[^"]*"/2\n&2\n/g' 

Esto produce:

2 
"a aa"2 
    MM 2 
"bbb b"2 
1 
MM MM1 
MM2 
"b b "2 
1 

Eso es fácil de transformar, sólo tiene que utilizar

sed -e '/".*"/ s/ /_/g' 

dando

2 
"a_aa"2 
    MM 2 
"bbb__b"2 
1 
MM MM1 
MM2 
"b_b_"2 
1 

Por último, necesitamos vuelve a armarlo Esto es en realidad bastante horrible en la sed, pero factible, utilizando el espacio de la bodega: (. Esto sería mucho más claro en, por ejemplo, awk)

sed -e '/1$/ {s/1$//;H;s/.*//;x;s/\n//g}' -e '/2$/ {s/2$//;H;d}' 

Pipe esos tres pasos juntos y ya está .

0

Estos pueden funcionar para usted:

sed 's/^/\n/;:a;s/\(\n[^"]*"[^ "]*\) \([^"]*"\)\n*/\1_\2\n/;ta;s/\n//;ta;s/\n//' file 

Explicación:

Anteponer un \n al inicio de la línea, esto va a ser utilizado para volcar lo largo de las sustituciones. Reemplace un solo con un _ dentro del " y mientras esté allí coloque un \n listo para la próxima ronda de sustituciones. Después de haber reemplazado todos los , elimine el \n y repita. Cuando se hayan producido todas las sustituciones, elimine el delimitador \n.

o esto:

sed -r ':a;s/"/\n/;s/"/\n/;:b;s/(\n[^\n ]*) ([^\n]*\n)/\1_\2/g;tb;s/\n/%%%/g;ta;s/%%%/"/g' file 

Explicación:

Sustituir el primer conjunto de "" 's con \n' s. Reemplace el primer espacio entre líneas nuevas con _, repita. Reemplace \n con un delimitador único (%%%), repita desde el principio. Poner en orden al final reemplazando todo %%% con ".

Una tercera manera:

sed 's/"[^"]*"/\n&\n/g;$!s/$/@@@/' file | 
sed '/"/y/ /_/;1{h;d};H;${x;s/\n//g;s/@@@/\n/g;p};d' 

Explicación:

Surround todas las expresiones citadas ("...") con saltos de línea (\n 's). Inserte un delimitador de final de línea @@@ en todas las líneas excepto en la última. Resultado de la tubería al segundo comando sed. Traducir todos a _ para líneas con " en ellos. Almacene cada línea en el espacio de espera (HS).Al final del archivo, de intercambio para el SA y eliminar todos los \n 's y reemplazar los delimitadores de fin de línea con \n' s

por último:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /' file | sh 

o sed de GNU:

sed 's/\("[^"]*"\)/$(tr '"' ' '_'"'<<<'"'"'\1'"'"')/g;s/^/echo /e' file 

dejado para que el lector funcione.

Cuestiones relacionadas