텍스트 파일에 여러 레코드가 있습니다. 각 레코드에는 쉼표로 구분된 여러 열이 있으며, 일부 열에는 중괄호 세트가 하나 있고 다른 열에는 중괄호가 여러 개 있습니다.
다음을 수행해야 합니다.
하나 이상의 중괄호 세트 외부에 쉼표가 있는 경우 쉼표를 수직 막대로 바꿔야 합니다.
하나 이상의 중괄호 세트 안에 쉼표가 있으면 해당 쉼표를 유지해야 합니다.
THING1,{THING2,{THING3,}},THING4
따라서 주어진 출력은THING1|{THING2,{THING3,}}|THING4
.
레코드 샘플:
(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})
결과는 다음과 같습니다.
(**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})
답변1
Perl
+ 조합 으로 간단 regex
하게 할 수 있습니다.
perl -pe 's/(\{(?:[^{}]|(?1))*\})(*SKIP)(*F)|,/|/g' file
예:
$ 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})
설명하다:
정규식을 두 부분으로 나누어 설명하겠습니다.
(\{(?:[^{}]|(?1))*\})
(*SKIP)(*F)|,
1 부
(\{(?:[^{}]|(?1))*\})
- 이 트릭은 중괄호가 올바르게 쌍을 이루는 경우에만 작동합니다.
()
캐릭터를 캡처하는 데 사용되는 캡처 그룹입니다.\{
여는 중괄호와 일치합니다.(?:[^{}]|(?1))
(?:...)
비캡처 그룹이라고 합니다.[^{}]
모든 문자와 일치하지만 일치하지{
않거나}
|
논리 OR 연산자.(?1)
첫 번째 캡처 그룹으로 재귀합니다.
(?:[^{}]|(?1))*
이전 토큰과 0회 이상 일치합니다.\}
끝}
기호.
다음 예제와 그 안에 중첩된 괄호와 일치하는 패턴을 고려하세요.
끈:
h{foo{bar}foobar}
무늬:
h(\{(?:[^{}]|(?1))*\})
- 먼저 정규식 엔진은 다음과 일치하려고 시도합니다
h
(이 패턴에 있어요) 입력 문자열의 경우. 따라서 첫 글자가h
일치합니다. - 균형 괄호를 찾는 패턴은 캡처 그룹에 입력됩니다.
- 이제 엔진은
\{
패턴의 두 번째 문자(예: )를 가져와 입력 문자열과 비교하려고 시도합니다. 그래서 첫 번째 사람이{
얻었습니다.캡처됩니다.\{
캡처 그룹 내에 있기 때문에 "일치" 대신 "캡처"라는 단어를 사용합니다 . (?:[^{}]|(?1))*
이는 정규식 엔진이 0회 이상의 문자를{
제외한 모든 문자 와 일치하도록 지시합니다. 또는 문자}
를 찾으면 첫 번째 캡처링 그룹으로 다시 반복됩니다. 이제 문자열이 캡처되었습니다. 다음 문자는 이므로 첫 번째 캡처 그룹으로 재귀됩니다. 이제 정규식 엔진의 재귀 수준이 한 단계 낮아졌습니다. 첫 번째 캡처 그룹의 첫 번째 패턴은 무엇입니까({
}
foo
{
정규식 보기)? 예\{
, 이제{
문자열 뒤의 기호와 일치합니다foo
.- 엔진의 재귀 깊이는 여전히 한 수준이며 패턴은
(?:[^{}]|(?1))*
문자열과 다시 일치됩니다bar
. 이제bar
뒤의 문자는}
문자열을 일치시킨 후에bar
정규식 엔진이 개입하지 않을 것이므로(?1)
비캡처 그룹을 반복하게 만드는 이유입니다.영또는 그 이상. 다음 모드(이후의 모델(?:[^{}]|(?1))*
)는 정규 표현식에서 입니다\}
. 따라서 이는 바로 뒤에 오는 중괄호\}
와 일치합니다 . 이제 정규식 엔진은 한 수준의 재귀에서 벗어났으며 패턴은 다음 문자열과 일치합니다 . 마지막 것은 마지막 버팀대와 일치합니다.}
bar
[^{}]*
foobar
\}
- 이제 첫 번째 캡처 그룹에는
{foo{bar}foobar}
.
두 번째 부분
(*SKIP)(*F)
일치 또는 캡처 실패를 유발하는 문자입니다. 따라서 이 예에서는 캡처된 모든 균형 괄호를 건너뜁니다. 즉, 정규식 엔진이 문자열의 나머지 문자와 일치하도록 강제합니다.구문 또는 형식
(*SKIP)(*F)
part1(*SKIP)(*F)|part2 | | |---- -----> Match this Don't match this
따라서 그 뒤에 오는 패턴은
|
나머지 문자열(중첩된 중괄호를 제외한 문자열).우리의 경우 다음 패턴
|
은 입니다,
. 따라서 중첩된 중괄호 밖의 모든 쉼표가 일치합니다.
읽다이것이해하다 Regular Expression Recursion
.
노트:
(?R)
전체 하위 패턴, 즉 전체 일치를 반복합니다. 우리도(?R)
쓸 수 있어요(?0)
(?1)
첫 번째 하위 패턴을 반복합니다(즉, 첫 번째 캡처링 그룹 내의 패턴).
답변2
바꾸다
,{
그리고
|{
그리고
},
그리고
}|
echo "THING1,{THING2,{THING3,}},THING4" | sed -re "s/,\{/|{/gi" | sed -re "s/},/}|/gi"
밝혀지다
THING1|{THING2|{THING3,}}|THING4
답변3
이것이 어려운 sed
진술임을 두려워하지 마십시오. 그러나 캐스케이드를 존중해야 합니다. 다음 줄은 다음과 같습니다.
sed -e 's/,/|/g;:a;s/{\([^{}]*\)|\([^{}]*\)}/{\1,\2}/g;ta;s/{\([^{}]*\)}/<\1>/g;ta;:b;s/<\([^<>]*\)>/{\1}/g;tb' file
주석이 달린 버전은 다음과 같습니다.
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
이렇게 하면 원하는 결과를 얻을 수 있습니다.
답변4
나는 이를 위한 여러 가지 방법을 생각해 냈지만 sed
대부분은 특수한 경우에 실패합니다. 그러나 그렇지 않은 것이 있습니다:
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
데이터 라인에 걸쳐 구분 기호를 사용하면 구분 기호(개행 문자)가 라인에서 발견되지 않는다는 것을 확인할 수 있습니다. 왼쪽에서 오른쪽으로 선을 따라가며 두 가지 관심 지점 중 다음 지점인 문자에서 멈춥니다 }{
. a에서 멈추면 {
구분 기호에 개행 문자를 추가하고, a에서 멈추면 }
2개가 있으면 1을 뺍니다.
줄에서 개행 문자만 찾을 수 있고 a 앞의 구분 기호 뒤에 쉼표가 있는 지점에서 멈추면 {}
이를 파이프로 바꾸고 재귀적으로 돌아가서 동일한 대체 테스트를 다시 시도합니다.
이는 필요한 경우 불균형 중괄호 그룹도 보호해야 하지만 인용된 중괄호를 처리하기 위해 아무 작업도 수행하지 않습니다.가볼만한 곳그럴 것 같지만, 그 사실을 알게 되어서 그다지 기쁘지는 않습니다.
예제 출력:
(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)