テキストでないファイルのdiff(差分)をとる方法
AppleScriptはスクリプト言語なんだけど、ファイルに保存するときはAppleScript形式にコンパイル(変換)される。その結果、AppleScriptエディタでは人間が理解できるコードに見えるのに、普通のテキストエディタで開くとこんな状態。
その実態は、AppleScript独自のバイナリファイルなのだ。バイナリファイルの痛いところは、diffを実行してもそのままでは差分がとれないこと。どうしても差分をとりたいと思うのなら、いったんAppleScriptファイルを開いて、テキストファイルとして保存したファイルに対してdiffを実行するしかない。
しかし、それでは手間がかかりすぎる。手軽にdiffできないと、いずれ使わなくなる。そんな訳で「AppleScriptはバイナリファイルなのだから、しょうがない」と以前から諦めていたところがあった。(Gitのあの素晴らしい仕組みを知るまでは)ところが、Gitにはファイルアトリビュートとテキストコンバートという設定がある。
# .gitattirbutesあるいは.git/info/attributesの設定
*.scpt diff=applescript
# git configの設定
$ git config diff.applescript.textconv osadecompile
このように設定しておくと、.scpt形式のAppleScriptファイルに対してdiffを実行したときに、事前にosadecompileを実行した結果に対してdiffしてくれるのだ。osadecompileは.scpt形式のAppleScriptファイルをテキストに変換してくれるOSX独自のコマンド。つまり、テキストに変換されたAppleScriptの差分がとれるのだ!やっていることは、手作業でテキストに変換してdiffを実行しているのと同じことなんだけど、一度設定してしまえば、そのリポジトリでは普通にdiffを実行するだけで、当然のようにAppleScriptの差分がとれてしまうところが素晴らしい。
そのような感動的に素晴らしい情報が書籍「入門Git」には さらり と書いてある。あまりにも さらり と書いてあるので、なかなか気付けなかった...。実は、何度も読み直している中でようやく気付いたのだ。この辺りの話題は14章ファイルアトリビュートの中で、数ある便利な一つの機能(P248バイナリファイルの比較)として紹介されている。その他にもファイルアトリビュート+何らかの設定により、素晴らしく便利な機能を手に入れることができるのだ。*1
一旦diffの恩恵を経験すると、差分をとれない環境というのがとても不安に思えてくる。あの、MacBook ProのRetinaモデルのデスクトップピクチャに採用された写真家のKent Shiraishi氏も、常時モニタを2台並べて写真の色合いのチェックをしているそうである。曰く、自分の感覚なんて当てにならない、正確な色を再現するためには完璧にデジタル調整されたモニタを並べて、その違いを感じて現像するそうである。つまり、色の差分を見ているのだ!差分を知ることはとても大切。写真でも、テキストでも。
という訳で、あらゆるものを可能な限り手軽にdiffできる環境を作っておきたい、と常に思っている。だから、もちろん前回作り始めたdropbox_diff.shでも、AppleScriptの差分をとれるようにしてみた。さらに、テキストに変換するコマンドさえあれば、あらゆるものの差分をとれるようになる。(リッチテキストやJPEG(exifだが)の差分だってとれる。PDFや表計算ソフトのファイルだって、工夫すればどうにかなるかもしれない。)
convert_diffプロジェクトの開始
DropboxやGitリポジトリの中ではAppleScriptの差分が見られるようになった。そうなると、それ以外の環境でも手軽にdiffしたくなるのが人情。ならば、dropbox_diff.shに組み込んだテキスト変換機能の部分だけを切り出して、diffコマンドのラッパーを作ってしまえばいいのだ。.scptなどの特定の拡張子のファイルだったら、テキストコンバートしてdiffする、それ以外はそのままdiffする。そんな動作をするconvert_diff.shを作ってみた。
- とりあえず、.scpt、.rtf、.jpgの拡張子に対して、テキスト変換するようにしてみた。
#!/bin/bash convert() { if [ "${PATH1##*.}" = "scpt" ] && [ "${PATH2##*.}" = "scpt" ]; then echo -n 'osadecompile' elif [ "${PATH1##*.}" = "rtf" ] && [ "${PATH2##*.}" = "rtf" ]; then echo -n 'textutil -convert txt -stdout' elif [ "${PATH1##*.}" = "jpg" ] && [ "${PATH2##*.}" = "jpg" ]; then echo -n 'exiftool' else echo -n 'cat' fi } OPTION=("$@") PATH1=${OPTION[$(($# - 2))]} PATH2=${OPTION[$(($# - 1))]} unset OPTION[$(($# - 1))] unset OPTION[$(($# - 2))] if [ -f "$PATH1" ] && [ -f "$PATH2" ] && [ "`convert`" != 'cat' ]; then diff "${OPTION[@]}" <(`convert` "$PATH1") <(`convert` "$PATH2") echo -e "\n\`diff ${OPTION[@]} <(`convert` \"$PATH1\") <(`convert` \"$PATH2\")\`\n" else diff "$@" fi
- プロセス置き換え機能を利用すると、一時ファイルを作らずに元ファイルを変換できるので便利!
- コマンド Tips - UNIX & Linux コマンド・シェルスクリプト リファレンス(素晴らしい情報に感謝です!)
- -<(コマンド) = <( )内のコマンドの実行結果をファイルとして扱ってくれる。
$ diff -u file1.scpt file2.scpt
- テキストに変換してからdiffするには、以下のように修正すればいいのだ。
$ diff -u <(osadecompile file1.scpt) <(osadecompile file2.scpt)
カラー表示
- そういえば、Gitのdiffではカラー表示が見やすかった。
- 変更箇所が目立っていると、感覚的に漏れなくチェックできる。
そうだ、色を付けよう!
- 調べてみると、その名のとおりのcolordiffコマンドが見つかった。
- diffをカラーで表示する - A Way of Code(素晴らしい情報に感謝です!)
- さっそくインストールしてみた。
$ brew install colordiff
- 使い方はdiffの代わりにcolordiffを実行するか、
$ colordiff file1 file2
- diffの出力とパイプで接続して、色付けする。
$ diff -u file1 file2|colordiff
- 既存のdiffの機能をすべて使いたいので、後者のパイプで接続してみた。
- colordiffは、パイプで接続したときの動作をサポートしていないようだ。
$ echo 'hello'|diff -u - ~/Desktop/hello.txt ...中略... -hello +hello world!!
$ echo 'hello'|colordiff -u - ~/Desktop/hello.txt ...中略... +hello world!!
- しかし、パイプを利用すると、巨大なファイルのdiffで問題が生じるかもしれない。
- 自分の環境では、約26万字の文字数制限があるのだ。
$ sysctl -A kern.argmax
kern.argmax: 262144
- 自分の使い方では今のところ、それほど巨大なファイルをdiffする需要はないので、
- 約26万文字の制限があることを念頭に置きながら、パイプで接続してみた。
#!/bin/bash convert() { if [ "${PATH1##*.}" = "scpt" ] && [ "${PATH2##*.}" = "scpt" ]; then echo -n 'osadecompile' elif [ "${PATH1##*.}" = "rtf" ] && [ "${PATH2##*.}" = "rtf" ]; then echo -n 'textutil -convert txt -stdout' elif [ "${PATH1##*.}" = "jpg" ] && [ "${PATH2##*.}" = "jpg" ]; then echo -n 'exiftool' else echo -n 'cat' fi } OPTION=("$@") PATH1=${OPTION[$(($# - 2))]} PATH2=${OPTION[$(($# - 1))]} unset OPTION[$(($# - 1))] unset OPTION[$(($# - 2))] if [ -f "$PATH1" ] && [ -f "$PATH2" ] && [ "`convert`" != 'cat' ]; then diff "${OPTION[@]}" <(`convert` "$PATH1") <(`convert` "$PATH2")|colordiff echo -e "\n\`diff ${OPTION[@]} <(`convert` \"$PATH1\") <(`convert` \"$PATH2\")\`\n" else diff "$@"|colordiff fi
- 差分
$ dropbox_diff.sh convert_diff.sh @@ -21,10 +21,10 @@ if [ -f "$PATH1" ] && [ -f "$PATH2" ] && [ "`convert`" != 'cat' ]; then unset OPTION[$( ($# - 1) )] unset OPTION[$( ($# - 2) )] - diff "${OPTION[@]}" <(`convert` "$PATH1") <(`convert` "$PATH2") + diff "${OPTION[@]}" <(`convert` "$PATH1") <(`convert` "$PATH2")|colordiff echo -e "\n\`diff ${OPTION[@]} <(`convert` \"$PATH1\") <(`convert` \"$PATH2\")\`\n" else - diff "$@" + diff "$@"|colordiff fi
これでAppleScriptなども手軽にdiffできるようになった!快適。快適。
改良バージョン
- convert_diff.shは、次に日記でより汎用的に使えるように、以下のように修正された。
#!/bin/bash convert() { case $1 in *.scpt ) echo -n "osadecompile" ;; *.rtf ) echo -n "textutil -convert txt -stdout" ;; *.jpg ) echo -n "exiftool" ;; * ) echo -n "cat";; esac } OPTION=("$@") PATH1=${OPTION[$(($# - 2))]} PATH2=${OPTION[$(($# - 1))]} unset OPTION[$(($# - 1))] unset OPTION[$(($# - 2))] # 一方がディレクトリだったら、他方のファイル名を補う if [ -d "$PATH1" ] && [ -f "$PATH2" ]; then PATH1="$PATH1/`basename "$PATH2"`" elif [ -f "$PATH1" ] && [ -d "$PATH2" ]; then PATH2="$PATH2/`basename "$PATH1"`" fi # 両方ともディレクトリだったら、変換せずにdiffを実行する if [ -d "$PATH1" ] && [ -d "$PATH2" ]; then echo "\`diff" "$@""|colordiff'" diff "$@"|(`which colordiff`||`which cat`) else echo "\`diff ${OPTION[@]} <(`convert "$PATH1"` \"$PATH1\") <(`convert "$PATH2"` \"$PATH2\")|colordiff'" diff "${OPTION[@]}" <(`convert "$PATH1"` "$PATH1") <(`convert "$PATH2"` "$PATH2")|(`which colordiff`||`which cat`) fi
colordiff 追加情報
- 設定ファイル(自分好みの色などを設定できる)
- /etc/colordiffrc(自分でmakeした場合)
- /usr/local/etc/colordiffrc(Homebrewでインストールした場合)
# Example colordiffrc file for dark backgrounds # # Set banner=no to suppress authorship info at top of # colordiff output banner=no # By default, when colordiff output is being redirected # to a file, it detects this and does not colour-highlight # To make the patch file *include* colours, change the option # below to 'yes' color_patches=no # # available colours are: white, yellow, green, blue, # cyan, red, magenta, black, # darkwhite, darkyellow, darkgreen, # darkblue, darkcyan, darkred, # darkmagenta, darkblack # # Can also specify 'none', 'normal' or 'off' which are all # aliases for the same thing, namely "don't colour highlight # this, use the default output colour" # plain=off newtext=blue oldtext=red diffstuff=magenta cvsstuff=green
- 上記オリジナルの設定ファイルをホームフォルダにコピーして、自分好みに修正するのだ。
$ cp /usr/local/etc/colordiffrc ~/.colordiffrc
- 自分の環境でGitデフォルト風に見える設定
banner=no color_patches=no #diff_cmd=diff #コメントアウトしないとエラーになってしまった plain=off newtext=darkgreen oldtext=darkred diffstuff=cyan cvsstuff=red
*1:入門Gitは、読み直す度に新たな発見がある。たぶん、すべての機能の解説が平等に網羅されているのだと思う。そのため、Gitをある程度知らないと読みにくいのだが、Gitをさらに便利に使いたいと思ったときには、そこには問題解決のヒントがたくさんあるのだ。タイトルが入門となっているが、これはGitの原典なのである。