おわび
最初に投稿した時点で App::Greple::msdoc の最新版をリリースしてませんでした。1.00 だと textconv を使った git diff に失敗します。perldoc App::Greple::msdoc して、バージョンが 1.00 だったら、最新版を入れなおしてください。
MS のドキュメントを端末で操作する
マイクロソフトという会社は別に嫌いではないが、ソフトウェアさえ作ってくれなければいいのになあと思う。そうは言っても Word の資料を扱わなければならないことはあり、読めと言われれば仕方がないので嫌でもビューアーとかアプリを立ち上げるわけだが、古い資料を検索したり、バージョンの違うドキュメントを比較したりしようとした途端に息が詰まる。これをなんとかしようという話。
grep する
正確には grep ではなく greple 用のApp::Greple::msdoc というモジュールを作った。greple モジュールは -M オプションで指定する。
$ greple -Mmsdoc 経費 kisairei.docx
複数の検索ワードを指定できるので「経費」と「換算」という単語を含む行を探したければ、こんな風に使う。
$ greple -Mmsdoc '経費 換算' kisairei.docx
ちなみに、こうしても同じだ。
$ greple -Mmsdoc -e 経費 -e 換算 kisairei.docx
-p オプションをつけるとパラグラフ単位で表示する。word のドキュメントは1パラグラフが1行なので、内容はほとんど変わらないが、切れ目はわかりやすくなる。
特定の単語を含まない行を見たければ、こんな風にすると、上の例であれば最初のパラグラフのみが出力される。
$ greple -Mmsdoc '経費 換算 -物品調達' kisairei.docx
$ greple -Mmsdoc -e 経費 -e 換算 -v 物品調達 kisairei.docx
PowerPoint の場合はページの切れ目に空行が入るので、パラグラフ単位で検索すると、大体ページ単位の出力が得られる。
cat する
さて、検索はできるようになった。ファイル全体を表示させることも、たとえばこんな風にすればできる。
$ greple -Mmsdoc '\A' --all kisairei.docx
\A は、データの先頭なので必ずマッチする。--all を指定するとデータ全体をブロックとして扱うので、結果的にファイルの内容が全部表示される。細かい説明は省くが、App::Greple::msdoc モジュールには、これと同じような動作をする --dump というオプションを入れてある。テキスト部分だけを抜き出しているので、フォーマットは何もなしだが、大体の意味はわかる。このように、word の場合は、パラグラフの間に空行が入る。
less する
もちろん --dump の結果をパイプで送れば less で見ることはできるが、LESSOPEN という環境変数を使ってこんな風にしてもいい。
$ LESSOPEN="| greple -Mmsdoc --dump %s" less kisairei.docx
しかし、いつも設定しておくわけにもいかない。ということで、optex を使うことにする。~/.optex.d/less.rc というファイルに、こんな設定をする。
option --ms -Mutil::setenv(LESSOPEN="| greple -Mmsdoc --dump %s")
option --msx -Mutil::setenv(LESSOPEN="| greple -Mmsdoc --indent --dump %s")
これで --ms と --msx というオプションが使えるようになり、こんな風に実行できる。
$ optex less --ms kisairei.docx
ちなみに --msx というオプションを指定すると greple -Mmsdoc に --indent オプションをつけて実行して、XML をインデントして表示する。
$ optex --ln less
を実行すると、~/.optex.d/bin の下に less → optex のシンボリックリンクを作成する。ここを PATH に入れて less を実行すると、自動的に optex 経由で実行される。なので less コマンドに、--ms と --msx という新たなオプションが追加されたように見える。
$ less --ms kisairei.docx
もちろん、alias しても ok。
ホントに grep する
greple や less には、入力を制御する仕組みがあるので、上のような対応ができる。しかし普通のコマンドにはそんなインタフェースは用意されていない。
でも、bash の process substitution を使えば、シェルスクリプトとか作らなくてもなんとかなる。
$ grep 経費 <(greple -Mmsdoc --dump kisairei.docx)
しかし、いちいち入力するのは面倒なので、ファイル名を見て、自動的にデータを変換してくれるとうれしい。
というわけで、これと同等の動きをする optex のモジュールを作った。App::optex::msdoc を使うと、ファイル名を見て、それを変換したデータを読むためのパスにコマンド引数を差し替える。
たとえば ~/.optex.d/default.rc に、次のような設定をする。
option --msdoc -Mmsdoc $<move>
こうすると、optex 経由で実行する、すべてのコマンドで --msdoc というオプションが使えるようになる。ほら、grep だってできた。
$ optex grep --msdoc 経費 kisairei.docx
diff する
App::optex::msdoc を使った場合、引数で指定したファイルをすべて変換した後にコマンドが実行される。だから多くのファイルを同時に指定して grep したり less したりする用途には向かない。その点 diff で指定するファイルは2つに決まっている。
実際のところ optex::msdoc モジュールは、ファイル名を見て必要な時にのみ処理をするので、不要な場合に使ってもオーバーヘッドはほとんどない。diff コマンドで常に有効にしたければ、~/.optex.d/diff.rc に default を設定する。
option default --msdoc
こうすれば、
$ optex diff kisairei-H25.docx kisairei-H26.docx
で word ファイルをテキストベースで比較することができる。下は、この出力を cdif に通した結果だ。cdif は --mecab オプション付きで実行されているので、mecab が分割した単位で色付けされている。
これで、さらに diff → optex のシンボリックリンクをパスに入れてあれば、単に diff するだけでいいのだが、間接的に実行される diff コマンドがすべて置き換えられてしまうことになるので、結果は変わらないが速度的に塩梅が悪いことがある。というか、この例の cdif がまさに影響を受ける。使いたければ alias などを使って会話的シェルでのみで有効にした方がいいだろう。
git でも diff する
git diff を実行できるようにするためには、~/.config/git/attributes ファイルに以下のような設定をして、ファイル拡張子に対する属性を定義する。
*.docx diff=msdoc
*.pptx diff=msdoc
*.xlsx diff=msdoc
(1) textconv を使う
git の設定で、textconv を指定する。
$ git config --global diff.msdoc.textconv "greple -Mmsdoc --dump"
こうすると ~/.gitconfig に次のような設定が入る。手で編集してもいい。
[diff "msdoc"]
textconv = greple -Mmsdoc --dump
この状態で git diff を実行すると、ここで指定したフィルターを通した結果を比較する。自分の場合、.gitconfig をこう設定してあるので、出力は sdif を通して表示される。
[pager]
log = sdif -n | less -cR
show = sdif -n | less -cR
diff = sdif -n | less -cR
(2) external diff command を使う
git diff するには、textconv ではなく、command を設定する方法もある。
$ git config --global diff.msdoc.command "optex --exit 0 diff --msdoc -u --git-external-diff"
~/.gitconfig の内容はこうなる。
[diff "msdoc"]
command = optex --exit 0 diff --msdoc -u --git-external-diff
--git-external-diff というオプションは、App::optex::msdoc で設定されている。
##
## GIT_EXTERNAL_DIFF is called with 7 parameters:
## path old-file old-hex old-mode new-file new-hex new-mode
## 0 1 2 3 4 5 6
##
option --git-external-diff $<copy(1,1)> $<copy(4,1)> $<remove>
コメントにあるように、git の外部コマンドは7つのパラメータと共に実行され、その2番目と5番目に新旧のファイル名が指定されている。このオプションは、その2つを取り出している。optex に --exit 0 というオプションが指定してあるのは、こうしてコマンドを正常終了しないと git でエラーになるためだ。
インストール
cpanminus
まずは cpanm コマンド (cpanminus) が必要だ。macOS だったら brew でインストールできる。
$ brew cpanminus
そうでなければ、apt コマンドとかを使うのだと思う。https://qiita.com/debug-ito/items/7caaecf6988870973438 などを参考にしてほしい。
ローカルにインストールするのなら、PATH などを設定する。
export PATH=${PATH}:${HOME}/perl5/bin
export PERL5LIB=${HOME}/perl5/lib/perl5:${PERL5LIB}
export MANPATH=${HOME}/perl5/man:${MANPATH}
それ以外に以下の条件が必要
- /dev/fd
- unzip コマンド
- perl5.014 以上
greple, optex
以上の環境を作るためには
App::GrepleApp::Greple::msdocApp::optexApp::optex::msdoc
という4つをインストールする必要があるのだが、App::optex::msdoc は他の3つに依存しているので、これをインストールすれば全部が入るはずだ。
$ cpanm App::optex::msdoc
自分の ~/.greplerc は、出力をパイプに渡した時にも着色するように、こう設定してある。
option default --color=always
greple の出力を less で見るのには -R オプションが必要。LESS と LESSANSIENDCHARS という環境変数を設定しておくと便利だ。
export LESS=-cR
export LESSANSIENDCHARS=mK
sdif, cdif
sdif や cdif を使いたい場合は App::sdif をインストールする。
$ cpanm App::sdif
cdif で --mecab オプションを有効にするには、~/.cdifrc を設定する。もちろん mecab コマンドがインストールされている必要がある。
option default --mecab
端末の背景色
バックグラウンドが白系のターミナルを使っている場合は、デフォルトの設定でうまく表示できるはずだ。また、Apple Terminal を使っている場合も、自動的に調整される。それ以外の黒系のターミナルを使っている人は sdif のマニュアルの COLOR セクションを読んでほしい。読むのが面倒な人は、とりあえず BRIGHTNESS という環境変数を 0 に設定して試してみるといい。
export BRIGHTNESS=0
SEE ALSO
本格的に変換したければ、そのようなツールを使うべきだ。ただ、どれも結構時間はかかるので、気軽に grep するような気分にはならないと思う。App::Greple::msdoc は、手抜きな分だけ高速に動作する。
greple で、これらを入力フィルタとして使うのは簡単だ。~/.greplerc に、こんな設定を書いておくと --pandoc, --tika というオプションが使えるようになる。pandoc は、pptx と xlsx という入力形式を理解しませんね。tika は、標準入力からどんな形式も受け付けるところがすごい。ただ、手元の環境では変なエラーが出ます。
option --pandoc \
--if '/\.docx$/:pandoc -f docx -t plain'
option --tika \
--if '/\.(docx|pptx|xlsx)$/:tika --text'
それにしても、結果が随分と違う。実行速度は、tika は遅すぎるが、pandoc だったら高速なマシンなら耐えられるかもしれない。










