sironekotoroの日記

Perl で楽をしたい

Perlでcsvファイルを読み込んで 配列|ハッシュ にする

先週からの続きです

先週でテキストファイルの読み書きができるようになりました。

sironekotoro.hateblo.jp

ということで、今回は代表的なテキストファイルのデータとしてCSVの処理方法をやっていきます。

CSVファイル

Comma Separated Values の頭文字を取った名前の通り、カンマ区切のデータのことです。

1行にカンマ区切りで複数のデータのがあり、それが複数行ある・・・というです。

この1行のことをレコードと呼び、レコードの中のカンマで区切られたところをフィールドと呼びます。

Perl入学式のリファレンス回の練習問題からデータを持ってcsv風に書くとこんな感じです。

name,country,perl,python,ruby,php,binary
Alice,England,60,80,80,50,30
Bob,America,40,10,50,30,50

では、これを records.csv というテキストファイルにして、色々と処理をしていきます。

csvファイルを単に読むだけ

先週の復習です。csvファイルと今回作成するPerlスクリプトは同じ場所に置いて実行してください。

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

my $filename = 'records.csv';
open my $FH, '<', $filename;
for my $line (<$FH>){
    chomp $line;
    print $line . "\n";
}
close $FH;

csvを読み込んで配列にする

データが詰まっている1行はカンマ区切りなので、split で分割して配列に入れるだけです。

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

my $filename = 'records.csv';
open my $FH, '<', $filename;

for my $line (<$FH>) {
    chomp $line;
    my @field = split /,/, $line;   # カンマ区切りで配列に格納
    print "@field" . "\n";          # 配列をprint
}

close $FH;

ファイルの読み込みと、読み込んだデータの処理を分けて書くときとには配列のリファレンスを使うことになると思います。

こんな感じかなー。無名配列でもっと短くかけますが、そこは各自で・・・

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

my @records;    # 配列リファレンスを格納する配列

my $filename = 'records.csv';
open my $FH, '<', $filename;

for my $line (<$FH>) {
    chomp $line;
    my @field = split /,/, $line;    # カンマ区切りで配列に格納

    my $record = \@field;            # 配列リファレンスにする
    push @records, $record;   # 配列リファレンスを格納用の配列に入れる
}

close $FH;

# 結果表示
for my $record (@records) {
    my @record = @{$record};    # デリファレンスして配列に戻す
    print "@record" . "\n";     # デリファレンスしたものを表示
}

csvを読み込んで配列にする(モジュールを使う)

ここまで書いてきてなんですが、自分でパース(データを分けること)することはお勧めしません。

例えば、フィールドのデータ内に , があると、split はそこで分割してしまいます。

Perlcsvを扱うモジュールといえば Text::CSV_XS が定番です。Text::CSV_XS であれば、そのようなデータにもうまく対応してくれます。

(ただし、処理するファイルの前提はある)

また、スクリプトの上の方に use Text::CSV_XS; とあると、「あー、csvを何かするスクリプトなのね」と理解の一助になります。

なお、標準モジュールではないのでインストールが必要です。

$ cpanm Text::CSV_XS

perldoc.jp

利用例としてはこんな感じです。

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

use TEXT::CSV_XS;

my $csv = Text::CSV_XS->new();    # csvを扱う便利オブジェクト

my $filename = 'records.csv';
open my $FH, '<', $filename;
for my $line (<$FH>) {
    chomp $line;
    my $status = $csv->parse($line); # CSV文字列をパースしてフィールド群に切り分ける
                                     # $status は成否判定が入っている(今回は使わない)
    my @columns = $csv->fields();    # パースされたフィールド群を配列に入れる
    print "@columns" . "\n";
}
close $FH;

csvを読み込んでハッシュにする

こちらの使い方も多いと思います。

ハッシュにするにあたって考えるのは、keyvalue をどうするか?ということです。

今回はこんな感じのデータ構造を作ってみます。配列の中にハッシュリファレンスが入っています。

ハッシュなので順不同なのですが、見やすさ優先で key でソートしてます。

$VAR1 = [
          {
            'binary' => '30',
            'country' => 'England'
            'name' => 'Alice',
            'perl' => '60',
            'python' => '50',
            'ruby' => '80',
          },
          {
            'binary' => '50'
            'country' => 'America',
            'name' => 'Bob',
            'perl' => '40',
            'python' => '30',
            'ruby' => '50',
          }
        ];
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

my @records;    # ハッシュリファレンスを格納する配列

my $filename = 'records.csv';
open my $FH, '<', $filename;

for my $line (<$FH>) {
    chomp $line;
    next
        if $line
        =~ /^name/;  # nameから始まる項目名の行だったら飛ばす

    my @field = split /,/, $line;    # カンマ区切りで配列に格納

    # ハッシュのデータ構造にする
    my %record = (
        name    => $field[0],
        country => $field[1],
        perl    => $field[2],
        python  => $field[3],
        ruby    => $field[4],
        python  => $field[5],
        binary  => $field[6],
    );

    my $record = \%record;    # ハッシュリファレンスにする
    push @records,
        $record;   # ハッシュリファレンスを格納用の配列に入れる
}

close $FH;

print Dumper \@records;

csvを読み込んでハッシュにする(モジュールを使う)

ここまで長く書いてきてなんですが、ハッシュにするときもモジュールを使うのが楽でおすすめです。

metacpan.org

こちらも標準モジュールではないのでインストールが必要です。

$ cpanm Text::CSV::Simple

3行で収まっちゃった・・・

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

use Text::CSV::Simple;

my $filename = 'records.csv';

my $parser = Text::CSV::Simple->new;
$parser->field_map(qw/name country perl python ruby php binary/);
my @data = $parser->read_file($filename);

print Dumper \@data;

というわけで

駆け足ながらPerlでのcsvファイル処理について書いてみました。

誰向けの記事かというと、6年くらい前の自分向けの記事です。