実は greple で PDF ファイルを検索できる
cdif v2.6 --mecab オプション
cdif v2.5
昨年末に更新した cdif を修正しました。
未修正部分の着色に対応
自分では必要ないと思っているのですが、未修整部分の文字にも色を付けられるようにしました。
また --colormap というオプションで、それぞれのフィールドの色を指定できるようになっています。
デフォルトの設定はこう。
cdif --cm 'COMMAND=SE,OMARK=CS,NMARK=MS' \
--cm '*TEXT=,*CHANGE=BDw,DELETE=APPEND=RDw'
--commandcolor, --markcolor, --textcolor オプションで、フィールドに色を付けるかどうかを指示することもできます。
--cc, --mc, --tc とも省略可。
デフォルトでは --notc になっています。

あ、DELETE がなかった…
diff -t, -T に対応
その他、若干のバグ修正
ところで
今回、--colormap オプションをテーブルに反映する部分のコードはこんな風になっています。
if (@opt_colormap) { map { my $c = pop @$_; map { $colormap{$_} = $c } map { match_glob $_, keys %colormap } @$_; } map { [ split /=/, $_, -1 ] } map { split /,/ } @opt_colormap; }
自分で書いといて、なんだか Perl っぽくないコードだなあと時代を感じたりするわけですが、3行目だけ古風な感じなので List::Util の reduce を使って書き直そうかと思うと、これがどうもうまくいかないのですよ。
次のプログラムを実行すると、$hash{k1}, $hash{k2} に v が入ってほしいのだけど、2番目は undef になってしまいます。
my %hash; reduce { $hash{$b} = $a; $a; } qw(v k1 k2); print Dumper \%hash;
$a は一度代入すると値を失うようで、$a; だけの行を削除すると動くし(この行は本来不必要)、代入する部分を "$a" にしても大丈夫。
どうも perl5.12 に含まれる reduce のバグではないかと。
List::Util::PP を使えば動作するし。
なんとか使う方法はわかり、map と reduce だけのそれなりに趣のあるコードになりますが、動いたところで変な warning は出るし、無駄に可読性を下げるだけなので不採用でした。 参考のために一応見せるとこんなです。
use List::Util qw(reduce); no warnings; if (@opt_colormap) { map { reduce { map { $colormap{$_} = "$a" } match_glob $b, keys %colormap; } reverse @$_; } map { [ split /=/, $_, -1 ] } map { split /,/ } @opt_colormap; }
cdif アップデートしました
久しぶりに cdif コマンドを使おうとしたら $* なんか使ったらいかんよと動いてくれなかった。
なにしろ、作ったのは1992年で、最後に更新したのが2003年という骨董品のコマンドなので、perl4 の仕様で書いてある。
翻訳作業などでは、文章の変更点を確認したいことがよくある。
diff を使えば、行単位での違いを表示してくれるのだが、1行が長くてその一部だけが変更されているような場合には、変更点を探すのが大変だ。
cdif を使うと、行の中で異なる部分をハイライトしてくれる。
フィルタとして動作するので、diff の出力をパイプで流し込めばよくて、通常の形式以外に -c や -u 形式にも対応している。
元はと言えば、FrameMaker だったか何かで書かれた英文マニュアルの更新作業のためにやっつけで作ったものだ。
ちょうど、UCB の CSRG と 4.4BSD の開発をしている時で、Berkeley から Cupertino に帰るフリーウェイを走りながら実装方法を考えて、帰ってから深夜に作った記憶がある。
滅多に使うわけではないがないと困るので、まずは、今時、他にもっといいツールがあるんじゃないかと思って調べてみた。
wdiff
- https://www.gnu.org/software/wdiff/
- C
- 独自形式
- Unicode 対応してないっぽい
- ワードの区切りはスペースだけで、変更もできなさそう
- 所感
- スペース区切りだけでは日本語の文章に使うのは無理
- 出力が見にくい
jwdiff (追加)
- http://fumiyas.github.io/2013/12/12/jwdiff.sh-advent-calendar.html
- シェルスクリプト
- wdiff の区切りが使えないので、mecab で分かち書きするシェルスクリプトを作ってる人がいた。
- mecab でトークンに分けたものをタブでつなぎ、wdiff にかけた後でタブを削除するというもの
- 当然元の文章にタブが入っていると困るわけだが、特定用途には使えると思われる
- トークンを1行に変換し、diff した結果を反映するという方法も考えられるが、これはまさに cdif がやっていること
colordiff
- http://www.colordiff.org/
- Perl
- diff の出力に行単位で色を付けるだけ
- wdiff の出力も受け付けるが、やはりただ色を付けるだけ
- 特に Unicode 対応はしていないようだが、行指向なので普通に動作する
- 所感
- なんでこんなもののために独自ドメイン取ったんだろう?
diffc
- https://code.google.com/p/diffc/
- Python
- Unicode 対応なし
- 単語ではなく文字単位で処理するので出力が見にくい
- デフォルトの色使いは非常に見にくい
- 遅い
- 所感
- 労作ではあるのだが、どうも実用のために作ったのではない気がする
- Python だし、改造するつもりがあれば、わりと筋がいいかもしれない
- でも、ソースがチョー長くて読む気にならない
- 動作が遅いので、大量のデータを処理するのには向いていないかも
git-diff
- --color で色分けしてくれるが、これは行単位
- --word-diff で、wdiff 形式で出力
- --word-diff-color で変更部分を色分けして出力
- Unicode は扱えるが、単語の区切りは空白
- そのため日本語は全部1ワードとして扱われてしまう
- 機能的には総じてよくできているが、通常の diff 形式には対応していないし、フィルタとしても動作しないような気がする
- 多少手を入れればコマンド化は難しくないと思う
- wdiff 形式を diff 形式に変換することは簡単だが、それでは diff 形式の出力を処理することはできない
diff-highlight.pl
- https://github.com/git/git/tree/master/contrib/diff-highlight
- 2011年
- Perl
- git で使うために作られたもの
- フィルタとして機能する
- git diff --color の出力も処理できる
- コマンド自体は Unicode 未対応だが、perl -CSDA で実行すれば問題なく機能する
- コードは極めて短く、処理も単純
- 問題点
- diff -u 形式にしか対応していない
- 変更前後の行数が同じものだけを処理する
- プログラムの変更点を見るにはいいが、文章の変更部分をチェックするのには向いていない
- 文字単位で処理するため、単語の変更がわかりにくい (diffc と同様)
- アルゴリズムが単純なので、パターンによってはうまく処理できず、変更部分の抽出に失敗する
- 所感
- ちょっとしたコードの変更をわかりやすくすることができ、実装も簡潔かつ高速で優れた実装だが、文章の校正のような用途には向いていない。
- パラグラフが1文になっているワープロ原稿のようなもので、修正点が微細な場合には使えると思う。
Python 版 diff-highlight
diff-highlight の機能が意外にしょぼいと思った人は他にもいるようで、拡張してくれていた。
- https://pypi.python.org/pypi/diff-highlight/
- blog
- 2013年
- Python
- Unicode 対応
- diff -u 形式にのみ対応
- 所感
- 実は、cdif を更新してからこの記事に気がついた
- 文字単位の比較なので、文章に適用するのに最適ではない
- 最大の問題は出力の順序を変更してしまうことだが、後で元に戻すのは簡単にできるだろう
- 他の diff 形式に対応して、単語を認識するようにすれば汎用コマンドとして使える可能性あり
ediff.el
- emacs で動作する
- 日本語には対応している。
- ワードは句読点で区切られる感じなので、荒すぎる。
- 所感
- 昔見た時にはよくできていると関心したのだが、実用的に使ったことはなかった
- 改めて使ってみると、そんなには関心しない
- 単語の区切りが荒すぎるせいもあるのだろうが、どちらかというとウインドウのインタフェースが使いにくいのだと思う。何がいけないかを分析するためには時間が必要だが、その労力に見合うか不明。
cdif
というわけで、どうも自分の目的にちょうどいいコマンドが見当たらないので、cdif を更新することにしました。 作ったのはモノクロ時代で、アンダーラインと反転で表示するしかなかったが、近代的にカラー化することに。 基本的な機能は20年前と変わっていなくて、モダンなプログラミングスタイルに書き換えて Unicode 対応した以外は、主にコスメティックな修正です。
- https://github.com/kaz-utashiro/cdif
- 1992年
- Perl
- Unicode 対応
- ワードは alphanumeric、漢字、平仮名、片仮名の連続
- それ以外の文字は、同じ文字が連続する場合はワードとして処理
- ついでにハングルも入れてある
- ヨーロッパ系はよくわからないので未対応
- 教えてくれたら入れます
- 中国語は漢字の連続をワードで処理すると困るかも
- 文字単位で比較したい場合は
-Bオプションを使う - 色使いは OS X のターミナルでチューニングしてある
- 一応 --colormap というオプションで変更可能
- 仕様が固まっていないのでマニュアルには書いていません
- 変えたい人は、ソースを書き換えちゃった方が早いです
- そこそこ高速に動作
- テキスト全体に色を付けるのは、見た目は派手だけど実用的に必要かというとそうでもない気がしてやっていない
- ハイライトから抜ける時の処理が面倒なだけでもあったりはする
- 実装は手抜きで、バックエンドで diff コマンドを使っているので、多分 Unix 系 OS のみ対応

greple -Mdiff
さて、ついでというわけではないが、単に diff の出力に色を付けるだけなら greple のマクロだけでできちゃうぜということで、diff 用のモジュールを書いてみた。 いかが?
Greple command module for colorise diff output.
(2018-3-27 更新)
greple --word-diff-color 相当
git diff で、--word-diff-color を付けると wdiff のシンボルを削除して色づけして表示してくれるので、使い方によっては見やすくなる。
ちょっとした関数を定義すると greple でも同じことができる。
コードは短いがやっていることは、少々複雑である。
パラメータとして連想配列の要素が渡され、matched というキーを持つ値にマッチした範囲が入っている。
テキスト全体は $_ に入っているので、これを書き換えれば出力を修正することができるのだが、位置が変わってしまうと、それに合わせて範囲の指定も修正してあげないといけない。
先頭から書き換えると後ろが狂ってきちゃうので、配列の最後からテキストと範囲の両方を修正してあげればいいという寸法だ。
ちなみに matched の要素は開始位置、終了位置以外に、パターンのインデックスも付いているので注意。
出力は通常の機能で行うので --print print_wdiff に --continue オプションも付けて使います。
見た目を気にしなければ wdiff -t を使えばいいので、機能自体はご愛嬌だが、print 関数の例としてはなかなかよいのではないかと。
Greple モジュール対応
関連
モジュール
Greple は、~/.greplerc にオプションやプログラムを書くことができて、これはこれで便利なのだが、量が多くなってくるとごちゃごちゃしてわかりにくくなるのが問題だった。 また、使わない機能を毎回読み込むのも無駄なので、以前からモジュールを定義するようにしたいと思っていた。 やっとそれが実装できたのでリリースすることにする。
以下に Perl のスクリプトを検索するためのモジュールを付ける。
デモ用なので、あまり凝ったことはしていなくて、コメント、POD、それ以外というセクションを指定してパターンを探すことができる。
組み合わせることも可能で、--allsection というオプションを使うと、すべてのセクションが指定される。
それではすべてのパターンが表示されてしまって意味がないようだが、マルチカラー環境だとそれぞれが違う色で表示されるのだ。
この程度の機能はわざわざプログラムを作らなくてもオプションだけで実装できる。
オプションの指定は、モジュールの __DATA__ 以降に書くことができる。
形式は .greplerc と同じ。
参考のために、プログラム版とオプション版の両方を記述してあるが、オプションのみで実装するなら package 宣言だけあれば十分だ。
Greple module to handle perl script.
使う時には、次のように -M オプションでモジュールを指定する。
このオプションは特別で、必ず先頭で指定しなければならない。
複数指定することもできる。
greple -Mperl --code default greple
greple -Mperl --colorful --allsection default greple
実行結果はこんな感じ。

マルチカラーは結構便利で、~/.greplerc に次のような行を入れておくと常に有効になる。 ただ、OS X のターミナルは比較的目に優しい色で表示してくれるのでいいのだが、原色で表示されるエミュレータなどを使っている場合は、何か方法を考えた方がいいだろう。
option default --colorful
デフォルトオプションは、もちろんモジュールでも定義することができる。
--chdir や --glob と組み合わせると、ファイルを指定しないで使用することもできる。
昔は、RFC のインデックスを検索するのにこの機能を使っていたが、最近は読まなくなっちゃったし、自分しか持っていないデータでなければ検索エンジンを使った方が早い。
--inside, --outside, --include, --exclude
--inside/outside オプションの仕様が変更されている。
上の説明からもわかると思うが、複数の領域を指定した場合、いずれかの領域に含まれるマッチがすべて選択される。
以前はすべての条件を満たすマッチが選択されていたが、これは --include/exclude オプションで実装するようにした。
単独で使うのであれば、どちらでも同じである。
--inside/outside オプションで必要なマッチを選択し、--include/exclude オプションで不必要な部分を削除するという使い方を想定している。
ところで
--include/exclude は、grep との互換性のために使わずにおいたものだが、わかりやすいので使ってしまった。
他にもっといい考えがあれば、早めに教えて頂ければ今からでも変えちゃうかもしれない。
ちなみに mg は再帰的検索をサポートしていたが、greple では廃止してしまった。
実際に役立ったことはほとんどなかったからだ。
また greple は、テキスト検索に特化するように変更したので、いろいろなファイルが混在した環境では Encode モジュールが文句を言ったりしてイマイチ使いにくい。
どうしてもやりたいという方は、find の出力をパイプで渡して --readlist で読み込んでください。
Greple 仕様変更と多色化
関連
--cut やめて --need にしました
新しく --need というオプションを作って、マッチするパターンの数を指定できるようにしました。
そこで負の値を指定すると、今までの --cut と同じ意味になります。
--cut の方はわかりにくいので、まだ使っている人はいないだろうと思って削除しちゃいました。
ネガティブマッチを許す --allow の方はそのままで、やはり負の値を受け付けるようにしました。
greple --need=2 --allow=1 'foo bar baz -yabba -dabba -doo'
--print 関数の仕様変更
--print オプションで指定する関数の仕様を変えました。
今までは関数の返り値は見ていなかったのですが、今は返って来た値を出力するようになっています。 ですから、関数では出力したい文字列を返すようにします。 関数内で出力しちゃった場合には、空文字列を返してください。
また、--continue オプションを指定すると、返り値をそのまま出力せずに、通常の出力処理を続けるようになっています。
元の文字列を変更してしまうと、うまく出ないことがあるので注意して使いましょう。
多色化
--colormode で複数の色を指定できるようにしました。
そうすると、指定したキーワード毎に違う色を使うようになります。
また --colorful というオプションを追加して、これは --colormode 'RD GD BD CD MD YD' と同じ意味です。
このように出力されます。

こんな例だとあまり役に立つようには見えませんが、たとえばこんな風にするといくらか使い道はあるかもしれません。
greple --colorful --need 1 --icode=auto -e '\p{Han}+' -e '\p{InHiragana}+' -e '\p{InKatakana}+' -e '[a-zA-Z]+' -e '\d+' -e '[\pP\pS]+'

調子にのって --random というオプションも付けてみました。
ポップな感じですが、あまり意味はありません。
greple --all --colorful --random '\S+' greple

Greple で iCal データを検索する
追記: 申し訳ありませんが、
--chdir, --glob オプション
OS X のカレンダーコマンドは iCalendar という形式でデータを管理していて、ファイルは ~/Library/Calendars というディレクトリの下に保存されている。
たとえば、こんな風にすれば iCal のデータを検索することはできる。
% cd ~/Library/Calendars
% grep PATTERN *.caldav/*.calendar/Events/*.ics
Greple の --chdir と --glob オプションを使うと、同じことがこんな風にできる。
% greple --chdir ~/Library/Calendars --glob='*.caldav/*.calendar/Events/*.ics' PATTERN
複数のディレクトリを --chdir で指定することもできる。
その場合、それぞれのディレクトリに移動した後で --glob オプションを解釈する。
% greple --chdir '~/Library/Calendars/*.caldav/*.calendar/Events/' --glob '*.ics' PATTERN
憶えておくのは大変なので、alias を作ってもいいし、~/.greplerc ファイルに次のように書いておくと --ical_data という新しいオプションが有効になる。
% greple --ical_data PATTERN
これだと、マッチした行しか表示されないので日付とかがわからない。
iCal のデータは、1つのイベントを1つのファイルに保存しているので、ファイル全体を表示させるためには、--all というオプションを使う。
% greple --ical_data --all PATTERN
ここまでは grep の -r オプションを使っても、大体同じことができる。
マニュアルには書いてないが、
と思ったけど、-C オプションに -1 を指定するとファイル全体を表示するようだ。-C オプションに負の数を指定した場合の挙動は不可解だ。
% grep -rhC-1 --include '*.ics' PATTERN *.caldav
--print オプション
greple の --print オプションを使うと、出力用の関数を定義することができる。
もっとも簡単な使い方はこんな具合。
% greple --print='sub{$_}' PATTERN
値には Perl のサブルーチン名を指定するか、直接コーディングする。
マッチした領域は $_ 変数に入ってきて、関数の返り値が出力される。
iCal のデータを表示するためには、たとえば ~/.greplerc にこのような関数を定義する。
.greplrc definition to search iCal data and displa ...
こうして、次のように実行すると、iCal のデータを1行ずつ表示してくれる。 イベント名だけを検索したければ SUMMARY 行を指定する。 TODO などを除外して EVENT だけを検索したければ、VEVENT というフィールドも指定する。
% greple --ical PATTERN
% greple --ical '^SUMMARY.*PATTERN'
% greple --ical 'VEVENT ^SUMMARY.*PATTERN'
これだと、順番が出鱈目なので、日付順に表示したければ結果を sort する。
--of オプションで出力フィルタを指定することができるので、.greplerc の中に --of=sort を入れてしまっても構わない。
試しに、10年前の正月にどんな映画を観ていたかを検索してみるとこんな結果になった。
なるほど、とっとこハム太郎を観ていたのかw
% greple --ical '200401\d\d 映画' | sort
2003/01/02 14:00-15:00 映画:とっとこハム太郎 ハムハムハムージャ!幻のプリンセス
2003/01/02 15:00-16:30 映画:ゴジラ対メカゴジラ
2004/01/02 12:20-13:25 映画:劇場版 とっとこハム太郎ハムハムグランプリオーロラ谷の奇跡リボンちゃん危機一髪
2004/01/02 13:30-15:00 映画:ゴジラ×モスラ×メカゴジラ 東京SOS
2004/01/08 19:30-21:30 映画:ミシェル・ヴァイヨン@池袋HUMAX
2004/01/13 20:10-22:00 映画:ブルース・オールマイティ@サンシャイン
2004/01/16 20:40-22:55 映画:半落ち@サンシャイン
2004/01/21 22:00-00:00 映画:リクルート
2004/01/24 21:30-23:30 映画:タイムライン
2004/01/25 21:00-23:00 映画:首都消失@TV
2004/01/29 20:30-23:00 映画:ミスティック・リバー@新宿ジョイシネマ
おや、2003年のデータが混じってるな?と思ったら、こうやって中身を見ることができる。
% greple --ical_data --all ハムハムハムージャ!
すると、DTSTAMP というフィールドが2004年になっていることがわかった。 RFC2445 によれば、これはオブジェクトが作成された日付ということだ。
厳密に書くとすればこんな具合だろうか。 最初からきっちり書くのではなく、ゆるく初めて必要に応じて絞り込むような使い方がお勧めである。
% greple --ical '^BEGIN:VEVENT ^DTSTART.*200401\d\d ^SUMMARY.*映画' | sort

