追記
その後、optex
だけを使ってより汎用的なインタフェースで実装しました。今から使うんだったら、こちらの方がいいと思います。
追記2
greple の -Mmsdoc モジュールは、1.04 (2020-09-23) から optex -Mtextconv のコードを利用するように変更されています。なので、今はどちらを使っても同じ結果になります。optex を使うと、指定したすべてのファイルを変換してからコマンドを実行するので、多くのファイルを指定すると時間がかかります。-Mmsdoc
モジュールの場合は、都度変換しながら処理します。
MS のドキュメントを端末で操作する
マイクロソフトという会社は別に嫌いではないが、ソフトウェアさえ作ってくれなければいいのになあと思う。そうは言っても Word の資料を扱わなければならないことはあり、読めと言われれば仕方がないので嫌でもビューアーとかアプリを立ち上げるわけだが、古い資料を検索したり、バージョンの違うドキュメントを比較したりしようとした途端に息が詰まる。これをなんとかしようという話。
grep する
正確には grep ではなく 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
というオプションを実装してある1。テキスト部分だけを抜き出しているので、フォーマット情報は皆無だが、大体の意味はわかる。このように、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 --dump --indent %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
のプロセス置換機能を使えば、引数にコマンドを指定することができる。
$ grep 経費 <(greple -Mmsdoc --dump kisairei.docx)
これをいちいち入力するのは面倒なので、ファイル名を見て、自動的にデータを変換してくれるとうれしい。
というわけで、これと同等の動きをする optex
のモジュールを作った。App::optex::msdoc を使うと、ファイル名を見て、それを変換したデータを読むためのパスにコマンド引数を差し替える。
たとえば ~/.optex.d/default.rc
に、次のような設定をする2。
option --msdoc -Mmsdoc $<move>
こうすると、optex
経由で実行する、すべてのコマンドで --msdoc
というオプションが使えるようになる。ほら、grep
だってできた。
$ grep --msdoc -e 経費 -e 換算 kisairei.docx
パターンを複数指定した場合の grep
と greple
の挙動には違いがあり、grep
はどれかのパターンを含む行をすべて出力するのに対して、greple
はすべてのパターンを含む行だけを出力する。また、grep
はすべてのパターンを同じ色で表示するが greple
は異なる色を使う。
diff する
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 が分割した単位で色付けされている。
word ドキュメントの diff 出力は、パラグラフ単位になってしまうので、ツールのサポートがないと、どこが変更されたのかを判別しにくい。diff 出力をハイライトするコマンドや環境はいくつもあるが、日本語をちゃんと処理できるものは、今の所 cdif 以外に知らない3。
これで、さらに diff
→ optex
のシンボリックリンクをパスに入れてあれば、単に diff するだけでいいのだが、間接的に実行される diff コマンドも置き換えられてしまうことになるので、バックエンドで大量の diff を実行する場合にはオーバーヘッドが問題になる。というか、この例の cdif
がまさに影響を受ける。使いたければ alias などを使って対話型シェルでのみで有効にした方がいいだろう。その場合は、default を設定するのではなく alias に含めたらいいだろう。
$ alias diff="optex diff -Mmsdoc"
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
CPAN から cpanminus 自身でインストールするのがよさそうだ。一般ユーザ権限で実行すると ~/perl5
にインストールするかと訊かれるので、言われた通りにする。管理者権限でインストールすると、どこに何が入ったのかわからなくなる。
$ curl -sL http://cpanmin.us | perl - App::cpanminus
ローカルにインストールするのなら、PATH
などを設定する。
export PATH=${HOME}/perl5/bin:${PATH}
export PERL5LIB=${HOME}/perl5/lib/perl5:${PERL5LIB}
export MANPATH=${HOME}/perl5/man:${MANPATH}
それ以外に以下の条件が必要。
- /dev/fd
- unzip コマンド
- perl5.014 以上
greple, optex
以上の環境を作るためには
App::Greple
App::Greple::msdoc
App::optex
App::optex::msdoc
という4つをインストールする必要があるが、App::optex::msdoc
は他の3つに依存しているので、これをインストールすれば全部が入るはずだ。
$ cpanm App::optex::msdoc
自分の ~/.greplerc
は、出力をパイプに渡した時にも着色するように、こう設定してある。
option default --color=always
greple
の出力を less で見るのには -R
オプションが必要。LESS
と LESSANSIENDCHARS
という環境変数を設定する。後者は、若干一般的ではない端末制御シークエンスを使っているため4。
export LESS=-cR
export LESSANSIENDCHARS=mK
sdif, cdif
sdif
や cdif
を使いたい場合は App::sdif をインストールする5。
$ cpanm App::sdif
cdif
で --mecab
オプションを有効にするには、~/.cdifrc
を設定する。もちろん mecab
コマンドがインストールされている必要がある。
option default --mecab
sdif
には、3種類のテーマ色のようなものが定義してある。デフォルトは green で、それ以外に cmy と mono が使える。cmy を使いたければ、~/.sdifrc
を次のように設定する。
option --light --cmy
option --dark --dark-cmy
端末の背景色
バックグラウンドが白系のターミナルを使っている場合は、デフォルトの設定でうまく表示できるはずだ。また、Apple Terminal を使っている場合は、自動的に調整される6。それ以外の黒系のターミナルを使っている人は sdif のマニュアルの COLOR セクションを読んでほしい。読むのが面倒な人は、とりあえず TERM_BGCOLOR という環境変数を 000 に設定して試してみるといい。
追記: 2019.01 にリリースした 4.8.0 から iTerm2 にも対応している。
追記: 2020.04 にリリースした 4.14.0 から、モジュールを Getopt::EX::termcolor に移行した。また、XTerm 互換の端末をサポートしている。
export TERM_BGCOLOR=000
明るい端末用の設定のままだとこんな風に見える。このままでも使えなくはないが、なんかケバい。
SEE ALSO
optex
optex
については以前 Qiita に書いてあったが、最新版に合わせてアップデートした。
Pandoc & Apache Tika
本格的に変換したければ、そのようなツールを使うべきだ。ただ、どれも結構時間はかかるので、気軽に 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 だったら高速なマシンなら耐えられるかもしれない。
FEEDBACK
- https://kitani3.blogspot.com/2018/07/greple-appgreplemsdoc-microsoft-office.html
- この方の使い方だったら tika の方がいいような気がするんだけど
- あと、みなさん、コマンドを使う前にマニュアルを読まないんですかね。
- PowerPoint のページをソートされるようにしました。いずれ、optex のモジュールを使うように書き換えようと思います。[2020-09]
-
実際にはこう。
option --dump --le &sub{} --need 0 --all --epilogue 'sub{exit(0)}'
--le
で&sub{}
で空リストを返す関数、つまり何もマッチしないパターンを定義しているが--need 0
を指定しているので、マッチしなくても表示する。\A
を避けたのは、空文字列にマッチしても、エスケープシークエンスは出力する仕様になっているためだ。--nocolor
にしてもそれは防げるが、こうすることでパターンを追加すれば着色して表示することができる。最初の色が消費されてしまうのがイマイチだが、そこは目を瞑った。マッチしないのでgrep
と同様にexit(1)
する。git diff
のtextconv
コマンドは正常終了しなければならないので、苦肉の策で--epilogue
でexit(0)
している。 ↩ -
ここで
$<move>
が必要なのには少々解説が必要だ。optex
というかGetopt::EX
モジュールがオプションを展開する時には、展開後の先頭にある-M
で始まるオプションをモジュール指定として取り扱うのだが、この時展開した結果だけが処理に渡され、残りの引数があっても無視される。optex::msdoc
モジュールは、その後に続く引数を操作するので、そこに対象が含まれないと操作対象にならないのだ。$<move>
は、それに続くすべての引数をそこに移動して展開する効果を持っている。実は、自分自身、これがうまく動かずにかなり悩んだ。 ↩ -
これについては随分昔だが記事を書いた。もう5年も前なので、状況は変わっているかもしれない。素敵なツールがあったら教えて欲しい。cdif の欠点は、無茶苦茶速いわけではないところだ。 ↩
-
具体的にはパラグラフ境界を示すマークだ。
BLOCKEND
の色指定は/WE
になっていて、/W
は白のバックグラウンドを表す。E
は{EL}
と同じで、Erase Line の意味。つまりバックグラウンド色で行末まで塗りつぶす効果がある。通常の色指定のシークエンスはm
で終わるが EL はK
で終わる。 ↩ -
これで
sdif
,cdif
,watchdiff
という3つのスクリプトがインストールされる。watchdiff
は、一定間隔で指定したコマンドを実行して、変更のあった部分を強調表示する。 ↩ -
AppleScript を使って Terminal の背景色を取得し、RGB から輝度を計算して 0-100 の数値に変換し、50以上なら
--light
そうでなければ--dark
というオプションを設定している。Seeperldoc -m App::sdif::autocolor::Apple_Terminal
↩