Perl
grep
diff
docx
pptx

Word や PowerPoint のファイルを grep したり diff したりする

おわび

最初に投稿した時点で 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

スクリーンショット.png

複数の検索ワードを指定できるので「経費」と「換算」という単語を含む行を探したければ、こんな風に使う。

$ greple -Mmsdoc '経費 換算' kisairei.docx

ちなみに、こうしても同じだ。

$ greple -Mmsdoc -e 経費 -e 換算 kisairei.docx

-p オプションをつけるとパラグラフ単位で表示する。word のドキュメントは1パラグラフが1行なので、内容はほとんど変わらないが、切れ目はわかりやすくなる。

スクリーンショット 2018-06-29 17.53.34.png

特定の単語を含まない行を見たければ、こんな風にすると、上の例であれば最初のパラグラフのみが出力される。

$ 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 というファイルに、こんな設定をする。

~/.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 の下に lessoptex のシンボリックリンクを作成する。ここを PATH に入れて less を実行すると、自動的に optex 経由で実行される。なので less コマンドに、--ms--msx という新たなオプションが追加されたように見える。

$ less --ms kisairei.docx

スクリーンショット

もちろん、alias しても ok。

ホントに grep する

grepleless には、入力を制御する仕組みがあるので、上のような対応ができる。しかし普通のコマンドにはそんなインタフェースは用意されていない。

でも、bash の process substitution を使えば、シェルスクリプトとか作らなくてもなんとかなる。

$ grep 経費 <(greple -Mmsdoc --dump kisairei.docx)

しかし、いちいち入力するのは面倒なので、ファイル名を見て、自動的にデータを変換してくれるとうれしい。

というわけで、これと同等の動きをする optex のモジュールを作った。App::optex::msdoc を使うと、ファイル名を見て、それを変換したデータを読むためのパスにコマンド引数を差し替える。

たとえば ~/.optex.d/default.rc に、次のような設定をする。

~/.optex.d/default.rc
option --msdoc -Mmsdoc $<move>

こうすると、optex 経由で実行する、すべてのコマンドで --msdoc というオプションが使えるようになる。ほら、grep だってできた。

$ optex grep --msdoc 経費 kisairei.docx

スクリーンショット 2018-07-01 00.46.25.png

diff する

App::optex::msdoc を使った場合、引数で指定したファイルをすべて変換した後にコマンドが実行される。だから多くのファイルを同時に指定して grep したり less したりする用途には向かない。その点 diff で指定するファイルは2つに決まっている。

実際のところ optex::msdoc モジュールは、ファイル名を見て必要な時にのみ処理をするので、不要な場合に使ってもオーバーヘッドはほとんどない。diff コマンドで常に有効にしたければ、~/.optex.d/diff.rc に default を設定する。

~/.optex.d/diff.rc
option default --msdoc

こうすれば、

$ optex diff kisairei-H25.docx kisairei-H26.docx

で word ファイルをテキストベースで比較することができる。下は、この出力を cdif に通した結果だ。cdif--mecab オプション付きで実行されているので、mecab が分割した単位で色付けされている。

スクリーンショット

これで、さらに diffoptex のシンボリックリンクをパスに入れてあれば、単に diff するだけでいいのだが、間接的に実行される diff コマンドがすべて置き換えられてしまうことになるので、結果は変わらないが速度的に塩梅が悪いことがある。というか、この例の cdif がまさに影響を受ける。使いたければ alias などを使って会話的シェルでのみで有効にした方がいいだろう。

git でも diff する

git diff を実行できるようにするためには、~/.config/git/attributes ファイルに以下のような設定をして、ファイル拡張子に対する属性を定義する。

~/.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 に次のような設定が入る。手で編集してもいい。

~/.gitconfig
[diff "msdoc"]
        textconv = greple -Mmsdoc --dump

この状態で git diff を実行すると、ここで指定したフィルターを通した結果を比較する。自分の場合、.gitconfig をこう設定してあるので、出力は sdif を通して表示される。

~/.gitconfig
[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 の内容はこうなる。

~/.gitconfig
[diff "msdoc"]
        command = optex --exit 0 diff --msdoc -u --git-external-diff

--git-external-diff というオプションは、App::optex::msdoc で設定されている。

App/optex/msdoc.pm
##
## 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 でエラーになるためだ。

スクリーンショット 2018-07-01 00.21.47.png

インストール

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::Greple
  • App::Greple::msdoc
  • App::optex
  • App::optex::msdoc

という4つをインストールする必要があるのだが、App::optex::msdoc は他の3つに依存しているので、これをインストールすれば全部が入るはずだ。

$ cpanm App::optex::msdoc

自分の ~/.greplerc は、出力をパイプに渡した時にも着色するように、こう設定してある。

~/.greplerc
option default --color=always

greple の出力を less で見るのには -R オプションが必要。LESSLESSANSIENDCHARS という環境変数を設定しておくと便利だ。

export LESS=-cR
export LESSANSIENDCHARS=mK

sdif, cdif

sdifcdif を使いたい場合は App::sdif をインストールする。

$ cpanm App::sdif

cdif--mecab オプションを有効にするには、~/.cdifrc を設定する。もちろん mecab コマンドがインストールされている必要がある。

~/.cdifrc
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 は、標準入力からどんな形式も受け付けるところがすごい。ただ、手元の環境では変なエラーが出ます。

~/.greplerc
option --pandoc \
    --if '/\.docx$/:pandoc -f docx -t plain'

option --tika \
    --if '/\.(docx|pptx|xlsx)$/:tika --text'

スクリーンショット

スクリーンショット

それにしても、結果が随分と違う。実行速度は、tika は遅すぎるが、pandoc だったら高速なマシンなら耐えられるかもしれない。