Getopt-EX v1.1.1 リリースしました

以前、こんな記事を書いた。

utashiro.hatenablog.com

結局2年半も経ってしまったが、やっと独立したリポジトリにしてみた。

github.com

そして、Minilla を使ってパッケージングして CPAN に公開。

metacpan.org

というわけで、遅ればせながらの CPAN デビュー。

電動歯ブラシホルダーの試作

もう12月も半ばを過ぎ、このままでは何も書かずに今年が終わってしまう。書くことがないわけではないが、最近は Facebook でなんとなく足りているので、わざわざブログを書く気にならないのが実情だ。目先を変えて、iOS アプリで書いてみる。

 

次男はどういうわけか電動歯ブラシを愛用している。以前、海外で買って来たものが気に入っているようだ。他の家族の歯ブラシは鏡に吸盤で貼り付けたホルダーに収まっているが、次男の電動歯ブラシは洗面台の上に立ててある。不安定なので、ちょっとした拍子に手が触れて倒れたり、落ちて洗濯機の下に転がってしまうこともある。

以前からなんとかしたいと思っていたところ、頼んだカーテンが頑丈なボール紙の芯に巻いて配達されたので、それを使ってホルダーを作ってみた。まず、12cmほどの長さに切る。底は密閉しない方がいいだろうと考え、4箇所に穴を開けて、タイラップを十字型に通した。ほどほどのクッションもあるので、置いた時のショックを和らげてくれる。

後は百均で買って来た吸盤をタイラップで留めて完成。洗面台の鏡に貼るとなかなかいい具合だ。そのままだと汚い洗面所が写ってしまうので、写真はバスルームの壁に貼ったもの。

f:id:uta46:20161216104234j:image

ボール紙のままだとみすぼらしいので、黒の艶消しの養生テープを巻いてある。これは普段から愛用している「影武者」というカッコいい名前の優れものだ。このテープは、ホントにすごい。マットブラックの質感が素晴らしく、貼るだけで何でも高級な印象になる。養生テープ特有の模様も、なんとなくカーボンぽく見えるから不思議だ。ただし、値段も養生テープのそれではなく700円くらいするのだが、十分その価値はある。

 ちなみに白もある。名前は「影武者 白」だ。しかし、こちらは黒ほど感動的ではない。白影という名前にすればいいのにとも思う。

 iOS アプリはあまり期待していなかったが、意外に調子いい。使ってみることにしよう。

Garmin 920XTJ は中高年に優しくていいやつ

自転車で坂道を登っている途中で GPS ウォッチの AUTO PAUSE が働くと心が折れそうになるのだが、 最近 20km/h で走行しているにもかかわらず Garmin 910XT が AUTO PAUSE するようになってきた。 それは、そろそろ買い換えろというメッセージかと思っていたら、920XTJ が発売するというのでさっそく予約注文。

昨年末にハワイに行った時に買おうとも思って結局買えなかったのだが、 スマホと連携してイベントが表示されたりするので、日本語対応を待ってよかったような気がする。

さて、まだランで使用しただけですが、910XT で不満だった点がほぼ解消されていて、なかなかいい製品ではないかと思います。 特に老眼で画面の文字が読みにくなった中高年に優しい仕様になっていると思う。 液晶がカラーになったところが一番の違いかと思うかもしれないが、実はカラー化の恩恵は今のところあまり感じられない。 逆に言えば、表示系でうれしい改善点は 910 でも最初からやっとけよというような部分ばかりだ。 以下そんなところを中心にした 920XTJ インプレ、ラン編。

ラップ表示がでかい

ランでは 1km 毎にラップタイムを取るようにしている。 この時、振動と共にそのラップの記録を表示してくれるのだが、910XT はこの表示が小さくて見にくかった。 その前には 610 を使っていたのだが、字が小さくて見にくいから 910 に替えて、 せっかくでかい字で表示するようにしているのに、 ラップ表示がそれより小さくて見えないのはどういうことよと不満だった。 その点、920 はちゃんと画面全体を使って表示してくれるて、 同時にラップ数やトータルタイムも、認識可能な大きさで表示してくれる。

START / STOP が確認しやすい

ランの場合 AUTO PAUSE 機能は使っていないので、信号待ちや休憩の時にはマニュアルでストップするようにしている。 その時 910 は Timer Stopped, Timer Started と、やはり小さい字で表示するだけだった。 手元の距離だと文字まで認識しずらいので、p のでっぱりと全体の長さでどちらかを判断しなければならない。 その点、920 は赤い四角と青い三角でわかりやすく表示してくれる。

タイマーの状態がわかりやすい

上と似ているが、910 はタイマーが動いているかどうか画面を一見しただけではわからない。 秒単位で時計が進んでいれば少し見ていればわかるが、いつもそんな画面だとは限らない。 どうすれば確実にわかるかというと、メニューを表示させると一番下にちっちゃーく時計のマークが表示される。 それがなければストップしているという極めて優しさに欠ける仕様だった。 920 はストップすると Resume ボタンが表示され、明らかに止まっていることがわかる。

START / STOP ボタンが押しやすい

920 の START / STOP ボタンは、手前側面に配置されている。 こうすると、反対側を人差し指で押さえて全体をつまむような感じで押すことができる。 910 のボタンは上を向いて付いていて、上から人差し指で押すような形になるが、920 の方が使いやすい。

有効な画面だけ表示してくれる

最初は心拍計なし、2回目はありで使ってみたところ、2回目には心拍数を表示する画面が出てくるようになった。 詳しくは見ていないが、センサーの有無によって表示する画面を切り替えてくれるようだ。 これは、なかなかよい。

追記:心拍センサーなしでもう一度走ってみると、心拍画面はやはり表示された。 センサーとペアリングしてあると画面が有効になるのかもしれない。 あるいは、最初出ていなかったのは気のせいか。

電源を切らなくてもいい

910XT は、電源を切らずに放置するとすぐにバッテリーがなくなってしまうので、使用後には必ず電源をオフにするのが習慣だった。 パワーセーブモードで4週間持つ 920XTJ は時計としても使えて地方に行く時などに便利なのだが、 実は電源を切らないでもいいという点だけでも随分と使い勝手が向上している。 持っている人ならわかると思うが、910XT は電源をオフにしてからしばらく画面の表示が消えず、 その間に充電ケーブルをつなぐと変なことになることが多かった。 だから、しばらくまってからつながなければならず、毎回それをやるのはちょっとしたストレスなのである。

自動パワーセーブ

さらに 920XTJ は、少し使わないでいるとすぐにパワーセーブモードに移行してくれる。 910XT でもバッテリーは20時間持つのだから、ランの練習くらいだったら毎回充電する必要はないのだが、 トラブルが怖いので使い終わったら充電するのを習慣にした方が無難だ。 地方に行った時など、荷物の中でいつの間にか電源がオンになっていて、着いたらバッテリーが空なんてこともあった。 その点、920XTJ は安心して使える。

GPS の捕捉が早い

自動パワーセーブの存在をさらに有効にしているのが GPS の捕捉の早さだ。 920XTJ は、計測途中でもちょっとストップしている時間が長いと、すぐにパワーセーブに移行しようとする。 30秒前から警告が出て、20秒前、10秒前に振動で教えてくれる。 移行して欲しくなければ、この間に操作すればよい。 しかし、移行してしまったとしても、回復は瞬時に行えて、しかも GPS をすぐに捕捉してくれるので、回復のストレスはまったく感じない。 910XT の場合、1分以上も待たなければならいこともあって、このような使い方はできなかった。

屋内でうるさくない(はず)

910XT と供にバイクに乗っている時、休憩で屋内に入ったりすると GPS が見えなくなったというアラートが出て、これが案外煩わしい。 こんな時は電源をオフにしてしまうのがバッテリーのためにもいいのだが、再開する時に GPS を捕捉するのを待たなければならない。 920XTJ ならパワーセーブに移行するので、うるさく言わないはずだ。

ランのピッチを計測してくれる

920XTJ は、ストライドセンサーを装着していなくてもランのピッチを測定してくれる。 910 でもスイムの泳法を検知してくれるくらいなので、ピッチくらい簡単に計測できるだろうにと思っていたが、 920 は電子コンパスが付いたせいなのかちゃんと計測してくれるようになった。 フットポッドも持っているが、練習用とレース用の靴が違うと、なんとなくどちらかにつけっぱなしになってしまう。

VO2 MAX は未知数だが、あるとなんとなくうれしい

練習で10km走った後の VO2 MAX の数値は46だった。 週末は、東京マラソンに外れたので、さいたまシティハーフを走ってきた。 18kmまでは平均心拍150程度でキロ5分ペース、最後の3kmは徐々にペースを上げて最終ラップは4分切りで心拍も180近く。 レース後、VO2 MAX の数字は 48 と表示されている。 Garmin のマニュアルによれば、50-59歳で 48 というのは、Excellent の一番上くらいに相当する。 そんなにいいわけないので、ちょっと眉唾な感じ。

f:id:uta46:20150223095151p:plain

でもそれから計算する予想タイムは、ハーフ 1:39、フル 3:27 と現実的な数字なので、そんなに出鱈目でもなさそうだ。 50歳台男子のエントリー数は1800人で、順位は355位だったので上位20%には入っているということか。 最初からペースアップすればもう2〜3分は短縮できたと見込めるし、スタートラインまでのタイムロスを考えると、 10%以内くらいならそんなにはずれてないということか。 案外活用できる数値かもしれない。

ちなみに、VO2 MAX は東京体育館で1650円で本格的に測定してもらえるが、大人気で予約開始時刻に電話をしても全然つながらないらしい。

ちなみにセンサーは、ANT+ でよければ Adidas micoach が安くていい。 3年前に10,850円で買った時も安いと思ったのに、今ならストライドセンサーと心拍センサー込みでたったの6000円だ。 実際使っているが micoach 本体は、どこに行ったかわからないw。 ついでに忠告すると、micoach のアプリは行儀が悪いので、使うつもりがなければインストールしない方がいい。

greple のリファクタリングと Getopt::EX

greple 改修中

そろそろ Perl 以外のプログラムにも手を出したくなってきているのだけど、そのためにはやり残したことをやっちゃってからにするかと、いろいろ棚卸し中なのである。 しかし、作りかけだったり未リリースだったりするものが多くて、なかなか片付かないという大掃除的状況が続いています。

というわけで、正月から greple を大改修中。 機能的には大きな変更はないのだが、シングルファイルで管理する限界を感じて、全体的に構成を見直してモジュール化を進めている。 やり始めてから意識したのだが、このような作業を巷ではリファクタリングと呼ぶらしい。 自分で書いたコードなので、修正した場合の影響範囲はわかっているつもりだが、案外予想しないところに影響が現れたりして、テスト環境が整っていないので確認が大変だ。 本格的な作業は十分なテストを伴わなければならないので、今やっているのは、なんちゃってリファクタリングだ。

作業中のファイルはまだ master には反映していなくて develop というブランチを切ってある。

Getopt::EX

さて、その作業の過程で greple で使っている rc ファイルと拡張モジュールの処理を独立したモジュールにしてみた。 とりあえず Getopt::EX という名前にしてある。

使い方は簡単で、Getopt::Long を Getopt::EX::Long に変えるだけだ。 すでに cdifsdif はこれに対応するようになっている。 もっとも、インストールしてない環境で使えないのは困るので、以下のようにして Getopt::Long にフォールバックするようになっている。 いずれ本格的に対応するつもりだが、現時点での修正はこれだけ。 もちろん OO 的インタフェースでも利用できる(はず...)。

eval {
    require Getopt::EX::Long;
    import  Getopt::EX::Long;
    1;
} or do {
    die if $! !~ /^No such file/;
    require Getopt::Long;
    import  Getopt::Long;
};

さて、これだけの修正で以下の2つの機能が使えるようになる。

  1. 自動的に rc ファイルを読み込むようになる。
  2. -M オプションで、拡張モジュールを読み込むようになる。

RC ファイル

たとえば sdif であれば、~/.sdifrc というファイルを読み込むようになる。 もちろん別のファイルを指定することもできるが、それにはプログラムに若干の修正が必要になる。

このファイルの中で、オプションを自由に定義することができる。 sdif / cdif は、デフォルトでは明るい色彩の端末で使用することを前提としているのだが、暗いバックグラウンド色を好む人のために --DARK_CMY というオプションを定義してみよう。

option  --DARK_CMY \
        --cm OCOMMAND=455/011;E         \
        --cm NCOMMAND=545/101;E         \
        --cm    OFILE=455/011;DE        \
        --cm    NFILE=545/101;DE        \
        --cm    OMARK=C/444             \
        --cm    NMARK=M/444             \
        --cm    UMARK                   \
        --cm    *LINE=Y                 \
        --cm [ON]TEXT=555/111;E         \
        --cm    UTEXT=                  \
        --cdifopts '--cm APPEND=DELETE=K/545,*CHANGE=K/455'

こうすることで、

% cdif --DARK_CMY

のように実行できるようになる。

これを常に付けて実行したければ default という名前でオプションを定義すればよい。 .sdifrc の中に次の行を追加するだけだ。

option default --DARK_CMY

シェルのエイリアスを使うのと決定的に違うのは、コマンドそのものが新しいオプションを受け入れるようになるという点である。 だから、別のコマンドから呼び出したり git 環境で使うような場合にも常に有効になる。

モジュール

先頭に -M ではじまるオプションがあると、それをモジュールの指定として取り扱う。

モジュールは App::sdif にあると仮定するので、たとえば colors というモジュールを指定すると App::sdif::colors という Perl モジュールを読み込む。

これは Perl のモジュールだが、パッケージ宣言だけあれば中身はなくてもいい。そして __DATA__ セクションに書いてある内容を RC ファイルと同じように解釈する。

package App::sdif::colors;
1;
__DATA__
define :CDIF      APPEND=DELETE=K/545,*CHANGE=K/455
define :DARK_CDIF APPEND=DELETE=555/311,*CHANGE=555/113
option  --green \
        --cm *COMMAND=010/555;SE        \
        --cm    *FILE=010/555;SED       \
        --cm [ON]MARK=010/444           \
        --cm    UMARK=                  \
        --cm    *LINE=220               \
        --cm [ON]TEXT=K/454;E           \
        --cm    UTEXT=                  \
        --cdifopts '--cm :CDIF'

この例だと --green というオプションが有効になる。このようにマクロを使用することもできる。

端末の背景色によってオプションを変えてみる

オプションが定義できるだけでも結構便利なのだが、スクリプトを活用すると動的にオプションを変更することもできる。 実行された時刻によってパラメータを変えるのなんかはオチャノコだ。 試しに、使用している端末の背景色によってオプションを変更してみよう。

App::sdif::osx_autocolor というモジュールを次のように定義する。

package App::sdif::osx_autocolor;

use strict;
use warnings;

sub brightness {
    my $app = "Terminal";
    my $do = "background color of first window";
    my $bg = qx{osascript -e \'tell application \"$app\" to $do\'};
    my($r, $g, $b) = $bg =~ /(\d+)/g;
    int(($r * 30 + $g * 59 + $b * 11) / 65535); # 0 .. 100
}

sub initialize {
    my $bucket = shift;
    $bucket->setopt(
        default =>
        brightness > 50 ? '--LIGHT_SCREEN' : '--DARK_SCREEN');
}

1;

細かい使い方はドキュメントを読んでほしいが、initialize というサブルーチンが用意されていればそれが実行され、引数としてそのファイルを処理するためのオブジェクトが渡される。そのオブジェクトを通して、RC ファイルで書けることは何でも指定できるし、スクリプトでできることはなんでもできる。

ここでは、AppleScript を使って OS X の Terminal アプリから背景色を取得し、輝度を計算して 0 から 100 の値を得る。 それが 50 より大きければ --LIGHT_SCREEN、小さければ --DARK_SCREEN というオプションをデフォルトとしている。 これらのオプションはこのモジュールの中では定義されていなくて、ユーザが .sdifrc などで設定することを想定している。 たとえば、こんな具合だ:

# ~/.sdifrc
option default -Mcolors -Mosx_autocolor --onword
option --LIGHT_SCREEN --cmy
option --DARK_SCREEN  --dark_cmy

僕の ~/.gitconfig は

[pager]
    log = less
    show = sdif -n --cdif | less -cR
    diff = sdif -n --cdif | less -cR

のように設定してあるので、こうすることで git diff などの出力が次のように変わる。

デフォルトカラー

f:id:uta46:20150202141635p:plain

明るい端末では --cmy オプションを指定

f:id:uta46:20150202142010p:plain

暗い端末では --dark_cmy を指定

f:id:uta46:20150202141645p:plain

明るい端末で --mono オプションを指定

f:id:uta46:20150202163021p:plain

暗い端末で --dark_mono オプションを指定

f:id:uta46:20150202165339p:plain

--dark_mono が意外とクールだ。 これでもちゃんと修正箇所はわかる。 実は Getopt::EX の方では 24 段階のグレイスケールが使えるので、もっと微妙な設定が可能なのだが、sdif がまだそれに対応していない。

とまあ、既存の Perl スクリプトを1行変更するだけでこんな感じのことができるようになるので、結構イカしてるんじゃないかと思うのだけど、いかがでしょう? 興味のある人は使ってご意見など頂けると嬉しい。手伝ってもらえるともっと嬉しい。 今の所 grepledevelp ブランチに入っているので、clone して PERLLIB に入れてもらうのがよいかと思います。

今作った App::sdif::colorsgithub にあげておこう。

他にもいろいろできる

Getopt::EX::Colormap

Getopt::EX には Getopt::EX::Colormap というモジュールが含まれていて、上で書いたような色を指定するオプションの処理や、実際に色を付けて出力する部分を実装している。 cdif / sdif は、まだこれに対応していないがいずれするつもりだ。 一点だけ、小文字の色指定の解釈の仕方が変更されている。 今までは rgbcmy を小文字で指定すると背景色の意味になったのだが、新しいモジュールでは ANSI 16色の後半8色を表すようになっている。 背景色を指定したい場合には / の後に書く。 これから使う人は、当面小文字を使わないことをお勧めします。

これは、別モジュールにしてもよさそうなものなのだが、この後の Getopt::EX::Func も絡んできて、なかなか切り離せない関係にある。

Getopt::EX::Func

オプションを定義するだけだと、本来持っている機能を組み合わせるだけなのだけど、モジュールで定義した機能をオプションから呼び出すことができる仕掛けが入っている。 これを活用すると、スクリプト本来の機能を拡張することができる。

まあ、これは使ってみないとわからないだろう。greple の改修作業の過程で、PGP の機能を拡張モジュールに切り出した。 このモジュールによって、スクリプト本体には何も加えずに、PGP で暗号化されたファイルが検索できるようになる。 面倒な作業は App::Greple::PgpDecryptor というモジュールがやってるんだけど、インタフェースの拡張はこれだけだ。 .gpg というサフィックスを持つファイルに対して、ここで定義している App::Greple::pgp::filter という関数を入力フィルタとして挿入するオプションを定義している。 フィルタが呼び出されると、処理するファイルは STDIN に開かれているので、fork して適切なコマンドを実行するという寸法だ。

package App::Greple::pgp;
use App::Greple::PgpDecryptor;
my  $pgp;
our $opt_pgppass;
sub activate {
    ($pgp = new App::Greple::PgpDecryptor)
        ->initialize({passphrase => $opt_pgppass});
}
sub filter {
    activate if not defined $pgp;
    $pgp->reset;
    my $pid = open(STDIN, '-|') // croak "process fork failed";
    if ($pid == 0) {
        exec $pgp->decrypt_command or die $!;
    }
    $pid;
}
1;
__DATA__
option default --if s/\\.(pgp|gpg|asc)$//:&App::Greple::pgp::filter
builtin pgppass=s $opt_pgppass // pgp passphrase

パッケージングできなくてリリースできません...

今までは、インストール作業の煩雑さが嫌いで、ファイルをコピーすれば動くようなものしか作ってこなかった。 現在の greple はモジュールに分割されてはいるが、clone するなり tz を展開するなりして、実行形式を直接指定するかシンボリックリンクを張れば実行できるようになっている。こんな風に書いてあるからなんだけどね。

BEGIN {
    if ((my $lib = abs_path($0)) =~ s{/ \K (?:bin/)? \w+ $}{lib}x) {
        push @INC, $lib if -d "$lib/App/Greple";
    }
}

とはいえ、ちゃんとしたインストーラーとテスト環境を作らないといけないなあとは思うのだけど、どうも今時の開発手法が使えませんよ。 Minilla とか Milla とか使おうとしてみたんだけど、Yosemite にうまくインストールできない。 どうしたらいいんでしょう。 CPAN も使ったことなかったので、とりあえず PAUSE のアカウントだけは作った (イマココ)。

cdif in Emacs

Emacs のバッファに cdif を実行するための関数を作りました。 cdif のリポジトリcdif.el として置いてあるのでどうぞ。

これは、今作業中の翻訳原稿。 以前の版から類似の文章を選択して、原文の diff と、古い訳文を挿入してある。 このバッファに対して cdif-buffer を実行すると、diff 出力部分を cdif コマンドで処理して、結果に ansi-color を使って着色します。

f:id:uta46:20140403170919p:plain

スコアは100点満点で、文章を単語に分解して、すべての単語が同じ場所にあれば100点。 同じ単語があって、順番が違うと、その差の分だけ減点するというアルゴリズムです。 実は、これは利き酒と同じ方法。

Emacs lisp なんて書くのは10年以上ぶり。 コマンドの実行に失敗した時の処理がわかりません。 誰か教えて_o_

Emacs のバッファに対して cdif を実行する関数。

Tea is ready - Command line kitchen timer for OS X Mavericks

OS X のターミナルからコマンドラインで使うタイマーコマンドを作ってみました。 ウィジェットiPhone アプリなど探せばいくらでもあるのでしょうが、たとえばカップ麺を作る時にお湯を入れてから探して起動してセットしたりしていると、それだけで1分近く経っちゃうんですね。

https://github.com/kaz-utashiro/tir

This is the command line kitchen timer for Apple OS X Mavericks. It will show the message on terminal, notification center, and read it out using audible speech. Default message is "Tea is ready" and the word "tea" is replaced by the command argument. If the first argument is number, command wait that time in second. Playing iTunes is paused automatically during speech.

Mavericks では AppleScript から通知センターの機能が利用できるようになったらしいのでそれを使って。 名付けて TIR (Tea Is Ready)。 これなら秒速で実行可能です。 英国文化圏では、お茶はもちろん、ご飯の準備ができたときにも "Tea is ready" と言うのだそうです。

最初は alias だったのが、徐々に複雑になってきたので sh になって、それも破綻してきたので、生まれて初めての bash スクリプトになりました。 というかシェルスクリプトで関数を使ったのが初めてで、関数がコマンドのように振る舞うのも知らなかった。

使い方は簡単で

tir

と実行すると、Tea is ready と表示すると共に、通知センターにメッセージを表示し、ついでに say(1) コマンドを使って内容を読み上げます。

tir 180

とすると180秒待ってからメッセージを表示します。

tir 3m Your ramen

とすると、3分経ってから Your ramen is ready と言ってくれます。

デフォルトの声が気に入らない場合には

tir -v Hysterical

のように声を指定することができて、

tir -r 5min Noodle

と -r オプションを指定すると、毎回ランダムに声を選びます。 スピーカーのボリュームが小さくなっているかもしれない場合は、

tir -V100

のように -V オプションで一時的にボリュームを上げることもできます。 システムの音量を変更するので音楽を再生しているような時には突然大きな音になってしまうので注意が必要です。 iTunes については再生中であれば一時的にポーズするようにしてありますが、それ以外のアプリのことはわかりません。

いつもターミナルを開いている人にだったら結構使えるんじゃないでしょうか。

ロングバージョン

さて、-v オプションで選べる声中には結構面白いものも入っていて、選択リストは

say -v ?

と実行すると見ることができます。

この中の Good News という声を使うとなかなかゴージャスな感じになることを発見しました。 ラーメンが出来上がったことを威風堂々のメロディにのせて歌い上げてくれるので、モチベーションも高まるのではないでしょうか。 こんな風に実行してみます。

tir -w 3min -v good -m 'ramen is ready ee ramen is ready, your ramen is ready ramen is ready'

結果はこうなりますw。 画像は手持ちの写真をテキトーに並べてみました。


Ramen is ready - YouTube

参考

通知センターを利用するのはこの記事が詳しい。

上の記事でも紹介されていますが、コマンドラインから利用するにはこんなツールがある。 大げさなので使ってませんが。

cdif / sdif / watchdiff now runs on Linux

OS X でしか使っていなかったのですが、Linux で実行してみたら使えなかったので修正しました。

  • 端末の幅を取得するのに stty を使っていたが、仕様の違いがあるので tput を使うように変更
  • Text::Glob は標準モジュールだと思っていたが違ったので使わないように変更

また、一時ファイルの取り扱いが変わっています。

  • 従来は /tmp にファイルを作成して使用後に削除していた
  • それを IO::File::new_tmpfile を使うように変更
    • これだとファイルをオープンすると同時に unlink するので削除する必要がない
    • そのため異常終了してもファイルが残らない
  • diff コマンドに与えるパスは /dev/fd/ を使うように変更

追記

会社の FreeBSD で試してみると /dev/fd/3, /dev/fd/4 が開けないといいますね。 CLOSE ON EXEC の解除ができていないのでは疑ったのですが、そうではなくてファイルシステムの問題のようです。 マニュアルを見ると devfs(5) は 0, 1, 2 しか提供しないので、fdescfs(5) を使えと書いてあります。 こういう場合は設定を変えてくださいという方針でいいんでしょうかね。

追記2

sdif, cdif, watchdiff を統合して sdif-tools というリポジトリで管理するようになっています。

CPAN にもリリースしたので、cpanminus でインストールできます。

$ cpanm App::sdif