substitua várias vírgulas fora de um ou mais conjuntos de chaves e exceto que em um ou mais conjuntos de chaves

substitua várias vírgulas fora de um ou mais conjuntos de chaves e exceto que em um ou mais conjuntos de chaves

No arquivo de texto tenho vários registros. Cada registro possui múltiplas colunas separadas por vírgulas, algumas colunas possuem um conjunto de chaves e outras possuem mais de uma chave.

Eu preciso de:

  1. Se uma vírgula for encontrada fora de um ou mais conjuntos de chaves, a vírgula deverá ser substituída por uma barra vertical.

  2. Se uma vírgula for encontrada dentro de um ou mais conjuntos de chaves, a vírgula deverá ser deixada sozinha. Portanto, dada THING1,{THING2,{THING3,}},THING4a saída, deveria ser THING1|{THING2,{THING3,}}|THING4.

Registro de amostra:

(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})

o resultado deve 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})

Responder1

Você poderia 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:

Dividi o regex em duas partes para explicação.

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

1ª parte

(\{(?:[^{}]|(?1))*\})
  • Este truque só funcionará se as chaves estiverem emparelhadas corretamente.
  • ()São grupos de captura, usados ​​para capturar personagens.
  • \{corresponde a uma chave de abertura.
  • (?:[^{}]|(?1))

    • (?:...)Chamado de grupo sem captura.
    • [^{}]Corresponderia a qualquer caractere, mas não de {ou}
    • |Operador lógico OR.
    • (?1)Recorre ao primeiro grupo de captura.
  • (?:[^{}]|(?1))*Combine o token anterior zero ou mais vezes.
  • \}Símbolo de fechamento }.

Considere o exemplo abaixo e o padrão que corresponde aos colchetes aninhados nele.

Corda:

h{foo{bar}foobar}

Padrão:

h(\{(?:[^{}]|(?1))*\})
  • Inicialmente, o mecanismo regex tenta corresponder ao h(que estava no padrão) em relação à string de entrada. Então a primeira letra hfoi correspondida.
  • O padrão para encontrar os parênteses balanceados é inserido em um grupo de captura.
  • Agora o mecanismo pega o segundo caractere (ou seja, \{) no padrão e tenta fazer a correspondência com a string de entrada. Então o primeiro {conseguiucapturado. Usei a palavra capturado em vez de combinar porque \{está dentro de um grupo de captura.
  • (?:[^{}]|(?1))*Isso diz ao mecanismo regex para corresponder a qualquer caractere, exceto {zero }ou mais vezes. Se você encontrou algum personagem {ou }então recorra ao primeiro grupo de captura mais um. Então agora a string foofoi capturada. O caractere a seguir é {, então ele recorre ao primeiro grupo de captura. Agora, o mecanismo regex está um nível abaixo na recursão. Qual é o primeiro padrão em nosso primeiro grupo de captura (veja o regex)? É \{, agora corresponde ao {símbolo que estava logo após a string foo.
  • O mecanismo ainda está com um nível de profundidade na recursão, novamente o padrão (?:[^{}]|(?1))*corresponde à string bar. Agora o caractere depois de baris }, então depois de combinar a string bar, o mecanismo regex não entrará, (?1)é por isso que fizemos o grupo de não captura repetirzeroou mais vezes. Próximo padrão (padrão depois de(?:[^{}]|(?1))*) na regex é \}. Portanto, isso \}corresponderia à }chave logo após to bar. Agora, o mecanismo regex sai de um nível profundo de recursão e o padrão [^{}]*corresponde à seguinte string foobar. 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. Portanto, em nosso caso, todas as chaves balanceadas capturadas foram ignoradas. Ou seja, força o mecanismo regex a corresponder aos caracteres da string restante.
  • Sintaxe ou formato de(*SKIP)(*F)

        part1(*SKIP)(*F)|part2
         |                  |
     |----                  -----> Match this
    Don't match this 
    
  • Portanto, o padrão logo após tentará |corresponder aos caracteres da string restante (string, exceto as chaves aninhadas).

  • No nosso caso, o padrão após |is ,. Portanto, todas as vírgulas que estão fora dos colchetes aninhados foram correspondidas.

Leressepara entender o Regular Expression Recursion.

Observação:

  • (?R)recorre a todo o subpadrão, ou seja, a correspondência inteira. Poderíamos também escrever (?R)como(?0)
  • (?1)recorre ao primeiro subpadrão (ou seja, padrão dentro do primeiro grupo de captura)

Responder2

substituir

,{ 

com

|{ 

e

}, 

com

}|

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

resulta em

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

Responder3

Não tenha medo, é uma sedafirmação difícil. Mas, deve respeitar a cascata. Aqui está em uma linha:

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

Aqui está a versão 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

Com isso obtive o resultado desejado.

Responder4

Eu descobri algumas maneiras de fazer isso sed, mas a maioria quebrou em casos extremos. Um, entretanto, não:

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

Isso percorre uma linha de dados usando um delimitador que você pode ter certeza de não encontrar em uma linha - um caractere de nova linha. Ele percorre a linha da esquerda para a direita, parando no próximo dos dois pontos de interesse - os personagens }{. Quando para em a, {adiciona um caractere de nova linha ao seu delimitador; quando em }a subtrai um se houver dois.

Quando ele é interrompido em um ponto em que há apenas um caractere de nova linha a ser encontrado na linha e uma vírgula segue seu delimitador antes de a, {}ele o substituirá por uma barra vertical e retornará para tentar o mesmo teste de substituição novamente.

Isso deve proteger até mesmo grupos de chaves desequilibrados, se necessário, embora não empregue nenhum método de lidar com uma chave entre aspas, o que pode ser feito adicionandopontos de interesseEu acho, mas não estou muito animado em descobrir.

A saída da sua amostra:

(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)

informação relacionada