第3章 正規表現でCSVファイルから必要な行を取り出す
正規表現で検索してCSVファイルから必要な行を取り出してみるよ。正規表現のパターンマッチの構文を使うと、文字のパターンで検索できるんだ。正規表現が使えるようになるとテキスト処理がとっても便利になるよ。

正規表現でCSVファイルから行を取り出す準備

正規表現を使ってCSVファイルから必要な行を取り出してみましょう。第2章で学習したCSVデータに発売日などの情報を追加したデータを使います。

ID,書名,著者名,価格,発売日
1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03
2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03
3,データベース入門,田中太郎,1900,2019-03-06
4,正規表現テクニック,田中太郎,3000,2019-01-08
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19
6,気になるperl,竜崎新次郎,5000,2017-05-23

これを「book_data.csv」として保存してください。文字コードはUTF-8で保存してください。

まず、第2章で学習した、プログラムを少し改造します。CSVファイルを入力して、CSVデータをそのまま出力するプログラム「output_csv.pl」を書きます。

use strict;
use warnings;
use utf8;
use Encode 'encode', 'decode';

# DATAセクションから行を一行ずつ読み込む
while (my $line = <>) {

  # 一行目は読み飛ばす($.はファイルの行番号。nextで次のループの先頭へ)
  if ($. == 1) { next }

  # PerlのUTF-8を内部文字列へデコード
  $line = decode('UTF-8', $line);
  
  # 改行を削除
  chomp $line;

  # カンマで分割して各変数へ
  my ($id, $name, $author, $price, $issued_date) = split(/,/, $line);
  
  # 出力行を作成
  my $output_line = join(',', $id, $name, $author, $price, $issued_date);
  
  # Perlの内部文字列をUTF-8にエンコードして出力
  print encode('UTF-8', $output_line) . "\n";
}

次のように入力ファイルと出力ファイルを指定して実行します。

perl output_csv.pl book_data.csv > raw_output.csv

出力結果です。ヘッダを除いて、入力されたデータとまったく同じデータが出力されていれば、OKです。

1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03
2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03
3,データベース入門,田中太郎,1900,2019-03-06
4,正規表現テクニック,田中太郎,3000,2019-01-08
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19
6,気になるperl,竜崎新次郎,5000,2017-05-23

正規表現を使った検索を実践していきましょう。

検索条件を指定してみよう

さまざまな検索条件を指定してみましょう。

書名にPerlが含まれている

Perlが書名に含まれている行だけを出力してみましょう。

以下の正規表現を使います。「=~」は、パターンマッチ演算子です。「/」と「/」の間に正規表現を書きます。

if ($name =~ /Perl/) {
  # マッチした場合の処理
}

以下がプログラムの全体「contain_perl.pl」です。マッチした場合のみ行を出力します。

use strict;
use warnings;
use utf8;
use Encode 'encode', 'decode';

# DATAセクションから行を一行ずつ読み込む
while (my $line = <>) {

  # 一行目は読み飛ばす($.はファイルの行番号。nextで次のループの先頭へ)
  if ($. == 1) { next }

  # PerlのUTF-8を内部文字列へデコード
  $line = decode('UTF-8', $line);
  
  # 改行を削除
  chomp $line;

  # カンマで分割して各変数へ
  my ($id, $name, $author, $price, $issued_date) = split(/,/, $line);
  
  if ($name =~ /Perl/) {
    # 出力行を作成
    my $output_line = join(',', $id, $name, $author, $price, $issued_date);
    
    # Perlの内部文字列をUTF-8にエンコードして出力
    print encode('UTF-8', $output_line) . "\n";
  }
}

実行してみましょう。

perl contain_perl.pl book_data.csv

出力結果では、書名に「Perl」を含む行だけが出力されます。

1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03
2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03

書名がPerlで始まる

書名がPerlで始まるという正規表現に変えてみましょう。「^」は先頭を意味する正規表現文字です。サンプルプログラムは「contain_perl_head.pl」です。

  if ($name =~ /^Perl/) {
    # 出力処理
  }

実行してみましょう。

perl contain_perl_head.pl book_data.csv

出力結果です。書名がPerlで始まる行だけが出力されています。

1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03

書名がPerlで終わる

書名がPerlで終わるという正規表現に変えてみましょう。「$」は終端を意味する正規表現文字です。サンプルプログラムは「contain_perl_tail.pl」です。

  if ($name =~ /Perl$/) {
    # 出力処理
  }

実行してみましょう。

perl contain_perl_tail.pl book_data.csv

出力結果です。

2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03

Perlの大文字と小文字を区別せずに検索

Perlの大文字と区別を区別せずに検索してみましょう。「Perl」「PERL」「perl」などにマッチさせます。

大文字と小文字を区別せずに検索するには正規表現の「i」オプションを使用します。サンプルプログラムは「contain_perl_nocase.pl」です。

  if ($name =~ /perl/i) {
    # 出力処理
  }

実行してみましょう。

perl contain_perl_nocase.pl book_data.csv

出力結果です。「Perl」を含む行、「perl」を含む行が取り出せています。

1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03
2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03
6,気になるperl,竜崎新次郎,5000,2017-05-23

書名にPerlまたはWebを含む

書名にPerlまたはWebを含むという正規表現を書いてみましょう。またはを表現する「|」を使用し、「()」で囲みます。サンプルプログラムは「contain_perl_or_web.pl」です。

  if ($name =~ /(Perl|Web)/) {
    # 出力処理
  }

実行してみましょう。

perl contain_perl_or_web.pl book_data.csv

出力結果です。書籍にPerlとWebが含まれている行が出力されます。

1,Perlテキスト処理プログラミング入門,木本裕紀,2900,2021-01-03
2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19

書名が「Web(なんでもよい一文字以上)Perl」を含む

書名が「Web(なんでもよい一文字以上)Perl」という正規表現を書いてみましょう。サンプルプログラムは「contain_web_any_perl.pl」です。

  if ($name =~ /Web.+Perl/) {
    # 出力処理
  }

「.」で「改行文字を除いたすべての文字」を表現できます。「+」で「直前の文字の1文字以上」を表現できます。

実行してみましょう。

perl contain_web_any_perl.pl book_data.csv

出力結果です。

2,Web開発をやるぜPerl,木本裕紀,2000,2021-01-03

もし、WebとPerlの間が0文字でも良い場合は「+」の代わりに「*」を使うことができます。「*」で「直前の文字の0文字以上」を表現できます。

  if ($name =~ /Web.*Perl/) {
    # 出力処理
  }

価格が1000円以上、2000円より小さい

これは、正規表現ではありませんが、価格のの大小で検索するサンプルを書いてみます。サンプルプログラムは「price_range.pl」です。

  if ($price >= 1000 && $price < 2000) {
    # 出力処理
  }

実行してみましょう。

perl price_range.pl book_data.csv

出力結果です。価格が1000円以上、2000円より小さい行が出力されています。サンプルプログラムは「price_range.pl」です。

3,データベース入門,田中太郎,1900,2019-03-06

2018年から2019年の間に発売された

2018年から2019年の間に発売されたという条件を書いてみましょう。サンプルプログラムは「issued_date_range_ymd.pl」です。

  # 正規表現で発売日の年の部分を取得
  my $issued_year;
  if ($issued_date =~ /^(\d{4})/) {
    $issued_year = $1;
  }
  
  # 出版年を取得できた場合のみ処理
  if (defined $issued_year) {
    # 出版年が2018年~2019年まで
    if ($issued_year >= 2018 && $issued_year <= 2019) {
      # 出力処理
    }
  }

実行してみましょう。

perl issued_date_range_ymd.pl book_data.csv

出力結果です。2018年から2019年の間に発売された書籍のみ出力されます。

3,データベース入門,田中太郎,1900,2019-03-06
4,正規表現テクニック,田中太郎,3000,2019-01-08
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19

年を取得する正規表現の解説

年を取得する正規表現の解説です。

  # 正規表現で発売日の年の部分を取得
  my $issued_year;
  if ($issued_date =~ /^(\d{4})/) {
    $issued_year = $1;
  }

「^」は先頭を表現する正規表現文字。「\d」は、数字を表現する正規表現の文字クラス。「{4}」は、4文字続くという意味の正規表現の量指定子です。

丸カッコ「()」を使って、キャプチャすることができます。キャプチャされた文字は「$1」という変数で取得できます。

次の処理を見てみましょう。年が取得できた場合のみ処理を行いたいのでdefined関数で、年が取得できていることを確認しています。その後は、比較演算子「>=」「<=」と論理演算子「&&」を使って「2018年以上、かつ、2019年以下」という条件を作成しています。

  # 出版年を取得できた場合のみ処理
  if (defined $issued_year) {
    # 出版年が2018年~2019年まで
    if ($issued_year >= 2018 && $issued_year <= 2019) {
      # 出力処理
    }
  }

年月日を取得する正規表現

上記の例では、年を取っているだけですが、年月日を取得する正規表現を書いてみましょう。キャプチャの丸かっこが複数存在する場合は「$2」「$3」でキャプチャを取得できます。サンプルプログラムは「issued_date_range_ymd.pl」です。サンプルプログラムは「issued_date_range_ymd.pl」です。

  my $issued_year;
  my $issued_month;
  my $issued_mday;
  if ($issued_date =~ /^(\d{4})-(\d{2})-(\d{2})/) {
    $issued_year = $1;
    $issued_month = $2;
    $issued_mday = $3;
  }

実行してみましょう。出力結果は、先ほどのサンプルと同じです。

perl issued_date_range_ymd.pl book_data.csv

出力結果です。2018年から2019年の間に発売された書籍のみ出力されます。

3,データベース入門,田中太郎,1900,2019-03-06
4,正規表現テクニック,田中太郎,3000,2019-01-08
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19

文字列比較で日付の範囲を指定する方法

また単純に以下のように文字列比較演算子「ge」「le」を使って文字列比較をしてもかまいません。サンプルプログラムは「issued_date_range_string.pl」です。

  # 2018年1月1日から2019年12月31の間に発売された
  if ($issued_date ge '2018-01-01' && $issued_date le '2019-12-31') {
    # 出力処理
  }

実行してみましょう。

perl issued_date_range_string.pl book_data.csv

出力結果です。2018年から2019年の間に発売された書籍のみ出力されます。

3,データベース入門,田中太郎,1900,2019-03-06
4,正規表現テクニック,田中太郎,3000,2019-01-08
5,本音で語るWeb開発,望月正太郎,5000,2018-02-19

次の章では、HTMLを題材にして正規表現の検索と置換をさらに実践していきます。

業務に役立つPerl

Perlテキスト処理のエッセンス 書籍版

関連情報