Perlの組み込みモジュールText::Diffで差分をとってみる
シンプルなDiffツールのひとつとして
職場ではWindowsを使っていて、普段DiffをとるときはGUIなAraxis Mergeを使っています。1文字単位で差分を色分けして表示してくれるので、わかりやすく便利です。 とはいえ有料ソフトなので、だれでもが使えるわけではないのです。
かんたんに差分を確認したいだけであれば、色分け表示とかなくても大丈夫でしょう。
そんなとき、Perlクックブック Vol.1をぱらぱら見ていたら、ずばりText::Diffというモジュールが見つかりました。
このモジュールの使い方と出力のサンプルをのせてみます。
コード
file1
吾輩わがはいは猫である。名前はまだ無い。 どこで生れたかとんと見当けんとうがつかぬ。 何でも薄暗いじめじめした所でミャオミャオないていた事だけは記憶している。
file2
吾輩わがはいは猫である。名前はまだ無い。 どこで生れたかとんと見当けんとうがつかぬ。 何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
鳴き声を変えてみました。
use Text::Diff; #比較するファイル2つを指定 my $file1 = 'f1.txt'; my $file2 = 'f2.txt'; #引数にファイルを渡すだけ my $diff = diff($file1, $file2); print $diff;
簡単です。
結果は↓
--- f1.txt Thu May 4 15:57:19 2017 +++ f2.txt Thu May 4 15:57:26 2017 @@ -1,3 +1,3 @@ 吾輩わがはいは猫である。名前はまだ無い。 どこで生れたかとんと見当けんとうがつかぬ。 -何でも薄暗いじめじめした所でミャオミャオないていた事だけは記憶している。 \ No newline at end of file +何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。 \ No newline at end of file
なんかよくわからない\ No newline at end of file
というメッセージが出てきてしまっていますが。
感想
1行ずつ比較されるようです。画面上で行の途中で折り返しされるほど1行の文字数が多い場合、どこが差分なのか探すのが大変な感じがします。
そのため、Text::Diffで差分をみるまえに、センテンス毎に改行を入れてやった方がみやすくなりそうです。つまり日本語であれば読点、英文であればピリオドで改行を入れてやる、と。
差分のみを出力する方法があれば、もっと便利だなと思います。
環境
Perl v5.18.2
Perl サブルーチンのレファレンスをつくる
テーマ
サブルーチンをレファレンス化して実行してみる。
サンプルコード
my $codeRef = \&daikeiNoMenseki; my $menseki = $codeRef->(200,40,500); print "Area measurement: $menseki\n"; sub daikeiNoMenseki { my ($joutei, $katei, $takasa) = @_; if ($joutei < 0 || $katei < 0 || $takasa < 0 ) { warn "argument must be greater than 0!\n"; return -1; } my $menseki = ($joutei + $katei) * $takasa / 2; return $menseki; }
ポイント
- 配列レファレンスやハッシュレファレンスと同様に
\
を付けることでレファレンスがつくれる。
&daikeiNoMenseki
を\&daikeiNoMenseki
とする。
2.サブルーチンレファレンスでも矢印記法でデリファレンスできる。
$codeRef->(200,40,500)
VBA Dir関数でフォルダ内のファイルを取得する
よくやる処理だけど、全然おぼえられないので写経する。
Office TANAKA - Excel VBAファイルの操作[ファイルの一覧を取得する]
Sub GetFiles() Dim buf As String Dim c As Long: c = 0 Dim fs() As String 'hoge以下にあるファイル一覧を取得したい Const Path As String = "D:\buf\" buf = Dir(Path & "*") Do While buf <> "" ReDim Preserve fs(c) As String fs(c) = buf c = c + 1 buf = Dir() Loop Dim i For i = LBound(fs) To UBound(fs) Debug.Print fs(i) Next End Sub
Perlで2次元ハッシュをつくる
ハッシュの中にハッシュを入れて2次元データ構造をつくってみる。
今回はハッシュのハッシュレファレンスにしてみた。
サンプルコード
use strict; use warnings; use DDP; my $hash_ref_with_hash; my $category; while (<DATA>) { chomp; if ( /^\[(.*)\]/ ) { $category = $1; } elsif (/^(.*)=(.*)$/) { $hash_ref_with_hash->{$category}{$1} = $2; } } p $hash_ref_with_hash; __DATA__ [File] perl_root=C:\Perl tmp=C:\tmp file1=file1.txt file2=file2.txt file3=file3.txt [Internet] web=http://www.gihyo.co.jp/ ftp=ftp://www.gihyo.co.jp/
出力
p $hash_ref
の出力。
\ { File { file1 "file1.txt", file2 "file2.txt", file3 "file3.txt", perl_root "C:\Perl", tmp "C:\tmp" }, Internet { ftp "ftp://www.gihyo.co.jp/", web "http://www.gihyo.co.jp/" } }
つまずきポイント
1.ハッシュレフにハッシュを入れる方法は、$hash_ref->{key} = $value
とする。
$hash_ref->{ $key => $value}
とすることはできない。
2.キャプチャ箇所の取得方法
if ($line = /^\[(.*)\]/) { $captcha = $1 }
とすると簡潔になる。
それ以前はいったん置換してから、その結果を新しい変数に代入するという冗長なことをやっていた。
こんな風に。
if ($line =~ /^\[/) { $line = s/^\[(.*)\]/$1/; $captcha = $1; }
展開する方法
keys %{$hash_ref_with_hash->{$category}}
の部分がポイント。
見た目が仰々しい。
## プレーンハッシュのハッシュレファレンスを展開する for $category (keys %$hash_ref_with_hash) { for my $key (keys %{$hash_ref_with_hash->{$category}}) { print "[$category]\n"; print " $key $hash_ref_with_hash->{$category}{$key}\n"; } }
Googleフォームから自動返信メールを送る
サンプルコード
コードだけアップ。
メール本文のテンプレートをスプレッドシートから取得している。
スクリプトにハードコーディングすると変更しづらいのでこのほうがよいかと思う。
function sendMailFromForm(e) { // 件名、本文 var subject = "受付完了"; //スプレッドシートから返信メール本文を取得する var body = getBody(); Logger.log(body); // 列名の指定 var MAIL_COL_NAME = 'メールアドレス'; // メール送信者 var admin = "sample@hoge.com"; var cc = ""; // Cc: var bcc = admin; // Bcc: var reply = admin; // Reply-To: var to = ""; // To: // 送信先オプション var options = {}; if ( cc ) options.cc = cc; if ( bcc ) options.bcc = bcc; //bccで確認メールを自分宛に送ることが可能 if ( reply ) options.replyTo = reply; //返信先メールアドレス try{ // スプレッドシートの操作 var sh = SpreadsheetApp.getActiveSheet(); //スプレッドシートをshに入れる var new_row = sh.getLastRow(); //シートの最終行取得する var col = sh.getLastColumn(); //右端の列を取得する var r = sh.getDataRange(); // メール件名・本文作成と送信先メールアドレス取得 for (var j = 1; j <= col; j++ ) { var col_name = r.getCell(1, j).getValue(); // 列名を取得。列見出しは1行目にあるのでgetCell(行数、列数)の行数=1で固定 var cell_value = r.getCell(new_row, j).getValue(); // 一番下の行のセル値、すなわち最新のフォームデータの値を取得 if ( col_name === MAIL_COL_NAME ) { to = cell_value; } } // メール送信 MailApp.sendEmail(to, subject, body, options); } catch(e) { MailApp.sendEmail(admin, "Error:自動返信メール送信エラー(Googleフォーム): ", e.message); } } //「返信」シートA1セルの中身を取得する関数 function getBody() { var bk = SpreadsheetApp.getActiveSpreadsheet(); var sh = bk.getSheetByName("body"); if(sh != null) { return sh.getRange("A1").getValue(); } }
正規表現で名前付きキャプチャを使う
正規表現パターンがある程度ややこしくなると、後方参照でキャプチャの順番を書くのが大変になります。
そこで番号ではなくて名前でキャプチャを指定できるという名前付きキャプチャを試してみました。
Perlでは(?<name>)
とするとキャプチャに名前が付きます。
名前付きキャプチャで補足した結果は%+
というハッシュに記憶されるようです。
サンプル。
my $str = '0123 The message: "The weather today is great" is displayed.'; my $p = '\A(?<no>\d+).*?"(?<quoted>.*?)".*\Z'; $str =~ /$p/; # 名前付きキャプチャは$+{name}で参照できる my $index = $+{no}; # 0123 my $quoted = $+{quoted}; # The weather today is great
このときの%+
をダンプしてみるとこんなふうになってました。
use Data::printer; p %+;
普通のハッシュです。
Tie::Hash::NamedCapture
というものがあるようです。
{ no "0123", quoted "The weather today is great" } (tied to Tie::Hash::NamedCapture)
感想
キャプチャした箇所をハッシュにまとめられるので、管理しやすそうだと思いました。
grepを使ってリストにマッチする要素が含まれるかを判定する
ループの代わりにgrepすると簡潔に書ける。
人の名前がはいったリストがあるとして、その中にFredさんがいるかどうかを確かめたい。
forループならこう書ける。
for my $person ( @people ) { next unless $person =~ /Fred/; $flag = 1; last; }
grepを使うと1行だ。
my $ret = grep { /Fred/ } @people;
結果のプリントを含めたサンプルコード。
use strict; use warnings; use 5.0100001; my @people = qw/ Jeff Masato Koji /; ## forループを使った場合 my $flag; for my $person ( @people ) { next unless $person =~ /Fred/; $flag = 1; last; } if ($flag) { say $flag; } else { say "Fred doesn't seem to be here." } ## grep演算子を使った場合 my $ret = grep { /Fred/ } @people; if ($ret) { say "Hi, Fred."; } else { say "Fred isn't here."; }