하나 이상의 중괄호 세트 외부에 있는 여러 쉼표를 바꾸고 하나 이상의 중괄호 세트 내의 예외를 대체합니다.

하나 이상의 중괄호 세트 외부에 있는 여러 쉼표를 바꾸고 하나 이상의 중괄호 세트 내의 예외를 대체합니다.

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

다음을 수행해야 합니다.

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

관련 정보