하나 이상의 중괄호 세트에서 여러 개의 쉼표를 바꾸고 하나 이상의 중괄호 세트에서 예외

하나 이상의 중괄호 세트에서 여러 개의 쉼표를 바꾸고 하나 이상의 중괄호 세트에서 예외

텍스트 파일에는 여러 레코드가 있습니다. 모든 레코드에는 쉼표로 구분된 여러 열이 있으며, 일부 열에는 중괄호 세트가 하나 있고 다른 열에는 중괄호가 두 개 이상 있습니다.

나는 필요하다:

  1. 하나 이상의 중괄호 세트 외부에 쉼표가 있는 경우 쉼표를 파이프로 바꿔야 합니다.

  2. 하나 이상의 중괄호 안에 쉼표가 있는 경우 쉼표는 그대로 두어야 합니다. 따라서 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. (\{(?:[^{}]|(?1))*\})
  2. (*SKIP)(*F)|,

첫 번째 부분

(\{(?:[^{}]|(?1))*\})
  • 이 트릭은 중괄호가 올바르게 쌍을 이루는 경우에만 작동합니다.
  • ()캐릭터를 캡처하는 데 사용되는 캡처 그룹입니다.
  • \{여는 중괄호와 일치합니다.
  • (?:[^{}]|(?1))

    • (?:...)비캡처 그룹이라고 합니다.
    • [^{}]{모든 문자와 일치하지만 또는 와 일치하지 않습니다 .}
    • |논리 OR 연산자.
    • (?1)첫 번째 캡처 그룹을 반복합니다.
  • (?:[^{}]|(?1))*이전 토큰을 0회 이상 일치시킵니다.
  • \}종료 }기호.

아래 예제와 그 안에 중첩된 대괄호와 일치하는 패턴을 고려하세요.

끈:

h{foo{bar}foobar}

무늬:

h(\{(?:[^{}]|(?1))*\})
  • 처음에 정규식 엔진은 h(패턴에 있던거) 입력 문자열에 대해. 그래서 첫 글자 h가 일치했습니다.
  • 균형 잡힌 괄호를 찾기 위한 패턴은 캡처 그룹에 제공됩니다.
  • 이제 엔진은 \{패턴의 두 번째 문자(예: )를 가져와 입력 문자열과 일치시키려고 합니다. 그래서 먼저 {얻은캡처. \{은(는) 캡처 그룹 내에 있기 때문에 일치라는 단어 대신 캡처라는 단어를 사용했습니다 .
  • (?:[^{}]|(?1))*이는 정규식 엔진이 0회 이상의 문자를 {제외한 모든 문자와 일치하도록 지시합니다. 또는 문자 }를 찾은 경우 첫 번째 캡처 그룹을 다시 한 번 반복합니다. 이제 문자열이 캡처되었습니다. 다음 문자는 이므로 첫 번째 캡처 그룹으로 재귀됩니다. 이제 정규식 엔진은 재귀에서 한 수준 아래입니다. 첫 번째 캡처 그룹의 첫 번째 패턴은 무엇입니까({}foo{정규식을 참조하세요)? 입니다 . 이제 문자열 바로 뒤에 있는 기호와 \{일치합니다 .{foo
  • 엔진은 여전히 ​​재귀의 한 수준 깊이에 있으며 패턴은 (?:[^{}]|(?1))*문자열과 일치합니다 bar. 이제 baris 다음의 문자 }이므로 string 을 일치시킨 후에 bar정규식 엔진이 들어가지 않으므로 (?1)비캡처 그룹을 반복하도록 만들었습니다.또는 그 이상. 다음 패턴(이후의 패턴(?:[^{}]|(?1))*) 정규식에는 가 있습니다 \}. 따라서 이것은 to 바로 뒤에 있는 중괄호 \}와 일치합니다 . 이제 정규식 엔진은 재귀의 한 수준에서 벗어나고 패턴은 다음 문자열과 일치합니다 . 마지막은 마지막 중괄호와 일치합니다.}bar[^{}]*foobar\}
  • 이제 첫 번째 캡처 그룹에는 {foo{bar}foobar}.

2부

  • (*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 앞에 구분 기호 뒤에 쉼표가 오는 지점에서 중지되면 {}파이프로 교체하고 다시 반복하여 동일한 교체 테스트를 다시 시도합니다.

이는 필요한 경우 불균형 버팀대 그룹도 보호해야 하지만 인용된 버팀대를 처리하는 방법은 사용하지 않습니다.가볼만한 곳그럴 것 같지만, 알아낸 것에 대해 그다지 흥분되지는 않습니다.

샘플의 출력:

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

관련 정보