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

上記のスクリプトは1行で書くこともできます。

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は3番目のファイルに2回表示されるため、このキーへのリンクされた出力には2行があると予想されます。

注:データをソートする必要はなく、結合にハッシュが使用されます。

$ 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 #/[^,]+|"[^"]*"|,/))))

関連情報