Ubuntu Weekly Recipe

第328回「違いがわかる人」なろう

人の世は移ろいゆくものです。人生の劇的な変化にはそうそう遭遇しないかもしれませんが、身の回りのちょっとした変化ぐらいであれば毎日のようにどこかで発生しています。そしてその違いに気が付けるかどうかで、世界を見る目は大きく変わるのです。

バージョンアップ前後のソースコードの差異、校正から戻ってきた原稿の変更点、ミステリーツアーを賭けた間違い探し、恋人の髪型の変化……。そんな日々の「違いがわかる人」になり、ゆくゆくは上質を知る人になるために、今回はいくつかの差分ツールを紹介します。

テキストの差分

Ubuntuの利用者が一番よく調べる「違い」はテキストデータ、とくにソースコードの「差分」でしょう。Ubuntuに最初から入っているdiffコマンドは、2つのファイルやディレクトリの差分を人と機械が読みやすいフォーマットで作成するためのコマンドです。

たとえば以下の2つのファイル、sampleA.txtとそれを校正したsampleB.txtが存在するとします。

sampleA.txt
Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Sed pulvinor nulla vel purus dictum, vel
convallis eros pellentesque. Sed vulputate, massa
wq
id tempor ullamcorper, arku nunc cursus jsto, in
porttitor.
sampleB.txt
Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Sed pulvinar nulla vel purus dictum, vel
convallis eros pellentesque. Sed vulputate, massa
id tempor ullamcorper, arcu nunc cursus justo, in
porttitor.

3個所ほど違いを仕込んであるのですが、わかりますでしょうか。見ただけですぐにわかる違いもありますし、ぱっとではわかりづらい部分もあります。これをdiffコマンドで比較してみましょう。

$ diff -u sampleA.txt sampleB.txt
--- sampleA.txt 2014-06-08 02:09:36.191824793 +0900
+++ sampleB.txt 2014-06-08 02:04:27.075837981 +0900
@@ -1,6 +1,5 @@
 Lorem ipsum dolor sit amet, consectetur adipiscing
-elit. Sed pulvinor nulla vel purus dictum, vel
+elit. Sed pulvinar nulla vel purus dictum, vel
 convallis eros pellentesque. Sed vulputate, massa
-wq
-id tempor ullamcorper, arku nunc cursus jsto, in
+id tempor ullamcorper, arcu nunc cursus justo, in
 porttitor.

実行結果の最初の2行が比較対象のファイルの情報、3行目は差分が発生した塊(Hunk)の場所を示す行番号です。今回はHunkは1つだけですが、差分がとびとびに存在する場合はHunkも複数出力されます。Hunkの中では各行の先頭の1文字が特別な意味を持っています。空白なら差分がないことを示し、⁠-⁠はsampleA.txtのみに存在する行を、⁠+⁠はsampleB.txtのみに存在する行を示します。

たとえばサンプルファイルの2行目はsampleA.txtとsampleB.txtで異なる内容であるため、⁠-⁠⁠+⁠が1つずつ表示されています。diffコマンドは行単位の比較なので、その行のどこか一部が違うだけで、行全体が違うと表示されるのです。目をこらせばどの文字が違うかわかるでしょう。

Hunk上の下から3行目(-wq)はsampleA.txtのみに存在する行です。新しい行が追加されることで、後ろの行番号がずれることになりますが、diffは賢いので最後の行は「同じ内容の行」と判断しています。

オプションに「-u」を付けているのは、⁠unified形式」で出力するためです。diffは、標準では「context形式」の差分を表示しますが、若干人間には読みづらいので、unified形式を使うほうが一般的です。他にも「-y」オプションで横に並べて表示したり、オプションで出力フォーマットを細かく調整することも可能です。

ソースコードであれば、⁠-p」オプションを付けると関数名も表示してくれます。⁠-b」「-w」で末尾の空白や空白の数の変化を無視することもできますので、必要に応じて細かく調整すると良いでしょう。

このように出力された差分データは、patchコマンドを使うことで適用できます。今回の例ですとほとんど恩恵はありませんが、テキストファイルに加えた変更を逐一「このファイルの何行目のこれをこう変更して」と言葉で伝えることなく「このパッチ(差分ファイル)を適用してください」の一言で済むのはとても便利です。今回紹介する差分ツールの中には、差分の適用機能まで持ったツールも多数存在します。

ここまでが差分ツールに関する基本的な話です。

diffコマンドの派生品

diffコマンドは変更履歴を確認できる非常に便利なツールであるため、その派生品や別実装、オプションなどが多数存在します。

たとえばdiffは1行ずつ比較するため、長い行のどこか一部が変更された場合、どこが変わっているか見付けるのは困難です。そんなとき単語ごとに比較するwdiffであれば、より簡単にどこが変わったか確認できます。

$ sudo apt install wdiff
$ wdiff sampleA.txt sampleB.txt
Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Sed [-pulvinor-] {+pulvinar+} nulla vel purus dictum, vel
convallis eros pellentesque. Sed vulputate, massa
[-wq-]
id tempor ullamcorper, [-arku-] {+arcu+} nunc cursus [-jsto,-] {+justo,+} in
porttitor.

“[--]⁠が削除された単語、⁠{++}⁠が追加された単語です。ただしwdiffは半角空白を単語の区切りとして使用しているため、日本語の文章で使うには難があります。日本語文章の単語単位の差分を表示したい場合は、後ほど紹介するDocDiffを使うと良いでしょう。

他にもcolordiffコマンドを使えば差分を色付きで表示できますし、imediff2は差分を適用するかどうかをncursesを使ってグラフィカルに選択できます。またEmacsやVimなどは、エディタ自身が差分を生成し表示できます。

図1 vimdiffコマンド(もしくはvim -d)で差分を表示した例
図1 vimdiffコマンド(もしくはvim -d)で差分を表示した例

多様な出力フォーマットに対応するDocDiff

Ruby製のツールであるDocDiffは、HTMLをはじめとするいくつかの出力形式や日本語文字コードに対応した差分表示ツールです。

$ sudo apt install docdiff
$ docdiff --tty sampleA.txt sampleB.txt
図2 wdiffのように単語で区切りつつ、色分けして表示される
図2 wdiffのように単語で区切りつつ、色分けして表示される
図3 日本語を考慮した単語区切りにも対応している
図3 日本語を考慮した単語区切りにも対応している

「--tty」オプションを付けることで、他のdiffコマンドと同様に端末に出力できますし、HTMLや真鵺道と言った出力方式も選べます。標準では単語単位の差分ですが、⁠--line」「--char」オプションによって、行単位だけでなく文字単位での差分も出力できます。

図4 文字単位の差分をHTMLとして出力した例
図4 文字単位の差分をHTMLとして出力した例

人間による読みやすさを追求するのであれば、DocDiffは大変便利でしょう。

GUIで差分を表示する

GUIで比較しながら編集したいなら、Meldが便利です。Ubuntuのリポジトリからmeldパッケージをインストールすることで利用できます。背景色と前景色を変更することで、行単位と文字単位の比較を同時に行えます。左右のスクロールバーには、ファイル全体に対して差分が存在する場所に印がついているため、ファイル全体を俯瞰しつつ編集が可能です。

図5 GUIならではの表現により、どの行のどの部分が異なるかわかりやすい
図5 GUIならではの表現により、どの行のどの部分が異なるかわかりやすい

ディレクトリ同士を比較したり、バージョン管理システムのディレクトリを開いた場合は、変更点が存在する場所が強調表示されるファイラーとしても使えます。

Ubuntu 14.04 LTSまでのMeldパッケージはGTK+2版ではありますが、Utopic以降はGTK+3に対応した現在開発中の3.x系列が採用される予定です。

特定のファイル形式向け差分ツール

ファイルの中身を解析を解析したうえで、その差分を表示するツールもあります。たとえばtardiffはtarアーカイブを展開し、1つずつ差分を取ってその解析結果を表示しますし、latexdiffは2つのTeXファイルの差分を新たなTeXファイルとして出力するツールです。numdiffは、異なる数値表現を正規化したうえで、同じかどうかや異なっている場合はその数字の差を表示しますので、数値データファイルの比較を行う場合に便利でしょう。

バイナリの差分

バイナリファイルの解析時などに、複数のバイナリデータの差分を取得できると便利な場合があります。手っ取り早いのは、hdコマンドでバイナリデータをASCII表現にしてしまって、それを前述の差分ツールで比較する方法です。手間はかかりますが特殊なソフトウェアが必要ないぶん、お手軽ではあるでしょう。

専用ビューワーで差分を表示したいのであれば、dhexがシンプルで使いやすいです。初回起動時にキーコードの入力が必要になりますので、適宜対応してください。~/.dhexrcで再設定も可能なので、自分好みのキーバインドに変更しても良いでしょう。

図6 初期設定でVimに近いキーバインドが割り当てられている
図6 初期設定でVimに近いキーバインドが割り当てられている

バイナリの差分を他のバイナリに「patch」として適用したい場合は、bsdiffパッケージに含まれるbsdiffとbspatchコマンドを使いましょう。bsdiffは専用フォーマットのパッチ用バイナリを生成します。

画像の差分を生成する

バイナリデータの中でも画像の差分であれば、重ね合わせて比較するのが一番簡単です。原始的には2つの画像ウィンドウを重ね合わせてAlt+Tabで切り替えたり、片方のウィンドウの透明度を変更することで、その違いがすぐにわかるでしょう。もう少し機械的に判別したい場合は、ImageMagickで重ね合わせてしまうことです[1]⁠。

$ sudo apt install imagemagick
$ composite -compose difference imageA.png imageB.png diff.png
図7 左2つの画像の差分を取った結果が右の画像になる
図7 左2つの画像の差分を取った結果が右の画像になる

上記にあるように、差分がある場所だけ画像が残り、それ以外は黒で表示されます。ちなみに一致しているかどうかは、次のコマンドで色の平均値が0(黒)になっているかどうかでも確認できます。

$ identify -format "%[mean]" diff.png
0
(一致すると0と表示される)

PDFの差分を生成する

DiffPDFは2つのPDFファイルの内容の差異をグラフィカルに表示してくれるツールです[2]⁠。Ubuntuのリポジトリからdiffpdfパッケージをインストールすることで利用できます。ガイドに合わせてPDFファイルを開き、Compareボタンを押すだけで、そのページの異なる部分を色で表示してくれます。

図8 日本語にも対応している
図8 日本語にも対応している

差分の表示方法は、Appearance(純粋な見た目、前述のImageMagickによる比較方法に近い方法⁠⁠、Char(文字単位⁠⁠、Words(単語単位、ただし日本語の単語区切りには未対応)の3種類から選べ、Appearanceの場合はさらにShowで差分の計算方法も選べます。このため、文字主体のページと画像主体のページでそれぞれ適切な差分表示ができます。

Save Asで比較対象の2つのページを見開きページに配置したPDFを生成できますので、他の人に差分データを提示するときも便利でしょう。

CUI上で比較を行いたい場合は、poppler-utilsパッケージ含まれる変換ツールを使うと良いでしょう。pdftoppmコマンドはPDFファイルを1ページずつ画像に変換します。pdftotextコマンドはPDFに含まれる文字列オブジェクトをテキストファイルに保存します。

$ sudo apt install poppler-utils
$ pdftoppm -png foo.pdf foo_images
(foo_images-(ページ番号).pngファイルが生成される)
$ pdftotext foo.pdf
(foo.txtファイルが生成される)

あとは前述の画像ファイルやテキストファイルの時と同様に差分を取得するだけです。

LibreOfficeの差分を生成する

LibreOfficeのファイルはいくつかのリソースファイルを固めたZipファイルです。よってそのまま差分を取得するよりも、他のファイルフォーマットに変換してから差分を取得するほうが読みやすくなります。変換方法には次の3つが考えられます。

  • PDFに変換して「PDFの差分を生成する」と同じ方法を行う
  • Flat ODFに変換して1つのXMLファイルとして差分を取得する
  • unoconvで任意の形式に変換して差分を取得する

最初のPDFへの変換は、LibreOfficeの標準機能を使います。GUI上の[ファイル][PDFとしてエクスポート]を選んでもいいですし、CUIから変換したければ、次のコマンドが使えます。ちなみにsofficeコマンドはLibreOfficeが起動していると動作しないので注意してください。

$ soffice --headless --convert-to pdf foo.odt
javaldx: Could not find a Java Runtime Environment!
Warning: failed to read path from javaldx
convert /home/shibata/temp/diff/foo.odt -> /home/shibata/temp/diff/foo.pdf using writer_pdf_Export
Overwriting: /home/shibata/temp/diff/foo.pdf

2つ目のFlat ODFは、LibreOfficeの標準ファイル形式であるOpenDocument Formatで定義されているファイル形式の1つで、すべてのデータを1つのXMLファイルにまとめた形式になります。バイナリデータもBase64エンコードしたうえで埋め込まれるので、ファイルサイズは大きくなるものの、テキストファイルとして扱えるため、今回のような差分を取得する場合に便利なファイル形式となります。

Flat ODFへの変換も、LibreOfficeで可能です。GUIなら[ファイル][名前を付けて保存]から「ファイルの種類」「Flat XML」にして保存してください。拡張子は、ODTファイルならfodt、ODSならfodsのようになります。CUIから変換したければ次のコマンドを使います。

$ soffice --headless --convert-to fodt foo.odt
javaldx: Could not find a Java Runtime Environment!
Warning: failed to read path from javaldx
convert /home/shibata/temp/diff/foo.odt -> /home/shibata/temp/diff/foo.fodt using OpenDocument Text Flat XML
Overwriting: /home/shibata/temp/diff/foo.fodt

あとはテキストとして差分を取得すれば変更箇所がわかります。メタデータも残っているので、差分をパッチとして適用することも可能です。Flat ODFから通常のODFへも「--conver-to」オプションを使って戻せます。ちなみに、XMLファイルの差分はxmldiffコマンドを使うともう少し見やすくなります。

図9 DocDiffで差分をとった例。ODFのタグもちゃんと残っていることがわかる
図9 DocDiffで差分をとった例。ODFのタグもちゃんと残っていることがわかる

最後のunoconvは、LibreOfficeのUNOバインディングを使って、ドキュメント形式の変換を行うツールです。対応フォーマットはLibreOfficeで対応しているものと同じなので、変換ツールという意味では上記の「--convert-to」オプションと大差ないのですが、クライアント/サーバー機能を備えているのでネットワーク越しのLibreOfficeインスタンスに変換を依頼できるところが特徴です。

$ sudo apt install unoconv
$ unoconv -f pdf foo.odt

Webページの差分を生成する

Webページの差分を取得するにはおもに2つの方法が考えられます。

まずはHTMLやその他のファイル一式をこれまで説明したやり方で差分を取る方法です。このとき、構成するファイルの種類によってやり方が変わります。HTMLだったらテキストの差分ですし、画像ファイルだったらバイナリの差分です。おそらくまずはディレクトリごと差分を取ることになるでしょう。

$ diff -ruN www1/ www2/

「-r」オプションはサブディレクトリも比較するオプションで、⁠-N」は存在しないファイルは「空のファイル」として扱うオプションです。これにより、新規に追加されたファイルの内容も表示できます。

もう1つは対象のWebページ全体を何らかの方法で画像化してしまい、⁠画像の差分を生成する」と同じように差分を取る方法です。Webページの画像化は、ブラウザの拡張機能を使うなどいろいろな方法が存在します。

CUIで画像を生成したいのであれば、WebKitエンジンを利用したCutyCaptが便利です。

$ sudo apt install cutycapt
$ cutycapt --url=https://gihyo.jp/ --out=gihyo.jp.png
図10 cutycaptでキャプチャした例。ちゃんとページの下のほうまで撮れている
図10 cutycaptでキャプチャした例。ちゃんとページの下のほうまで撮れている

これでWebページ全体をレンダリングしたものがgihyo.jp.pngとして保存されるので、あとは「画像の差分を生成する」と同じ方法で比較できます。何回か取得すると広告部分が変わるので、実際にどんな風に変わるかわかることでしょう。

Debianパッケージの差分を生成する

devscriptsパッケージに含まれているdebdiffコマンドを使うと、Debianパッケージの内容を比較できます。たとえば13.10と14.04のaptパッケージを比較してみましょう。

$ sudo apt install devscripts
$ curl -LO http://jp.archive.ubuntu.com/ubuntu/pool/main/a/apt/apt_0.9.9.1~ubuntu3.1_amd64.deb
$ curl -LO http://jp.archive.ubuntu.com/ubuntu/pool/main/a/apt/apt_1.0.2ubuntu2_amd64.deb
$ debdiff apt_0.9.9.1~ubuntu3.1_amd64.deb apt_1.0.2ubuntu2_amd64.deb
(中略)
Files in second .deb but not in first
-------------------------------------
(中略)
-rw-r--r--  root/root   DEBIAN/shlibs
-rwxr-xr-x  root/root   /usr/bin/apt
-rwxr-xr-x  root/root   /usr/lib/apt/apt-helper

前回紹介したように、aptコマンドそのものが追加されていることがわかります。

上記はバイナリパッケージを直接比較しましたが、ソースパッケージを比較することも可能です。最近セキュリティアップデートされたOpenSSLのソースパッケージを比較してみましょう[3]⁠。

$ apt-get source openssl=1.0.1f-1ubuntu2
$ apt-get source openssl=1.0.1f-1ubuntu2.2
$ debdiff openssl_1.0.1f-1ubuntu2{,.2}.dsc
(中略)
--- openssl-1.0.1f/debian/patches/CVE-2014-0224-1.patch 1970-01-01 09:00:00.000000000 +0900
+++ openssl-1.0.1f/debian/patches/CVE-2014-0224-1.patch 2014-06-03 00:00:03.000000000 +0900
(中略)
diff -Nru openssl-1.0.1f/debian/patches/series openssl-1.0.1f/debian/patches/series
--- openssl-1.0.1f/debian/patches/series        2014-04-08 04:37:33.000000000 +0900
+++ openssl-1.0.1f/debian/patches/series        2014-06-03 02:56:46.000000000 +0900
@@ -36,3 +36,11 @@
 ppc64-support
 CVE-2014-0076.patch
 CVE-2014-0160.patch
+CVE-2010-5298.patch
+CVE-2014-0198.patch
+CVE-2014-0195.patch
+CVE-2014-0221.patch
+CVE-2014-0224-1.patch
+CVE-2014-0224-2.patch
+CVE-2014-3470.patch
+CVE-2014-0224-3.patch

debian/patches以下に新規にパッチが8つほど追加されていることがわかるでしょう。

まとめ

コンピューターに限らずどんな分野でも、いつ何がどのように変わったのかを把握しておくことは非常に重要です。コンピューターの世界では差分が比較的簡単に取得できますので、ぜひ自分が普段使うデータフォーマットの差分の取得方法を把握しておくことをお勧めします。たとえばファイルタイプに合わせて今回紹介したコマンドを呼ぶようなシェルスクリプトを1つ作っておけば、⁠git diff⁠時にテキスト以外の差分も表示できるようになります。詳しいことは、⁠man git⁠「Git Diffs」を参照してください。なお、恋人の髪型の変化に気づく方法は自分でなんとかしてください。

おすすめ記事

記事・ニュース一覧