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 letrahfoi 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 stringfoofoi 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)