Excelのcsv形式の読み込みに関する問題と解決法

このQ&Aのポイント
  • Excelのcsv形式のデータを読み込んでいる際、未入力データ箇所があるために出力データがズレるという問題に遭遇しました。
  • 解決法として正規表現を使用して半角スペースを挿入しようとしましたが、基データを直接操作することは望ましくないため、プログラムで処理する方法を探しています。
  • 現在のプログラムでは、file.csvのデータを読み込んでout.csvに出力しています。出力する際に、行頭の"や行末の"を削除して、空データを半角スペースに置換しています。
回答を見る
  • ベストアンサー

Excelのcsv形式の読み込み

Excelのcsv形式の読み込み 前回、http://okwave.jp/qa/q6018540.htmlで 質問させていただきましたJurassic_periodです。 お力を貸していただきました方々本当にありがとうございました。 今回ですが同じプログラムでまた壁にぶつかってしまいました。 どうかよろしくお願いいたします。 Excelのcsv形式の「file.csv」のようなデータを読み込んでいます。 「out.csv」のように出力したいのですが 未入力「""」のデータ箇所が多々ある事に気が付きました。 次データを読み込むため出力データがズレてしまい困っています。 「file.csv」 "2010/1/1","C","こんにちは","田中","end", "2009/10/2","B","おはよう","","end", "2007/3/20","E","Good mor ning","佐藤","end", "1988/8/16","","こんばんは","中 村","end", "","A","Hello","木村","end", "2005/9/17","D","おはようご ざいます","斎藤","end", 「out.csv」(このように出力したいです) C,こんにちは,田中,2010/1/1,end, B,おはよう, ,2009/10/2,end, E,Good morning,佐藤,2007/3/20,end, ,こんばんは,中村,1988/8/16,end, A,Hello,木村, ,end, D,おはようございます,斎藤,2005/9/17,end, 解決法として、正規表現を用いて「半角スペース」を入れようとしました。 また、直接「file.csv」の「,"",」を置換で「," ",」にしましたが 基データを操作するのは好ましくないのでプログラムで どうにか処理ができないでしょうか。 「今書いているプログラムです」 #!/usr/local/bin/perl use strict; use Fatal qw/ open /; my $csv_file = "file.csv"; my @csv; &readCsvFile($csv_file); open(OUT,">out.csv"); for(my $i=0; $i<=$#csv; $i++){ $csv[$i][0] =~ s/"//; #行頭の"を削除 $csv[$i][4] =~ s/"//; #行末の"を削除 $csv[$i][0] =~ s// /; #空データを半角スペースに置換 $csv[$i][1] =~ s// /; $csv[$i][3] =~ s// /; print OUT $csv[$i][1],","; print OUT $csv[$i][2],","; print OUT $csv[$i][3],","; print OUT $csv[$i][0],","; print OUT $csv[$i][4],",\n"; } close(OUT); sub readCsvFile { open(IN, $_[0]); my $line = ""; while(<IN>) { chomp; $line .= $_; next if $line !~ /end/; push @csv, [ grep { length } split(/","|",|"/, $line) ]; $line = ""; } close(IN); } どうか、よろしくお願いいたします。

  • Perl
  • 回答数5
  • ありがとう数12

質問者が選んだベストアンサー

  • ベストアンサー
  • kumoz
  • ベストアンサー率64% (120/185)
回答No.2

grep { length } が空要素を削除しています。grep { length } の処理を行わずに、行の先頭の " と行末の ", を別の手段で削除すれば空要素もそのまま残るようになります。 sub readCsvFile {    open(IN, $_[0]);    my $line = "";    while(<IN>) {      chomp;      $line .= $_;      next if $line !~ /end/;      $line =~ s/^"//; $line =~ s/",$//; # この行を追加      push @csv, [ split(/","/, $line) ]; # "," のみに戻す      $line = "";    }    close(IN); }

Jurassic_period
質問者

お礼

回答ありがとうございました。 無事に解決できました! 本当にありがとうございました!

その他の回答 (4)

  • ralf124c
  • ベストアンサー率52% (232/446)
回答No.5

流儀が違うのでご参考になるかわかりませんが、たぶん期待した結果が得られると思うものを作ってみました。 -------------------------------------- #!/usr/local/bin/perl open(WR_DATA,">out.csv"); open(RD_DATA, "file.csv"); while(my $line = <RD_DATA>){  $line .= <RD_DATA> while ($line =~ tr/"// % 2 and !eof(RD_DATA));  $line =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/;  $line =~ s/\r\n|\r|\n//g;  my @csv = map {/^"(.*)"$/s ? scalar($_ = $1, s/""/"/g, $_) : $_}        ($line =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);  if($csv[0] eq ""){ $csv[0] = " "; }  if($csv[1] eq ""){ $csv[1] = " "; }  if($csv[3] eq ""){ $csv[3] = " "; }  my $sTmp = sprintf("%s,%s,%s,%s,%s,\n",$csv[1],$csv[2],$csv[3],$csv[0],$csv[4]);  print WR_DATA $sTmp; } close(RD_DATA); close(WR_DATA); exit;

Jurassic_period
質問者

お礼

ralf124cさま、2回目の回答ありがとうございます。 ANo.3の回答を入れてプログラムを書いていただき とても参考になりました。 こんな書き方があったのかと驚きました。 本当にありがとうございました。

回答No.4

最近は閑古鳥が泣いている某サイトに昔ちょうどいい感じのお題が出ていました。 perlの投稿は2件。 べた書きされている方は、戻り読みとかすればもう少しスマートになりそうだけど 紹介しておきます。 Text::CSV_XSは日本語の扱いに問題があるって聞いたことあるけど 今はもう直っているのかな?

参考URL:
http://ja.doukaku.org/33/
Jurassic_period
質問者

お礼

こんなサイトがあったのですね! 参考にさせていただきました。 Text::CSV_XSを使用しようか悩みましたが、 問題があるとあちこちで書かれていたため結局やめました… ありがとうございました。

  • ralf124c
  • ベストアンサー率52% (232/446)
回答No.3

根本部分は他の方がご指摘されておりますのであと   $csv[$i][0] =~ s// /; #空データを半角スペースに置換 これだと、中身がなんでも先頭に必ずスペースが入ってしまう。 まわりくどいけど   if($csv[$i][0] eq ''){ $csv[$i][0] = ' '; } こんな感じでいいんじゃないでしょうか。

Jurassic_period
質問者

お礼

細かなところまで指摘していただいてありがとうございました。 お陰でかなりスッキリしました!

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

split で一気に処理しようなどと思わず, ちょっとずつやっていけばいいのでは?

Jurassic_period
質問者

お礼

回答ありがとうございました。 ちょっとずつやるやり方も分らなかったので質問をしました。

関連するQ&A

  • ファイルの読み込みと出力

    ファイルの読み込みと出力 Perl初心者です。よろしくお願いします。 file.csvのようなファイルを読み込んで、 out.csvのように出力するプログラムを作成しているのですが 途中で変な改行が度々入っているためどうしてもうまくいきません。 (file.csvですが、実際は1500行以上あります。 また、最後に必ずendが入っています。 下記のcsvですがテキスト形式で表示した内容です。) 初歩的な質問で申し訳ありません。 調べる限り調べたのですが分かりませんでした。 どうかよろしくお願いします。 「file.csv」 "2010/1/1","C","こんにちは","田中","end", "2009/10/2","B","おはよう","斉藤","end", "2007/3/20","E","Good mor ning","佐藤","end", "1988/8/16","F","こんばんは","中 村","end", "1999/1/10","A","Hello","木村","end", "2005/9/17","D","おはようご ざいます","斎藤","end", 「out.csv」(このように出力したいです) C,こんにちは,田中,2010/1/1,end, B,おはよう,斉藤,2009/10/2,end, E,Good morning,佐藤,2007/3/20,end, F,こんばんは,中村,1988/8/16,end, A,Hello,木村,1999/1/10,end, D,おはようございます,斎藤,2005/9/17,end, 「今書いているプログラムです」 #!/usr/local/bin/perl use strict; use Fatal qw/ open /; my $csv_file = "file.csv"; my @csv = &readCsvFile($csv_file); open(OUT,">out.csv"); for(my $i=0; $i<=5; $i++){    print OUT $csv[$i][1],",";    print OUT $csv[$i][2],",";    print OUT $csv[$i][3],",";    print OUT $csv[$i][0],",";    print OUT $csv[$i][4],","; } close(OUT); sub readCsvFile {    open(DATA, $_[0]);    while(<DATA>) {      chomp;      push @csv, [ split(/",\"/) ];    }    close(DATA);    return @csv; }

    • ベストアンサー
    • Perl
  • CSV形式にすると。。。

    ファイルを読み込みCSVカンマ区切りのデータにするプログラムを作ったのですが、 ダブルクォーテーションを取得行の行末に付加した場合に文字化けを起こすケースがあり、「150」が「"15"」等のようになってしまいます。  どなたか、分かる方がいましたらよろしくお願いします。 # ファイルのオープン open ( FILEIN , "$FName") || die "ファイルを開けません :$!\n"; @line = <FILEIN>; close (FILEIN); foreach $line (@line){ ##### 文字化け対策 ##### $line =~ s/ⅰ|ⅱ|ⅲ|ⅳ|ⅴ|ⅵ|ⅶ|ⅷ|ⅸ|ⅹ//g; $line =~ s/\"/\"\"/g; $line =~ s/\,\"\?/\"\,\"/g; $line =~ s/\"\,\?/\"\,\"/g; $line =~ s/\ |\ /\"\,\"/g; # sjis --> euc コードへ変換 &jcode::sjis2euc(\$line,"z"); if (length $line > 1){ $cd_22 = chr(0x0022); $line = "$cd_22$line"; $line =~ s/[^$1\n]$/\"/g; } } open (FILEOUT, ">./log/$file") or die; print FILEOUT @line; close (FILEOUT); }

    • ベストアンサー
    • Perl
  • 文字コードの変換(Shift-JISからUTF8)

    文字コードがShift-JISのCSVファイルを読み込み、UTF-8のテキストファイルに出力するのに プログラムの中で変更しようとしているのですが、うまくいきません。出力ファイルの文字コードを 確認するとShift-JISのままです。 どなたか教えていただけないでしょうか? ActivePerl v5.16.0を使用し、Encodeモジュールのfrom_toを使用しています。 #!/usr/bin/perl use strict; use warnings; use utf8; use Encode; my $input_file="input.csv"; my $output_file="output.txt"; open (IN, $input_file) or die "$!"; open (OUT, ">$output_file") or die "$!"; while (<IN>){ chomp ($_); my @data=split(/,/,$_); for(my $i=0;$i<@data;$i++){ $data[$i]=Encode::from_to($data[$i],'shiftjis','utf8'); #Shift-JISからUTF-8に変換 $data[$i]=~s/\s+//g; print OUT $_; } print OUT "\n"; } close (IN); close (OUT);

    • ベストアンサー
    • Perl
  • <Perl>参照配列の出力に失敗する。

    <Perl>参照配列の出力に失敗する。 お世話になります。 配列の出力部で以下のエラーが出力されます。 Use of uninitialized value in print at test2.pl line 12. -----コーディングは以下の通りです。----- #!C:\perl use strict; use warnings; my @l = (); #----------- #GetDataへCSVファイル名と、格納用配列を渡す #----------- my $cnt = &GetData("test.csv", \@l); print "COUNT -> ".$cnt; for(my $i=0; $i < $cnt; $i++){ print $l[$i]; } ################################################################## # 概   要:指定したCSVファイルをオープンしCSVデータを配列に取得する。 # パラメータ:ファイル名, CSVデータ格納用配列 # 戻 り 値:データ取得件数 ################################################################## sub GetData { my ($f, @bf) = @_; my $rcnt = 0; print "FILE NAME -> ".$f."\n"; if ( open(FP, "<${f}") ){ print "FILE OPEN -> success.\n"; @bf = split(/,/, <FP>); close(FP); $rcnt = @bf; print "CSV GET COUNT -> ".$rcnt."\n"; } return $rcnt; } -----実行結果は以下の通りです。----- D:\>perl test.pl FILE NAME -> test.csv FILE OPEN -> success. CSV GET COUNT -> 5 Use of uninitialized value in print at test2.pl line 12. Use of uninitialized value in print at test2.pl line 12. Use of uninitialized value in print at test2.pl line 12. Use of uninitialized value in print at test2.pl line 12. COUNT -> 5 -----CSVファイルの内容は以下の通りです。(ファイル名:test.csv)----- あいうえお,かきくけこ,さしすせそ,たちつてと,なにぬねの 配列の要素数が取れているので、配列内にデータは格納されているとは思っています。 出力方法をどのように正せばよいがご教示お願い致します。

    • ベストアンサー
    • Perl
  • CSVの条件出力について

    ディレクトリー内のCSVを1ファイルにするルーチンで、現在46カラム目から3桁が"000"なら出力としている判断を、5項目めが"000"ならに変更したいのですが、どのようにすればよいでしょうか? 現在の記述は下記の通りです。 if(open(OUT,">$path/$year$mon$mday$csv")){ for my $fname (sort @FTPlist){ if(open(IN,"$path/$fname")){ while (<IN>) { $sip = substr($_,46,3); if ($sip == "000") { print OUT "$_"; } } close(IN); } } close(OUT); }

    • ベストアンサー
    • Perl
  • CSVデータの編集の際の重複チェックの方法

    今、data.csv(カンマ区切り)として、左から順位、名前、性別という3項目で、10人程度のリストデータがあります。 data.csv(カンマ区切り)を編集するようにしていますが、順位は重複してはならないので、重複していたらエラーを出したいのですが。。。 ($rank,$name,$sex) = split(/\,/,$line); です。 open(IN,"$logfile") || &error("ファイルが開けません"); @lines = <IN>; close(IN); # 情報の書換え foreach $line (@lines) { ($rank,$name,$sex) = split(/\,/,$line); $line = "$in{'rank'},$name,$sex\n";} push(@new,$line); } # ファイルを更新 open(OUT,">$logfile") || &error("ファイルが開けません"); print OUT @new; close(OUT);

  • 複数のCSVを1つのファイルにまとめる

    リモートからダウンロードしたCSVファイルをひとつにまとめる処理をしています。 ダウンロードするリストファイルとCSVファイルのダウンロードはできたのですが、最後にダウンロードしてきたCSVファイルをひとつにまとめるところがうまくいきません。 こちらの過去ログから下記記述してみたのですが、うまくいきません。 if(open(OUT,">$path/$year$mon$mday$csv")){      for my $fname (sort @dirs){        if(open(IN,"$path/$fname")){          my @lines = <IN>;          print OUT @lines;          close(IN);        }     }     close(OUT);   } for my $fname (sort @dirs){の記述で、Unrecognized characterといったエラーが出てうまくいきません。 項目の設定に誤りがあるのでしょうか?perl初心者でよくわかりません。 $fnameや@dirsはどのように設定すればいいのでしょうか?

    • ベストアンサー
    • Perl
  • perlでCSVをソートする方法について

    perl初心者です。いつもありがとうございます。 perlでcsvファイル(1行のカラム数は200)、総行数は約3万行のファイルを37番目のカラム(-25以上25未満の数値データ)で降順ソートしその値によって行数がだいたい均等になるよう3分割し、2番目のカラムに文字でも数字でもよいのですがその4つのグループごとにフラグ(例えば1,2,3)を入れたいと思ってます。グループ化については境目の37番カラムの値は重複している場合が多いと思うのですがその場合は下(別に上でもかまいません)に入れるものとします。 ソートロジックは過去の質問を参照して理解しましたがグループ化しフラグを入れるルーチンがうまく作れません。下記のように作ったのですがこの先同じことを何度もやらなくてはならないので先に進めません。どなたかお助けください。最終的にやりたいことはカラム37でグループ化→カラム2にフラグを立てる、次にカラム2とカラム38(-25から0までの数値)でソートし同様に同じ行数になるようにグループ化→カラム3にフラグを立てる、さらにカラム2とカラム3とカラム39(-25以上25未満の数値データ)でソートし・・・同様に繰り返し最終的に1グループが100件(行)~150件(行)になるようにしたいのです。つまり約3万件のデータを3*4*2*4*2=192分割(5列の値で分類)したい、そしてどのような範囲で分割したかという情報も得たいのです。 use strict; use warnings; use utf8; use Encode; binmode STDOUT, ':encoding(utf-8)'; my $dir = './data'; # 処理するディレクトリ my $motoFile = 'customer.txt'; # もとファイル open my $fh, '<:encoding(cp932)', "$dir/$motoFile" or die 'ファイルが開けません。',"$!"; my %sorted; while (my $line = <$fh>) { my $key = (split /,/, $line)[37]; push @{$sorted{$key}}, $line; if (@{$sorted{$key}} == 1000) { open OUT, '>>:encoding(cp932)', "$dir/$key.tmp" or die "Can't open: $!"; print OUT @{$sorted{$key}}; close OUT; @{$sorted{$key}} = (); } } open OUT, '>:encoding(cp932)', "$dir/out.txt" or die "Can't open: $!"; foreach my $key (sort { $b <=> $a } keys %sorted) { if (-e "$key.tmp") { open IN, '<:encoding(cp932)', "$dir/$key.tmp" or die "Can't open: $!"; print OUT while <IN>; close IN; } print OUT @{$sorted{$key}} if @{$sorted{$key}}; } close OUT; #↓↓↓↓ここからフラグを作成するルーチン # 行数を調べ3つに分けるルーチン my @colum37; open IN, '<:encoding(cp932)', "$dir/out.txt" or die 'ファイルが開けません。',"$!"; my @in = <IN>; close IN; my $gyousuu = scalar(@in); my $amari = $gyousuu % 3; if ($amari == 0) { my $groupGyousuu = ($gyousuu-$amari)/3; print "総行数は$gyousuu","で、1グループの行数は$groupGyousuu","ほど、余りは$amari\n"; # あまりが0の時、group1は@inの0行 ~$groupGyousuu-1行まで #         group2は@inの$groupGyousuu行 ~$groupGyousuu*2-1行まで #         group3は@inの$groupGyousuu*2行~$groupGyousuu*3-1行まで foreach my $num (1..2) { push @colum37, (split /,/, $in[$groupGyousuu*$num])[37]; # これは境目の先頭の37番目 } print "@colum37\n"; #これでここまでは完成、分けるべき値がこの配列に入っている。 open OUT, '>:encoding(cp932)', "$dir/out.txt" or die "Can't open: $!"; foreach my $line (@in) { my @line = split /,/,$line; if ($line[37]>=$colum37[0]) { $line[1] = 1; }elsif ($line[37]>=$colum37[1] and $line[37]<$colum37[0]) { $line[1] = 2; }elsif ($line[37]<$colum37[1]) { $line[1] = 3; } $line = join (',',@line); print OUT $line; } close OUT; } elsif ($amari == 1) { この後未作成

    • ベストアンサー
    • Perl
  • perlで容量の大きいCSVファイルを開く方法

    perlで容量の大きいCSVファイルを開く方法 ファイル容量の大きいcsvファイルから、必要な項目を抜き出して別ファイルにするプログラムを作成したいと思ってます。 csvファイルが少ない場合は動作したのですが、容量が140MBを超えたデータを読み込もうとすると、ブラウザー表示で何も変化いたしません プログラムは以下のようになってます。 ------------------------------------------------- 略 open(IN,"$inport") || &error(" $inport を読み込みopen出来ません"); flock(IN,1); @lines = <IN>; foreach $lines (@lines) { local(@val) = split("\,", $lines); print "$val[0]"; $dat .= "$val[1]\,$val[5]\n"; } open(OUT,">$dcsv"); flock(OUT,2); print OUT "$dat"; close OUT; 略 ------------------------------------------------- 件数も多いので、foreachを$lines (@lines) としないで($start .. $end)として読み込みの件数を制限して対応しようと考えてましたが、うまくいきませんでした。 ご指導いただけますと幸いです。

    • ベストアンサー
    • Perl
  • FTP接続時失敗時、接続先名をcsvに出力する

    Perl 初心者です。 FTP接続時失敗時、接続先名をcsvに出力する(書き出す)プログラムを作成しております。 ※複数FTPサーバがあり順番に接続していき、接続失敗したところをcsvに一覧にして出力したいです。もし、csvファイルがない場合、C:/logの下にERROR.csvを作成する必要もあります。 今現在、私が作成しているプログラムが以下になります。 #!C:/Perl/bin/perl use Net::FTP; use strict; my $i; my $j; my @ftp_data = ( ['ftp','192.168.0.0'], ['ftp2','192.168.0.10'], ); sub error{ my $file = "ERROR.csv"; if(!-d"C:/log/$file"){ open(TFILE,"+>> $file") or die($!); print OUT "$ftp_data[$i][0]"; close(TFILE); } else{ open(OUT, ">> $file"); # 追加オープン print OUT "$ftp_data[$i][0]"; close(TFILE); } exit; } for ($i = 0; $i <= $#ftp_data; $i++){ #FTPサーバーへの接続 my $ftp = Net::FTP->new("$ftp_data[$i][1]") or &error; #ユーザー名とパスワードを指定してログイン $ftp->login('user','password') or die $ftp->message; print "connect $ftp_data[$i][1]\n"; #接続終了 $ftp->quit; } 実行してみると何もでないでずっとループ?しているみたいなのですが、 どこが悪いのかよく分からないのです。 ご教示いただけないでしょうか?

    • ベストアンサー
    • Perl

専門家に質問してみよう