CSV 파일 병합, 필드 구분 기호도 따옴표 안에 표시됩니다.

CSV 파일 병합, 필드 구분 기호도 따옴표 안에 표시됩니다.

3개의 csv 파일이 있고 첫 번째 열(id 열)로 조인하고 싶습니다.

각 파일에는 동일한 3개의 열이 있습니다.

라인 예:

id        | timestamp       |  Name

3792318, 2014-07-15 00:00:00, "A, B"

3개의 csv 파일을 합칠 때

join -t, <(join -t, csv1 csv2) csv3 > out.csv

파일 out.csv의 행당 열 수가 다릅니다. 아마도 구분 기호가 쉼표이고 일부 행(위 예와 같은)에는 셀 내용에 쉼표가 포함되어 있기 때문일 것입니다.

답변1

분명히 csv 파서를 사용하는 것이 더 낫겠지만, 안전하게 가정할 수 있다면

  1. 첫 번째 필드에는 쉼표가 포함되지 않습니다.
  2. 첫 번째 파일에 존재하는 ID만 필요합니다(ID가 file2 또는 file3에 있지만 file1에는 없으면 무시하세요).
  3. 이러한 파일은 RAM에 들어갈 만큼 작습니다.

그러면 이 Perl 방법이 작동할 것입니다:

#!/usr/bin/env perl 
use strict;

my %f;
## Read the files
while (<>) {
    ## remove trailing newlines
    chomp;
    ## Replace any commas within quotes with '|'.
    ## I am using a while loop to deal with multiple commas.
    while (s/\"([^"]*?),([^"]*?)\"/"$1|$2"/){}
    ## match the id and the rest.
    /^(.+?)(,.+)/; 
    ## The keys of the %f hash are the ids
    ## each line with the same id is appended to
    ## the current value of the key in the hash.
    $f{$1}.=$2; 
}
## Print the lines
foreach my $id (keys(%f)) {
    print "$id$f{$id}\n";
}

위의 스크립트를 다른 이름으로 저장하고 foo.pl다음과 같이 실행합니다.

perl foo.pl file1.csv file2.csv file3.csv

위 스크립트는 한 줄로 작성할 수도 있습니다.

perl -lne 'while(s/\"([^"]*?),([^"]*)\"/"$1|$2"/){} /^(.+?)(,.+)/; $k{$1}.=$2; 
           END{print "$_$k{$_}" for keys(%k)}' file1 file2 file3

답변2

TxR언어:

@(do
   (defun csv-parse (str)
     (let ((toks (tok-str str #/[^\s,][^,]+[^\s,]|"[^"]*"|[^\s,]/)))
       [mapcar (do let ((l (match-regex @1 #/".*"/)))
                     (if (eql l (length @1))
                       [@1 1..-1] @1)) toks]))

   (defun csv-format (list)
     (cat-str (mapcar (do if (find #\, @1) `"@1"` @1) list) ", "))

   (defun join-recs (recs-left recs-right)
     (append-each ((l recs-left))
       (collect-each ((r recs-right))
         (append l r))))

   (let ((hashes (collect-each ((arg *args*))
                   (let ((stream (open-file arg)))
                     [group-by first [mapcar csv-parse (gun (get-line stream))]
                               :equal-based]))))
     (when hashes
       (let ((joined (reduce-left (op hash-isec @1 @2 join-recs) hashes)))
         (dohash (key recs joined)
           (each ((rec recs))
             (put-line (csv-format rec))))))))

견본.

참고: 키 3792318은 세 번째 파일에 두 번 나타나므로 이 키에 대한 연결된 출력에는 두 줄이 있을 것으로 예상됩니다.

참고: 데이터를 정렬할 필요는 없으며 조인에 해싱이 사용됩니다.

$ for x in csv* ; do echo "File $x:" ; cat $x ; done
File csv1:
3792318, 2014-07-15 00:00:00, "A, B"
3792319, 2014-07-16 00:00:01, "B, C"
3792320, 2014-07-17 00:00:02, "D, E"
File csv2:
3792319, 2014-07-15 00:02:00, "X, Y"
3792320, 2014-07-11 00:03:00, "S, T"
3792318, 2014-07-16 00:02:01, "W, Z"
File csv3:
3792319, 2014-07-10 00:04:00, "M"
3792320, 2014-07-09 00:06:00, "N"
3792318, 2014-07-05 00:07:01, "P"
3792318, 2014-07-16 00:08:01, "Q"

달리기:

$ txr join.txr csv1 csv2 csv3
3792319, 2014-07-16 00:00:01, "B, C", 3792319, 2014-07-15 00:02:00, "X, Y", 3792319, 2014-07-10 00:04:00, M
3792318, 2014-07-15 00:00:00, "A, B", 3792318, 2014-07-16 00:02:01, "W, Z", 3792318, 2014-07-05 00:07:01, P
3792318, 2014-07-15 00:00:00, "A, B", 3792318, 2014-07-16 00:02:01, "W, Z", 3792318, 2014-07-16 00:08:01, Q
3792320, 2014-07-17 00:00:02, "D, E", 3792320, 2014-07-11 00:03:00, "S, T", 3792320, 2014-07-09 00:06:00, N

보다 "올바른" csv-parse기능은 다음과 같습니다.

   ;; Include the comma separators as tokens; then parse the token
   ;; list, recognizing consecutive comma tokens as an empty field,
   ;; and stripping leading/trailing whitespace and quotes.
   (defun csv-parse (str)
     (labels ((clean (str)
                (set str (trim-str str))
                (if (and (= [str 0] #\")
                         (= [str -1] #\"))
                  [str 1..-1]
                  str))
              (post-process (tokens)
                (tree-case tokens
                  ((tok sep . rest)
                   (if (equal tok ",")
                     ^("" ,*(post-process (cons sep rest)))
                     ^(,(clean tok) ,*(post-process rest))))
                  ((tok . rest)
                   (if (equal tok ",")
                     '("")
                     ^(,(clean tok)))))))
       (post-process (tok-str str #/[^,]+|"[^"]*"|,/))))

관련 정보