패턴 일치를 사용하여 텍스트 파일의 각 줄을 구문 분석합니다.

패턴 일치를 사용하여 텍스트 파일의 각 줄을 구문 분석합니다.

다시 포맷해야 하는 줄이 많은 텍스트 파일이 있는데 멈췄습니다. 각 행에는 HeaderSegment가 포함되며 그 뒤에는 하위 세그먼트가 포함될 수도 있는 하나 이상의 세부 세그먼트가 포함됩니다.

파일을 읽고 패턴을 일치시킨 다음 "뭔가를 수행"해야 합니다(아래 설명 참조).

다음은 내 텍스트 파일의 두 줄 예입니다. (HeaderSegment로 시작)

HeaderSegment:1234:989898:51:2101211748:29:DetailSegment:123467:654321:2:20210122112325:C:0:0:Purchased:SubSegment:064:null:Cash:Whaler:DetailSegment:123468:814211:1:20210121233042:N:0:147:Refund:SubSegment:000:null:Check:Everglades:DetailSegment:234569:825455:1:20210121233113:N:0:685:Purchased:SubSegment:000:null:Cash:Key West:DetailSegment:201754:663854:2:20210122012327:P:128:128:Purchased:SubSegment:000:null:null:null
HeaderSegment:1234:989898:22:2101211750:28:DetailSegment:55555:6637948:0:20210122013332:N:0:401:Refund:SubSegment:000:null:Credit:Whaler
HeaderSegment:1234:989898:22:2101211750:28:DetailSegment:55555:6637948:0:20210122013332:N:0:401:Sale:SubSegment:000:null:Credit:Whaler:SubSegment:30757:null:Cash:Whaler:SubSegment:25500:null:Credit:Seavee

HeaderSegment는 항상 Header로 시작하고 5개의 데이터 필드를 포함합니다.

DetailSegment는 항상 DetailSegment로 시작하고 8개의 데이터 필드를 포함하며 1개 이상의 SubSegment가 연결되어 있습니다.

SubSegments는 DetailSegments에 연결되며 4개의 데이터 필드를 포함합니다.

행당 헤더는 1개만 있지만 세부 섹션과 하위 섹션은 여러 개 있을 수 있습니다.

텍스트 파일의 각 줄을 구문 분석하고 각 세부 세그먼트에 대해 한 줄에 여러 줄이 포함된 새 출력을 만들어야 합니다. 이 줄에는 다음이 포함됩니다.

  • 헤더 섹션
  • 세부정보 섹션
  • DetailSegment와 다음 하위 세그먼트 사이에 있는 각 하위 세그먼트의 첫 번째 필드의 합계입니다(동일한 행에 2개의 하위 세그먼트가 있는 경우).
  • 필드 구분 기호를 다음으로 변경해야 합니다.

예시 출력은 다음과 같습니다:

1234,989898,51,2101211748,29,123467,654321,2,20210122112325,C,0,0,Purchased,064
1234,989898,51,2101211748,29,123468,814211,1,20210121233042,N,0,147,Refund,000
1234,989898,51,2101211748,29,234569,825455,1,20210121233113,N,0,685,Purchased,000
1234,989898,51,2101211748,29,201754,663854,2,20210122012327,P,128,128,Purchased,000
1234,989898,22,2101211750,28,55555,6637948,0,20210122013332,N,0,401,Refund,000
1234,989898,22,2101211750,28,55555,6637948,0,20210122013332,N,0,401,Sale,56257

awk를 사용해 보았지만 awk 지식이 부족하여 세그먼트를 분할하는 데 문제가 있습니다.

누군가가 지침을 제공할 수 있기를 바랍니다(권장 솔루션에 대한 설명은 이를 더 쉽게 "학습"하는 데 도움이 되므로 매우 환영합니다).

답변1

awk -F':?(Header|Detail)Segment:' '
    { sumPos=10;
      for(i=3; i<=NF; i++) { 
          split($i, tmp, ":")
          for(x in tmp) { 
              sum+=tmp[sumPos]; sumPos+=5
          };
          gsub(/:|:SubSegment.*/, ",", $i)
          gsub(/:/, ",", $2)
          printf("%s,%s%.3d\n", $2, $i, sum)
          sum=0
      };
}' infile

답변2

당신은 "학습"을 언급했으므로 여기에 당신의 문제를 해결한다고 생각하는 매우 간단한 awk 스크립트가 있습니다. 속도나 코드 길이 측면에서 최적화되지는 않았지만 읽을 수 있기를 바랍니다. 각 부분을 설명하여 학습할 수 있도록 노력하겠습니다.

코드는 다음과 같습니다.

BEGIN {
  FS=":"
  OFS=","
}
{
  pos=readheader()
  sanitycheck(pos)
  printresult()
}
func readheader() {
  r["a"]=$2
  r["b"]=$3
  r["c"]=$4
  r["d"]=$5
  r["e"]=$6
  pos=7
  dcount=0
  while($(pos) == "DetailSegment") {
    pos=readdetail(pos+1, dcount)
    dcount++
  }
  return pos
}
func readdetail(pos, dcount) {
  r["detail"][dcount]["a"]=$(pos+0)
  r["detail"][dcount]["b"]=$(pos+1)
  r["detail"][dcount]["c"]=$(pos+2)
  r["detail"][dcount]["d"]=$(pos+3)
  r["detail"][dcount]["e"]=$(pos+4)
  r["detail"][dcount]["f"]=$(pos+5)
  r["detail"][dcount]["g"]=$(pos+6)
  r["detail"][dcount]["h"]=$(pos+7)
  pos=pos+8
  scount=0
  while($(pos) == "SubSegment") {
    pos=readsub(pos+1, dcount, scount)
    scount++
  }
  return pos
}
func readsub(pos, dcount, scount) {
  r["detail"][dcount]["sub"][scount]["a"]=$(pos+0)
  r["detail"][dcount]["sub"][scount]["b"]=$(pos+1)
  r["detail"][dcount]["sub"][scount]["c"]=$(pos+2)
  r["detail"][dcount]["sub"][scount]["d"]=$(pos+3)
  return pos+4
}
func sanitycheck(pos) {
  if (pos <= NF) {
    print "error line "NR" only parsed "pos" of "NF" fields"
  }
}
func printresult() {
  for(d in r["detail"]) {
    subsum=0
    for(s in r["detail"][d]["sub"]) {
      subsum+=r["detail"][d]["sub"][s]["a"]
    }
    print r["a"],r["e"],r["detail"][d]["a"],r["detail"][d]["h"],subsum
  }
}

이름이 지정된 파일에 저장 하고 명령을 filter.awk입력하세요.input

$ awk -f filter.awk input

또는 소스에서 파이프 인

$ fromwherecomesinput | awk -f filter.awk

다음은 귀하가 제공한 세 개의 샘플 라인을 처리한 결과입니다.

1234,29,123467,Purchased,64
1234,29,123468,Refund,0
1234,29,234569,Purchased,0
1234,29,201754,Purchased,0
1234,28,55555,Refund,0
1234,28,123468,Refund,0
1234,28,234569,Purchased,0
1234,28,201754,Purchased,0
1234,28,55555,Sale,56257
1234,28,123468,Refund,0
1234,28,234569,Purchased,0
1234,28,201754,Purchased,0

모든 필드를 출력하지 않습니다. 다 입력하기에는 너무 게으릅니다.

귀하의 출력 요구 사항을 올바르게 이해하고 있는지 잘 모르겠습니다. 어쩌면 내가 잘못 들었을 수도 있습니다. 하지만 코드의 나머지 부분을 이해했다면 원하는 경우 출력 기능을 변경할 수 있도록 코드를 설명하려고 합니다.

행의 형식이 올바르게 지정되지 않은 경우 다음과 같이 출력됩니다.

error line 3 only parsed 7 of 20 fields

나는 이 디버그 출력 라인 외에 오류 처리 및 보고를 위한 다른 코드를 작성하지 않았습니다.

코드 설명:

먼저 코드가 수행하는 작업에 대한 개요입니다. awk는 입력을 한 줄씩 읽습니다. 각 줄은 콜론으로 구분됩니다. 그런 다음 필드를 반복하면서 세그먼트를 찾습니다. 그런 다음 트리 구조로 데이터를 수집합니다. 마지막으로 트리를 반복하고 원하는 출력을 계산합니다.

다행히 모든 세그먼트에는 고정된 수의 필드가 있습니다. 이렇게 하면 하위 구분을 매우 쉽게 찾을 수 있습니다.

트리 구조는 대략 다음과 같습니다. 루트에는 5개의 헤더 변수와 세부 정보 목록이 있습니다. 각 세부사항에는 8개의 세부변수와 하위 목록이 있습니다. 각 하위에는 4개의 하위 변수가 있습니다.

마지막으로 awk 매뉴얼의 일부 관련 페이지에 연결했습니다. 따라서 특정 주제에 대해 더 알고 싶다면 끝을 참조하세요.

자세한 설명부터 시작하겠습니다

BEGIN {
  FS=":"
  OFS=","
}

BEGIN블록은 입력의 첫 번째 줄을 읽기 전에 awk에 의해 실행됩니다. 주로 변수를 초기화하는데 사용됩니다.

END마지막 줄을 읽은 후 실행될 블록 도 있습니다 . 일반적으로 최종 결과를 인쇄하는 데 사용됩니다. 우리의 경우에는 각 행에 대한 결과가 있지만 누적된 최종 결과가 없으므로 블록이 없습니다 END.

FS필드 구분 기호입니다. 이는 awk에게 각 입력 행을 소위 필드로 분할하는 방법을 알려줍니다. 이것이 awk의 핵심 강점 중 하나입니다. 적절한 필드 구분 기호 값이 솔루션의 절반인 경우가 많습니다. 이 예에서는 필드 구분 기호를 콜론( :)으로 설정했습니다.

OFS출력 필드 구분 기호입니다. 이는 print 문의 필드 사이에 있는 문자입니다. 이 경우에는 쉼표( )로 설정했습니다 ,.

이러한 변수는 awk 작동 방식을 변경하므로 제어 변수라고 합니다.

다음은 "라인당" 코드 블록입니다.

{
  pos=readheader()
  sanitycheck(pos)
  printresult()
}

이 코드 블록은 각 행(awk 용어의 각 레코드)에 대해 실행됩니다. 이 블록은 짧고 간단하도록 코드를 함수로 추출했습니다.

(레코드 구분 기호를 개행이 아닌 다른 것으로 변경할 수도 있으며, 그러면 레코드에 입력 라인이 더 많거나 적어질 수 있습니다.)

awk에서는 모든 변수가 전역 변수이므로(코드 블록에서도) 이러한 함수는 주로 인간 구성을 위한 것입니다. 그렇기 때문에 printresult()데이터를 전달하지 않고도 결과를 인쇄할 수 있습니다. 단지 전역 변수의 결과를 인쇄합니다. 글로벌이기 때문에 꼭 pos돌아올 readheader()필요는 없지만, 마음에 들어서 남겨두었습니다.

또한 awk에는 문자열과 숫자(그리고 배열)라는 두 가지 유형의 변수만 있다는 점에 유의하세요. 변환은 암시적입니다. 초기화되지 않은 변수는 항상 0이거나 빈 문자열입니다. 이는 지나치게 단순화된 것입니다. 마지막에 링크된 매뉴얼을 읽어보세요.

코드 블록에는 일반적으로 접두사가 붙습니다. 예를 들어

/foo/ { ... }

또는

NR > 1 { ... }

이것들은 모두 조건입니다. 즉, 현재 레코드가 조건을 만족하는 경우에만 블록이 실행됩니다.

우리 코드 블록에는 그러한 조건이 없으므로 모든 줄에서 실행됩니다.

awk 용어에서는 조건을 패턴이라고 하고 코드 블록을 작업이라고 합니다.

함수에 대한 설명은 다음과 같습니다.

func readheader() {
  r["a"]=$2
  r["b"]=$3
  r["c"]=$4
  r["d"]=$5
  r["e"]=$6
  pos=7
  dcount=0
  while($(pos) == "DetailSegment") {
    pos=readdetail(pos+1, dcount)
    dcount++
  }
  return pos
}

여기의 코드는 일반적인 프로그래밍 언어처럼 보이기 시작합니다. 이 함수는 "for Every line" 블록에서 호출된다는 것을 기억하세요. 따라서 이 함수는 한 줄에 한 번씩 호출됩니다.

$2다른 달러 수치는 필드에 대한 참조입니다. 이 필드는 awk가 분할하는 줄의 일부입니다. 우리의 경우 필드는 콜론 사이의 값입니다.

( $awk에서는 필드 변수에만 사용됩니다. 정규식에서는 완전히 다른 의미를 갖지만 이는 완전히 다른 이야기입니다. 이 코드에는 정규식이 없으므로 여기에도 없습니다)

$0항상 전체 라인.

$1우리의 경우 항상 HeaderSegment그렇으므로 그냥 건너뜁니다(오류 검사 없음). $2예, $6다섯 가지 가치 HeaderSegment.

우리는 이러한 변수를 이라는 배열에 저장합니다 r. 짧은 이름은 모든 것이 전역적이기 때문에 위험하지만 우리에게는 이 변수가 꼭 필요하고 게을러서 짧은 이름을 사용했습니다.

awk의 배열은 C나 Java와 같은 배열이 아니라 맵이나 사전입니다. 키-값 매핑. 키는 문자열이나 숫자일 수 있습니다. 반복하면 순서는 기본적으로 무작위입니다. 이 값은 다른 배열을 포함한 모든 값이 될 수 있습니다. 이들 배열의 배열은 우리가 트리를 만드는 데 사용하는 것입니다.

값에 대한 더 나은 이름이 없기 때문에 이 키를 사용하고 "a"있습니다 . "e"이러한 값의 의미를 알고 있으며 "customerID"또는 같은 더 의미 있는 이름을 지정할 수 있습니다 "froobazzaloopaCount".

$(pos)계산되거나 간접 필드 변수입니다. 괄호 안의 부분이 먼저 평가됩니다. 그런 다음 해당 필드를 참조하십시오. 그렇다면 pos그렇다면 7그것은 $(pos)이고 $7입니다 .$(pos+1)$8다음 항목을 찾기 위해 여기에서 반복하는 것처럼 필드를 반복하는 경우 이는 특히 펑키합니다 DetailSegment.

awk 용어로 이를 상수가 아닌 필드 번호라고 합니다.

다른 기능( readdetailreadsub)도 비슷하게 작동합니다.

기능 sanitycheck:

func sanitycheck(pos) {
  if (pos <= NF) {
    print "error line "NR" only parsed "pos" of "NF" fields"
  }
}

NF이 레코드의 필드 수입니다. pos다음에 살펴볼 필드를 추적하는 데 사용하는 변수입니다. 그러니까 pos그보다 적으면 NF문제가 있는 거죠. 그런 다음 버그를 보고합니다.

NR현재 레코드의 번호입니다. 우리의 경우 레코드는 기본적으로 행이므로 이것이 행 번호입니다.

이러한 변수는 awk의 현재 상태에 대한 정보를 제공하므로 정보 변수라고 합니다.

인쇄 결과 기능:

func printresult() {
  for(d in r["detail"]) {
    subsum=0
    for(s in r["detail"][d]["sub"]) {
      subsum+=r["detail"][d]["sub"][s]["a"]
    }
    print r["a"],r["e"],r["detail"][d]["a"],r["detail"][d]["h"],subsum
  }
}

큰 놀라움은 없습니다. 이것은 요즘 대부분의 현대 언어와 같습니다. for(d in r["detail"])배열의 키를 반복합니다 r["detail"]. 첫 번째 루프 반복 세부정보입니다. 두 번째 루프는 하위 항목을 자세히 반복합니다.

각 세부사항에 대해 subs의 첫 번째 값의 수와 합계를 인쇄합니다.

그 진술에 대한 약간의 참고 사항 print:

여기에 있는 내용은 print 1,2,3(쉼표로 구분됨)이고 출력은 1,2,3(쉼표로 구분됨)입니다. OFS(출력 필드 구분 기호)를 쉼표로 설정 했기 때문입니다 . OFS예를 들어, 그렇다면 #다음이 print 1,2,3출력됩니다 1#2#3.

print "1,2,3"(quote) 는 항상 1,2,3무엇이든 될 것입니다. OFS왜냐하면 이번에는 쉼표가 문자 그대로의 쉼표이기 때문입니다.

이것이 awk를 사용하여 문제를 해결하는 방법을 이해하는 데 도움이 되기를 바랍니다. 여러분이 추가 요구 사항에 맞게 코드를 조정할 수 있도록 내용을 충분히 잘 설명할 수 있었으면 좋겠습니다.


Fine awk 매뉴얼의 관련 주제에 대한 링크

BEGIN및 차단에 대한 추가 정보 END:https://www.gnu.org/software/gawk/manual/html_node/Using-BEGIN_002fEND.html

필드 구분 기호( )에 대한 FS추가 정보 :https://www.gnu.org/software/gawk/manual/html_node/Field-Separators.html

FS"제어" 변수( 등 OFS) 에 대한 추가 정보 :https://www.gnu.org/software/gawk/manual/html_node/User_002dmodified.html

"info" 변수( NR및 )에 대한 NF추가 정보 :https://www.gnu.org/software/gawk/manual/html_node/Auto_002dset.html

기록에 대한 추가 정보(가장 일반적인 줄):https://www.gnu.org/software/gawk/manual/html_node/Records.html

변수 가시성에 대한 추가 정보(모든 것이 전역적임):https://www.gnu.org/software/gawk/manual/html_node/Global-Namespace.html하지만 일부는 현지일 수도 있습니다.https://www.gnu.org/software/gawk/manual/html_node/Variable-Scope.html

변수 유형(문자열 및 숫자)에 대한 추가 정보:https://www.gnu.org/software/gawk/manual/html_node/Variable-Typing.html

패턴 및 작업(조건 및 코드 블록)에 대한 추가 정보:https://www.gnu.org/software/gawk/manual/html_node/Patterns-and-Actions.html

배열(키-값 맵 또는 사전)에 대한 추가 정보:https://www.gnu.org/software/gawk/manual/html_node/Arrays.html

필드 번호(달러 숫자 또는 필드 변수)에 대한 추가 정보:https://www.gnu.org/software/gawk/manual/html_node/Fields.html

상수가 아닌 필드 번호(계산 또는 간접 필드 변수)에 대한 추가 정보:https://www.gnu.org/software/gawk/manual/html_node/Nonconstant-Fields.html

관련 정보