누락된 열이 있는 CSV 파일 구성

누락된 열이 있는 CSV 파일 구성

질문

열 정보가 누락되어 올바르게 작성되지 않은 .csv 파일이 있습니다.

파일은 다음과 같습니다(명확성을 위해 공백을 사용함).

  C1,  C2,  C3,  C4,
R1C1,R1C2,R1C3,R1C4,
R2C1,R2C3,    ,    ,
R3C1,R3C4,    ,    ,
...

여기서 Cx는 열 헤더이고 와 문자열 값을 공유합니다 RyCx. 예를 들어,

Name     , Date       , Address           , Email              ,
Name Alex, Date Sept 3, Address 123 Madeup, Email [email protected],
Name Jenn, Date Sept 4, Email [email protected],                    ,

이제 열의 올바른 위치에 있지 않습니다.Email [email protected]Address

데이터 뒤에는 공백이 얼마든지 올 수 있습니다. 데이터는 R1C1,R1C2,R1C3데이터가 없는 경우를 제외하고 각 행이 [ ...] 순서대로 배치됩니다 . 이 경우 열은 왼쪽으로 이동하지만 해당 레이블은 Cx변경되지 않습니다. 이는 데이터를 출력하는 프로그램이 빈 셀을 생성하지 않기 때문입니다.

이 데이터에는 다른 패턴이 없습니다.

이 데이터를 다음과 같은 적절한 열로 재구성하고 싶습니다.

  C1,  C2,  C3,  C4,
R1C1,R1C2,R1C3,R1C4,
R2C1,    ,R2C3,    ,
R3C1,    ,    ,R3C4,
...

아니면 예시에서

Name     , Date       , Address           , Email              ,
Name Alex, Date Sept 3, Address 123 Madeup, Email [email protected],
Name Jenn, Date Sept 4,                   , Email [email protected] ,

예전 슈퍼컴퓨터 시뮬레이션의 결과였던 정보를 수집했던 곳으로 돌아갈 수는 없습니다.


해결책

FKEinternet과 urcodebetterznow에게 감사드립니다.

while IFS= read -r line; do # read input line by line
IFS=, read -ra fields <<<"$line" #separate fields by commas

    j=0; 
        for i in $(cat headers.txt); do #I wrote the headers to a different file

            if [ "${fields[j]}" == "$i" ]; then #replaced with grep -o result because fields are not exact matches for the header, but contain exact matches
                val="${fields[j]}"; : $((j += 1)); 
            else  val=''; 
            fi; 

            printf '%s,' "$val"; #I later used sed to erase the redundant information already in the header
         done

done < datafile.txt > solution.csv

headers.txt 파일은 다음과 같습니다.

a
b
c
d
e
f
g
h

데이터는 datafile.txt와 같습니다.

a,b,c,d,e,
a,c,e
b,d,f,g,h
c,d,g,h
d,h
b,f,g
a,d,f,g

내가 받은 bash 스크립트를 실행합니다(명확성을 위해 공백을 사용함).

a,b,c,d,e, , , ,
a, ,c, ,e, , , ,
 ,b, ,d, ,f,g,h,
 , ,c,d, , ,g,h,
 , , ,d, , , ,h,
 ,b, , , ,f,g, ,
a, , ,d, ,f,g, ,

이것이 우리가 원하는 결과입니다.

답변1

이제 다시 작성한 질문은 개념적으로 대답하기 쉽습니다. 데이터의 각 행에 표시되거나 표시되지 않을 수 있는 레이블 집합이 있습니다. 각 행을 읽고 열을 순차적으로 살펴보며 해당 열의 태그가 예상된 태그인지 확인하려고 합니다. 그렇지 않은 경우 빈 셀을 삽입하고 다음 열을 확인하십시오. 예상 레이블 목록의 끝에 도달하면 재구성된 행이 내보내집니다.

선택한 언어로 구현할 수 있는 의사코드는 다음과 같습니다.

read the first row
split the text on commas to create the array of expected tags
read the next row
    if no more data, exit
    split the text on commas to create a row data array
    for each expected tag
        check the current column in the row's data
        if the tag matches
            write the column data to the output
            advance the current column in the row data
        else
            write a blank column to the output
        terminate the output line

답변2

방금 데이터의 각 열이 실제로 열 이름으로 시작한다는 것을 알았습니다. 귀하의 질문을 처음 봤을 때 이 내용을 놓쳤나 봐요. 이렇게 하면 데이터 형식을 다시 지정할 수 있을 뿐만 아니라 매우 쉽습니다.

#!/usr/bin/perl

use strict;
my @headers; # array to hold the headers in the order they were seen.
my @search;  # array to hold a copy of @headers sorted by string length

while(<>) {
  chomp;    # remove newline character at end-of-line

  if ($. == 1) {
    next if (scalar @headers); # only process headers for first file
    # Split the first line into @headers array, removing any
    # leading or trailing spaces from each column
    @headers = split '\s*,\s*';

    # In case one key might be a substring of another key, copy the
    # @headers array, sorted by length, so we can compare the data
    # with the longest header names first.
    @search = sort { length($b) <=> length($a) } @headers;

    print join(",", @headers), "\n";

  } else {
    my %columns = ();
    # Loop over each column of the input line (row), inserting it into
    # the %columns hash, using the appropriate column name as the key.
    foreach my $c (split '\s*,\s*') {
      my $found = 0;
      foreach my $h (@search) {
        # If the current column ($c) begins with a header
        # name ($h), we've found the right key for it.
        if ($c =~ s/^$h\s+//i) { # match and remove header from column
        #if ($c =~ m/^$h\s+/i) { # or just match without removing header
          $columns{$h} = $c;
          $found = 1;
        };
      };
      warn "Unknown column '$c' in line $. of $ARGV\n" if
        ($c ne '' && ! $found);
    };

    # Output every column in the same order as in the header line.
    # Columns not actually present in a row are output as an empty field
    print join(",", @columns{@headers}), "\n";
  };

  # Reset the line counter at the end of each input file if
  # there's more than one
  close(ARGV) if eof;
}

각 열과 일치하는 정규식은 대소문자를 구분하지 않고 일치됩니다. 데이터에 대문자 또는 대소문자 혼합 버전이 포함된 열이 포함된 경우동일한이름을 지정한 다음 /i정규식에서 수정자를 제거합니다.

예를 들어 적절한 이름으로 저장 ./fix-data.pl하고 실행 가능하게 만듭니다 chmod +x ./fix-data.pl.

예제 출력:

$ ./fix-data.pl datafile.txt 
Name,Date,Address,Email
Alex,Sept 3,123 Madeup,[email protected]
Jenn,Sept 4,,[email protected]

또는 주석 처리된 대체 if문을 사용하세요.

$ ./fix-data.pl datafile.txt 
Name,Date,Address,Email
Name Alex,Date Sept 3,Address 123 Madeup,Email [email protected]
Name Jenn,Date Sept 4,,Email [email protected]

열 이름이 이미 머리글 행에 있고 각 출력 행의 각 열이 올바른 순서로 되어 있기 때문에 누군가가 두 번째 형식을 원하는 이유가 무엇인지 모르겠습니다. 하지만 이것이 원하는 것이라면 쉽습니다. 하다.

그런데 파이프를 통해 출력 형식을 동일한 너비의 열이 있는 테이블로 지정할 수 있습니다 column.

$ ./fix-data.pl datafile.txt | column -t -s , -o ', '
Name, Date  , Address   , Email
Alex, Sept 3, 123 Madeup, [email protected]
Jenn, Sept 4,           , [email protected]

column제 생각에는 with를 ' | '출력 구분 기호로 사용하는 것이 읽기 더 쉽습니다(여전히 스프레드시트로 쉽게 가져오거나 다른 프로그램에서 구문 분석할 수 있습니다).

$ ./fix-data.pl datafile.txt | column -t -s , -o ' | '
Name | Date   | Address    | Email
Alex | Sept 3 | 123 Madeup | [email protected]
Jenn | Sept 4 |            | [email protected]

column데이터를 유효한 json으로 출력할 수도 있습니다. 예를 들면 다음과 같습니다.

$ ./fix-data.pl datafile.txt |
  tail -n +2 |
  column --json -s , \
      --table-columns "$(sed -n -e '1s/ *, */,/gp' datafile.txt)"
{
   "table": [
      {
         "name": "Alex",
         "date": "Sept 3",
         "address": "123 Madeup",
         "email": "[email protected]"
      },{
         "name": "Jenn",
         "date": "Sept 4",
         "address": null,
         "email": "[email protected]"
      }
   ]
}

(적어도 Debian에서는 패키지 column에 있습니다 . 다른 배포판에서는 패키지에 있을 수 있습니다.bsdextrautils유틸리티Linux)

밀러그리고데이터 혼합데이터를 적절한 형식으로 변환하면 유용한 명령줄 도구이기도 합니다.


참고: 이 스크립트는 데이터가 간단한 쉼표로 구분된 형식이라고 가정합니다.아니요올바른 형식의 CSV(예:RFC 4180 - 쉼표로 구분된 값(CSV) 파일의 일반 형식 및 MIME 유형) 인용된 문자열 필드나 삽입된 쉼표를 사용할 수 있습니다.이내에참조된 필드입니다. 행에 인용된 열이 포함된 경우 각 입력 행을 단순히 쉼표로 구분하는 대신 CSV 파서를 사용해야 합니다. 예를 들어 Perl의텍스트::CSV기준 치수. 나는 이것이 필요하다고 생각하지 않습니다. 귀하의 데이터는 그것을 생성한 사람이 분명히 발명한 이상한 비 CSV 형식이기 때문입니다. (CSV를 알고 있다면 아마도 그것을 사용했을 것입니다... 또는 데이터를 엉망으로 만드는 것은 심지어 지금보다 더 나빠요).

이 경고는 모든 언어의 모든 구현에 적용됩니다. 문제는 코드보다는 엉망인 데이터로 인해 발생하기 때문입니다.

column또한 쉼표가 포함된 CSV에서는 작동하지 않습니다.

관련 정보