
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:
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.
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,}},THING4
a saída, deveria serTHING1|{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))*\})
(*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 letrah
foi 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 stringfoo
foi 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 stringfoo
.- O mecanismo ainda está com um nível de profundidade na recursão, novamente o padrão
(?:[^{}]|(?1))*
corresponde à stringbar
. Agora o caractere depois debar
is}
, então depois de combinar a stringbar
, 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 tobar
. Agora, o mecanismo regex sai de um nível profundo de recursão e o padrão[^{}]*
corresponde à 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. 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 sed
afirmaçã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)