Você pode fazer isso simplesmente através da combinação Perl
+ regex
.
perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file
Exemplo:
$ 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})
Explicação:
Eu divido o regex em duas partes para a explicação.
-
(\{(?:[^{}]|(?1))*\})
-
(*SKIP)(*F)|,
1ª parte
(\{(?:[^{}]|(?1))*\})
- Esse truque funcionará somente se as chaves forem adequadamente pareadas.
-
()
Estes são grupos de captura, usados para capturar caracteres. -
\{
corresponde a uma chave de abertura. -
(?:[^{}]|(?1))
-
(?:...)
Chamado grupo sem captura. -
[^{}]
Corresponderá a qualquer caractere, mas não a{
ou}
-
|
operador OR lógico. -
(?1)
Recorre ao primeiro grupo de captura.
-
-
(?:[^{}]|(?1))*
Corresponde ao token anterior zero ou mais vezes. -
\}
Fechando o símbolo}
.
Considere o exemplo abaixo e o padrão que corresponde aos colchetes aninhados.
String:
h{foo{bar}foobar}
Padrão:
h(\{(?:[^{}]|(?1))*\})
- No início, o mecanismo de regex tenta corresponder o
h
( que estava no padrão ) à cadeia de entrada. Então a primeira letrah
foi correspondida. - O padrão para encontrar os parênteses equilibrados é inserido em um grupo de captura.
- Agora, o mecanismo pega o segundo caractere (isto é,
\{
) no padrão e tenta corresponder à string de entrada. Então o primeiro{
foi capturado . Eu usei a palavra capturada em vez da correspondência porque\{
está dentro de um grupo de captura. -
(?:[^{}]|(?1))*
Indica ao mecanismo de expressão regular que corresponda a qualquer caractere, exceto{
ou}
zero ou mais vezes. Se você encontrou qualquer caractere{
ou}
, recrie novamente o primeiro grupo de captura. Então agora a stringfoo
foi capturada. O caractere a seguir é{
, então ele recursiva para o primeiro grupo de captura. Agora, o mecanismo regex está um nível abaixo na recursão. Qual é o primeiro padrão no nosso primeiro grupo de captura ( veja o regex )? É\{
, agora corresponde ao símbolo{
que foi logo após a stringfoo
. - O mecanismo ainda tem um nível de recursão, novamente o padrão
(?:[^{}]|(?1))*
corresponde à sequênciabar
. Agora, o caractere após obar
é}
, portanto, depois de corresponder à stringbar
, o mecanismo regex não entrará em(?1)
. Por isso, fizemos o grupo que não captura para repetir zero ou mais vezes. O próximo padrão ( padrão após a(?:[^{}]|(?1))*
) na regex é\}
. Portanto, esse\}
corresponderia à}
brace que estava logo após abar
. Agora, o mecanismo regex sai de um nível de recursão e o padrão[^{}]*
corresponderia à seguinte stringfoobar
. O último\}
corresponderia ao último colchete. - Agora, nosso primeiro grupo de captura contém
{foo{bar}foobar}
.
2ª parte
-
(*SKIP)(*F)
Faz com que os caracteres que são correspondidos ou capturados falhem. Então, no nosso caso, todas as chaves balanceadas capturadas foram ignoradas. Ou seja, ele força o mecanismo de expressão regular a corresponder aos caracteres da sequência restante. -
Sintaxe ou formato de
(*SKIP)(*F)
part1(*SKIP)(*F)|part2 | | |---- -----> Match this Don't match this
-
Portanto, o padrão que estava logo após o
|
tentará corresponder os caracteres da string restante ( string, exceto as chaves aninhadas ). -
No nosso caso, o padrão após o
|
é,
. Então, todas as vírgulas que estão fora das chaves aninhadas foram combinadas.
Leia este para entender o Regular Expression Recursion
.
Nota:
-
(?R)
recursa todo o sub-padrão, ou seja, toda a correspondência. Também poderíamos escrever(?R)
as(?0)
-
(?1)
recurses no primeiro subpadrão (ou seja, padrão dentro do primeiro grupo de captura)