csv 파일을 구문 분석하여 열 값의 일치하는 문자 집합을 기반으로 행을 필터링합니다.

csv 파일을 구문 분석하여 열 값의 일치하는 문자 집합을 기반으로 행을 필터링합니다.

다음 csv 파일을 고려하십시오.

A,3300   
B,8440   
B,8443   
B,8444 
C,304
C,404  
M,5502   
M,5511

실제 csv 파일은 상당히 큽니다(약 60,000행). 문제를 설명하기 위해 작은 버전만 제공했습니다.

두 번째 필드를 기반으로 행을 필터링하여 일치하는 문자 집합이 있는 행을 단일 행으로 그룹화하는 스크립트를 만들어야 합니다(두 번째 필드를 일치하는 문자 집합으로 대체).

즉, 위에 제공된 csv 파일에서 다음과 같은 출력이 예상됩니다.

A,3300   
B,844  
C,304
C,404 
M,55   

두 번째 csv 필드의 콘텐츠만 스크립트 목적과 관련이 있습니다. 따라서 다른 필드에서 발생하는 모든 일치/불일치는 파일에 있는 그대로 유지되어야 합니다.

이 작업에 awk가 유용합니까? 아니면 다른 내장 기능이 있나요? 어떤 도움이라도 대단히 감사하겠습니다.

답변1

awk두 문자열 사이의 공통 시작 문자를 찾기 위해 작은 함수를 작성했습니다 .

awk '
BEGIN{OFS=FS=","}
function common_chars(a,b, o){
    split(a,asplit,"")
    split(b,bsplit,"")
    n=1
    while (asplit[n]==bsplit[n]){
        o=o""asplit[n]
        n++
    }
    return o
}
s[$1] {v[$1]=common_chars(v[$1],$2)}
!s[$1] {v[$1]=$2;s[$1]=1 }
END {for(a in v){print a,v[a]}}
' file

표시 되지 않으면 $1(상태는 에 저장됨 ) 배열에 s[$1]저장됩니다 . 표시된 경우 함수의 반환 값을 자체와 사이에 설정하세요 . 이 함수는 첫 번째 문자에서 불일치를 찾을 때까지 개별 문자에 대해 while 루프를 실행합니다.$2v[$1]=$2v[$1]$2

for C,404그리고 C,304그것은 인쇄됩니다C,

산출:

A,3300   
B,844
C,
M,55

답변2

60,000개 행의 경우 약간 느릴 수 있지만 실행 가능한 것으로 보입니다. 하다아니요여기에 따옴표를 넣으세요 $line!

스크립트 어딘가에 처리할 데이터가 더 많이 표시되는 버그가 있다는 이상한 느낌이 아직도 듭니다...

$ sort -u testfile | datamash -t, -g1 collapse 2  \
| tr ',' ' ' | while read line ; do ./my_filter $line ; done
A,3300
B,844
C,304
C,404
M,55

데이터를 전처리 datamash하고 정렬된 데이터를 얻으려면 my_filter한 줄씩 입력하면 됩니다.

$ sort -u testfile | datamash -t, -g1 collapse 2 
A,3300
B,8440,8443,8444
C,304,404
M,5502,5511

그것은 my_filter:

$ cat my_filter
#!/bin/bash
_longest_match () {
  if ((${#1}>${#2})); then
    long="$1" short="$2"
  else
    long="$2" short="$1"
  fi

  lshort=${#short}
  score=0
  for ((l=score+1;l<=lshort;++l)); do
    sub="${short:0:l}"

    [[ $long != $sub* ]] && break
    subfound="$sub" score="$l"
  done

  if ((score)); then
    printf '%s\n' "$subfound"
  fi
} # ----------  end of function _longest_match  ----------


_output () {
  for item in $(echo "$@"|tr ' ' '\n' | sort -u) ; do
    printf '%s,%s\n' "$key" "$item"
  done
} # ----------  end of function _output  ----------

declare -A matches
declare -A no_matches

key=$1
shift

for item in $( printf '%s\n' "$@"| sort -nr ); do
  if [ -z "$one" ]; then
    one=$1
    two=${2:-$1}
    shift 2
  else
    two=$1
    shift
  fi

  three=$(_longest_match $one $two)

  [ ${#three} -gt 0 ] && matches[$key]+="$three " || no_matches[$key]+="$one $two "
  [ ${#three} -gt 0 ] && one="$three" || one="$two"
done

  _output "${matches[@]} ${no_matches[@]}" | sort -u

_longest_match영감을 찾았어요https://stackoverflow.com/a/23297950

테스트 파일의 이중 항목을 사용하여 몇 가지 추가 테스트를 수행했습니다.

$ cat testfile.new 
A,3300
B,8440
B,8440
U,3
U,7
U,7
U,73
B,8440
B,8443
B,8444
B,976
C,304
C,404
M,5502
M,5511

결과 :

$ sort -u testfile | datamash -t, -g1 collapse 2  \
| tr ',' ' ' | while read line ; do ./my_filter $line ; done
A,3300
B,844
B,976
C,304
C,404
M,55
U,3
U,7

예상했던 결과와 같나요?

답변3

사용 awk:

BEGIN { OFS=FS="," }

prefixlength[$1] == "" {
        # First time seeing this label.
        # Remember the full string and its length.

        prefix[$1] = $2
        prefixlength[$1] = length($2)
        next
}

{
        # Compare the current string to the (current) longest
        # prefix related to this label. Update the prefix length
        # to the longest common prefix length.

        for (i = 1; i <= prefixlength[$1]; ++i)
                if (substr(prefix[$1],i,1) != substr($2,i,1)) {
                        prefixlength[$1] = i-1
                        break
                }
}

END {
        # Output labels and their longest prefix.

        for (i in prefix)
                print i, substr(prefix[i],1,prefixlength[i])
}

지정된 입력에 대해 다음을 수행합니다.

$ awk -f script file
A,3300
B,844
C,
M,55

계산된 가장 긴 접두사 길이가 0일 때 빈 접두사가 표시되므로 이 특별한 경우에 모든 문자열을 표시해야 하는 경우 코드를 약간 수정해야 할 수도 있습니다.

BEGIN { OFS=FS="," }

prefixlength[$1] == "" {
        # First time seeing this label.
        # Remember the full string and its length.

        prefix[$1] = $2
        prefixlength[$1] = length($2)
        next
}

{
        # Remember all found strings in a sort of "array". The
        # strings added after the first will only ever be used
        # if the prefix length ends up as zero.

        prefix[$1] = prefix[$1] SUBSEP $2
}

{
        # Compare the current string to the (current) longest
        # prefix related to this label. Update the prefix length
        # to the longest common prefix length.

        for (i = 1; i <= prefixlength[$1]; ++i)
                if (substr(prefix[$1],i,1) != substr($2,i,1)) {
                        prefixlength[$1] = i-1
                        break
                }
}

END {
        # Output labels and their longest prefix. If the prefix length
        # is zero for a label, output all collected strings as separate
        # lines.

        for (i in prefix)
                if (prefixlength[i] > 0)
                        print i, substr(prefix[i],1,prefixlength[i])
                else {
                        n = split(prefix[i],a,SUBSEP)
                        for (j = 1; j <= n; ++j)
                                print i, a[j]
                }
}

시험:

$ awk -f script file
A,3300
B,844
C,304
C,404
M,55

관련 정보