reemplace varias comas fuera de uno o más conjuntos de llaves y excepción que en uno o más conjuntos de llaves

reemplace varias comas fuera de uno o más conjuntos de llaves y excepción que en uno o más conjuntos de llaves

En el archivo de texto tengo varios registros. Cada registro tiene varias columnas separadas por comas, algunas columnas tienen un conjunto de llaves y otras tienen más de una llave.

Necesito:

  1. Si se encuentra una coma fuera de uno o más conjuntos de llaves, la coma debe reemplazarse con una barra vertical.

  2. Si se encuentra una coma dentro de uno o más conjuntos de llaves, la coma debe dejarse como está. Entonces, dado que THING1,{THING2,{THING3,}},THING4la salida debería ser THING1|{THING2,{THING3,}}|THING4.

Registro de muestra:

(999969,2500,"777777888",0,"45265","65522",NULL,10001,2014-09-15 10:27:07.287,2014-09-15 10:28:49.085,2014-09-15 06:28:50.000,0,0,NULL,"text","401c4133091977",{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL},NULL,{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]},{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1},NULL,NULL,NULL,NULL,NULL,{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

el resultado debería ser:

(**999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|**{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}**|NULL|**{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}**|**{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}**|NULL|NULL|NULL|NULL|NULL|**{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

Respuesta1

Puedes hacer esto simplemente mediante la combinación Perl+ regex.

perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file

Ejemplo:

$ perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file
(999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}|NULL|{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}|{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}|NULL|NULL|NULL|NULL|NULL|{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL})

Explicación:

Dividí la expresión regular en dos partes para la explicación.

  1. (\{(?:[^{}]|(?1))*\})
  2. (*SKIP)(*F)|,

1ra parte

(\{(?:[^{}]|(?1))*\})
  • Este truco funcionará sólo si las llaves están emparejadas correctamente.
  • ()Estos son grupos de captura, utilizados para capturar personajes.
  • \{coincide con una llave de apertura.
  • (?:[^{}]|(?1))

    • (?:...)Llamado grupo de no captura.
    • [^{}]Coincidiría con cualquier personaje pero no de {o}
    • |Operador lógico OR.
    • (?1)Recurre al primer grupo de captura.
  • (?:[^{}]|(?1))*Haga coincidir el token anterior cero o más veces.
  • \}Símbolo de cierre }.

Considere el siguiente ejemplo y el patrón que coincide con los corchetes anidados en él.

Cadena:

h{foo{bar}foobar}

Patrón:

h(\{(?:[^{}]|(?1))*\})
  • Al principio, el motor de expresiones regulares intenta hacer coincidir el h(que estaba en el patrón) contra la cadena de entrada. Entonces la primera letra hcoincidió.
  • El patrón para encontrar el paréntesis equilibrado se introduce en un grupo de captura.
  • Ahora el motor toma el segundo carácter (es decir, \{) del patrón e intenta compararlo con la cadena de entrada. Entonces el primero {consiguiócapturado. Utilicé la palabra capturado en lugar de coincidir porque \{está dentro de un grupo de captura.
  • (?:[^{}]|(?1))*Esto le dice al motor de expresiones regulares que coincida con cualquier carácter excepto {cero }o más veces. Si encuentra alguno {o }personaje, recurra al primer grupo de captura uno más. Así que ahora la cuerda foofue capturada. El siguiente carácter es {, por lo que recurre al primer grupo de captura. Ahora el motor de expresiones regulares está un nivel por debajo en recursividad. ¿Cuál es el primer patrón en nuestro primer grupo de captura (ver la expresión regular)? Es \{, ahora coincide con el {símbolo que estaba justo después de la cadena foo.
  • El motor todavía está en un nivel de recursividad, nuevamente el patrón (?:[^{}]|(?1))*coincide con la cadena bar. Ahora el carácter después de bares }, por lo que después de hacer coincidir la cadena bar, el motor de expresiones regulares no entrará, (?1)es por eso que hicimos que el grupo de no captura se repitaceroo más veces. Siguiente patrón (patrón después de(?:[^{}]|(?1))*) en la expresión regular es \}. Entonces esto \}coincidiría con la }llave que estaba justo después de bar. Ahora el motor de expresiones regulares sale de un nivel profundo en recursividad y el patrón [^{}]*coincidirá con la siguiente cadena foobar. El último \}coincidiría con el último corchete.
  • Ahora nuestro primer grupo de captura contiene {foo{bar}foobar}.

2da Parte

  • (*SKIP)(*F)Provoca que fallen los caracteres que coinciden o se capturan. Entonces, en nuestro caso, se omitieron todas las llaves equilibradas capturadas. Es decir, obliga al motor de expresiones regulares a hacer coincidir los caracteres de la cadena restante.
  • Sintaxis o formato de(*SKIP)(*F)

        part1(*SKIP)(*F)|part2
         |                  |
     |----                  -----> Match this
    Don't match this 
    
  • Entonces, el patrón que estaba justo después |intentará hacer coincidir los caracteres de la cadena restante (cadena excepto las llaves anidadas).

  • En nuestro caso, el patrón después de |es ,. Entonces todas las comas que están fuera de las llaves anidadas coincidieron.

Leerestepara entender el Regular Expression Recursion.

Nota:

  • (?R)recurre todo el subpatrón, es decir, toda la coincidencia. También podríamos escribir (?R)como(?0)
  • (?1)recurre al primer subpatrón (es decir, patrón dentro del primer grupo de captura)

Respuesta2

reemplazar

,{ 

con

|{ 

y

}, 

con

}|

 echo "THING1,{THING2,{THING3,}},THING4" | sed -re "s/,\{/|{/gi" | sed -re "s/},/}|/gi"

resultados en

THING1|{THING2|{THING3,}}|THING4

Respuesta3

No tenga miedo de que sea una sedafirmación difícil. Pero debe respetar la cascada. Aquí lo tienes en una sola línea:

sed -e 's/,/|/g;:a;s/{\([^{}]*\)|\([^{}]*\)}/{\1,\2}/g;ta;s/{\([^{}]*\)}/<\1>/g;ta;:b;s/<\([^<>]*\)>/{\1}/g;tb' file

Aquí está la versión comentada:

sed -e '
        s/,/|/g;                                 #replaces all commas (,) with pipes (|)
        :a;                                      #sets a label called a
            s/{\([^{}]*\)|\([^{}]*\)}/{\1,\2}/g; #replaces {a|b|c} with {a,b|c}
          ta;                                    #go back to the label `a` and repeat the
                                                 #prevous part until there is nothing more
                                                 #to replace: when {a|b|c} became {a,b,c}
          s/{\([^{}]*\)}/<\1>/g;                 #replace {...} with <...>
        ta;                                      #go back to label a again until all {} are 
                                                 #replaces by <>
        :b;                                      #create a new label called b
          s/<\([^<>]*\)>/{\1}/g;                 #replace <...> back to {...}
        tb;                                      #and back to label b to repeat the previous
                                                 #part
' file

Con eso obtuve el resultado deseado.

Respuesta4

Se me ocurrieron algunas formas de hacer esto en sed, pero la mayoría falló en los casos de esquina. Uno, sin embargo, no lo hace:

sed 's/^/\n/;:b
/\n\n/!s/\(\n[^,{}]*\),/\1|/;tb
s/\(\n\n*\)\([^{}]*[{}]\)/\2\1/
s/{\(\n\)/&\1/;s/\(}\n\)\n/\1/;tb
s/\n//g' <<\DATA
(999969,2500,"777777888",0,"45265","65522",NULL,10001,2014-09-15 10:27:07.287,2014-09-15 10:28:49.085,2014-09-15 06:28:50.000,0,0,NULL,"text","401c4133091977",{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL},NULL,{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]},{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1},NULL,NULL,NULL,NULL,NULL,{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL},poopoer,sciioooper)
DATA

Eso recorre una línea de datos usando un delimitador que puede estar seguro de no encontrar en una línea: un carácter de nueva línea. Recorre la línea de izquierda a derecha y se detiene en el siguiente de cualquiera de los dos puntos de interés: los personajes }{. Cuando se detiene en a, {agrega un carácter de nueva línea a su delimitador; cuando en a }resta uno si se tienen dos.

Cuando se detiene en un punto en el que solo se puede encontrar un carácter de nueva línea en la línea y una coma sigue a su delimitador antes de a, {}lo reemplazará con una tubería y volverá a intentar la misma prueba de reemplazo nuevamente.

Esto debería proteger incluso los grupos de llaves desequilibrados si es necesario, aunque no emplea ningún método para manejar una llave entre comillas, lo que podría hacerse agregandopuntos de interésSupongo, pero no estoy muy entusiasmado por descubrirlo.

El resultado de su muestra:

(999969|2500|"777777888"|0|"45265"|"65522"|NULL|10001|2014-09-15 10:27:07.287|2014-09-15 10:28:49.085|2014-09-15 06:28:50.000|0|0|NULL|"text"|"401c4133091977"|{F,F,"711592473,"00967711580001,F,NULL,NULL,"421010617759466","'401c4133091977H'",NULL,NULL,NULL,NULL,NULL,NULL,1,1,10,1,0,0,0,"a30200000000276f",NULL}|NULL|{gggg{-1, 0, -1, 1410762530000, 87, 0, 0}, rrrr[{"foot", 24000, 976000, 3999-12-31 23:59:59, 0}], rrrr[{1000003, 1410762443000, 120, 87, 0, 0, 2, 1, 24000, 0, 0}]}|{dd=0, ff=0, gg=0, hh=1, ctr="live", dddd="52265", eni=55, cuc=1}|NULL|NULL|NULL|NULL|NULL|{NULL,NULL,NULL,0,"wwww","eeee",2014-10-10 10:45:59.000,2015-03-09 23:59:59.000,2015-06-07 23:59:59.000,2015-08-06 23:59:59.000,NULL}|poopoer|sciioooper)

información relacionada