Arabesque

Unix as IDE: Introduction

このエントリーは、 Unix as IDE シリーズ全7回のうちの第1回です。

*このシリーズは 中国語ロシア語トルコ語 韓国語電子書籍としてフォーマットされています。

初心者も経験豊富なプロのプログラマーも、IDE、つまり統合開発環境のコンセプトを高く評価している。 コードを整理し、書き、保守し、テストし、デバッグするために必要な主要ツールを、すべての異なるツールに共通のインターフェイスを持つ統合されたアプリケーションの中に持っていることは、確かに非常に貴重な資産です。さらに、さまざまな言語でのプログラミングのために特別に設計された環境は、オートコンプリート、構文チェック、ハイライトなどの利点をもたらす。

このようなツールは、GNU/LinuxやBSDを含むすべての主要なデスクトップオペレーティングシステム上で開発者が利用可能であり、その多くは無料で利用できるため、Windowsのメモ帳やnanocatでコードを書く正当な理由はありません。

しかし、Unixとその現代の派生ツールの信奉者の間では、「UnixはIDEである」というマイナーなミームがあり、これは開発者がターミナル上で利用できるツールが、最先端のデスクトップIDEの主要な機能をある程度簡単にカバーしているという意味である。これについては意見が分かれるところだが、EclipseやMicrosoft Visual Studioと同じ意味でUnixをIDEと呼ぶのが妥当だと思うかどうかは別として、地味なBashシェルがどれほど包括的な開発環境になりうるかについては、驚くかもしれない。

なぜUNIXはIDEなのか?

IDEを使う主な理由は、すべてのツールを同じ場所に集め、ほぼ同じユーザーインターフェイスパラダイムで、別々のアプリケーションを協調させるために多大な努力をすることなく、協調して使うことができるからです。GUIアプリケーションでこれが特に望ましくなるのは、ウィンドウ・アプリケーションに共通言語を話させたり、互いにうまく動作させたりするのが非常に難しいからだ。テキストのカット&ペーストは別として、これらは共通インターフェースを共有していない。

シェルユーザーにとってこの問題で興味深いのは、よく設計され、不朽のUnixツールは、すでにテキストのストリーム永続オブジェクトとしてのファイルという共通のユーザーインターフェイスを共有しているということだ。Unixのほとんどすべてがこの2つのコンセプトに基づいて構築されており、この共通のユーザーインターフェイスと、ユーザーや開発者が特に相互運用性を重視してきた40年にわたる高性能ツールの歴史が相まって、Unixを本格的なIDEと同じくらい強力なものにしているのです。

正しい考え

EmacsとVi(GNU EmacsとVim)という2つの古いテキストエディタの現代版には、あらゆる編集作業をサポートするプラグインを開発するコミュニティが活発に存在している。どちらのエディタにも、プログラミングで本当にやりたいことはほとんど何でもできるプラグインがあり、Vimのジャンキーなら誰でも、少なくとも3つか4つは「必須」だと感じるプラグインを口にできるだろう。

しかし、このような取り組みについて読んでいると、関係する開発者がこれらのテキストエディタを独自のIDEにしようとしていることがよくわかる。Vimから離れる必要はないとか、Emacsから離れる必要はないという投稿があります。 しかし、VimやEmacsをそうでないものに押し込めようとするのは、問題を正しく考えているとは言えないと思います。 Vimの作者であるBram Moolenaarも、:help design-notを読めばわかるように、ある程度同意しているようです。 シェルはCtrl+Zを押すだけで、その成熟した、高度にコンポーザブルなツールセットによって、どちらのエディタよりも大きな力を得ることができる。 EDIT 2017年10月: Vim 8.xの新バージョンでは、:terminalコマンドでアクセス可能な埋め込みターミナルが追加されました。これまでのプラグインベースの試みよりもずっとうまく機能する。この新機能があっても、私は代わりにこれらの投稿で議論されているアプローチを強くお勧めします

このシリーズについて

この一連の投稿では、IDEの6つの主要な機能について説明し、GNU/Linuxで利用可能な一般的なツールによって、どのようにそれらを簡単に一緒に使うことができるかを示す例を挙げます。これは決して包括的な調査ではありませんし、これから紹介するツールが唯一の選択肢というわけでもありません。

  • ファイルとプロジェクトの管理lsfindgrep/ackbash
  • テキストエディタと編集ツールvimawksortcolumn
  • コンパイラおよび/またはインタプリタgcc, perl
  • ビルドツールmake
  • デバッガgdb, valgrind, ltrace, lsof, pmap
  • バージョン管理ツールdiff, patch, svn, git

言いたいことではないこと

私はIDEが悪いとは思わない;素晴らしいものだと思う。だからこそ、UnixをIDEとして使うことができる、少なくともIDEとして考えることができる、と納得させようとしているのだ。Unixがどんなプログラミング作業にも常に最適なツールだと言うつもりもない。JavaやC#のような「業界」言語よりも、C、C++、Python、Perl、Shellの開発の方が、特にGUIを多用するアプリケーションを書く場合には、間違いなく適している。 とりわけ、せっかく身につけたEclipseやMicrosoft Visual Studioの知識を、時に難解なコマンドラインの世界のために捨てろと説得するつもりはない。私がしたいのは、フェンスの向こう側で私たちが何をしているかをお見せすることだけです。

Unix as IDE: ファイル

このエントリーは、シリーズ Unix as IDE のパート2です。

IDEの顕著な特徴のひとつは、ファイルを管理するためのビルトインシステムです。移動、名前変更、削除のような基本的な機能から、コンパイルや構文チェックのような開発に特化した機能まであります。 また、特定の拡張子やサイズのファイルを探したり、特定のパターンでファイルを検索したりといった、ファイルの集合に対する操作も便利でしょう。この最初の記事では、ほとんどのGNU/Linuxユーザーにとって馴染みのあるツールを、プロジェクト内のファイル群を扱う目的で使う便利な方法をいくつか紹介する。

ファイルの一覧表示

ls の使用は、管理者がディレクトリの内容の簡単なリストを取得するために最初に習うコマンドの一つであろう。ほとんどの管理者は -a-l スイッチについても知っているはずで、それぞれドットファイルを含むすべてのファイルを表示したり、ファイルに関するより詳細なデータをカラムで表示したりすることができます。

GNU ls には、あまり頻繁に使用されないが、プログラミング上非常に便利なスイッチが他にもある:

  • -t — 最終更新日順にファイルを一覧表示する。これは非常に大きなディレクトリで、 headsed 10q を通してパイプされるような、最近変更されたファイルのリストを素早く取得したい場合に便利である。おそらく -l と組み合わせると最も便利だろう。最も古いファイルが欲しい場合は、 -r を追加してリストを反転させることができる。
  • - X — 拡張子でファイルをグループ化する。ポリグロットコードの場合、ヘッダーファイルとソースファイルを別々にグループ化したり、ディレクトリやビルドファイルからソースファイルを分離したりするのに便利である。
  • -v — ファイル名のバージョン番号を自然にソートする。
  • -S — ファイルサイズでソートする。
  • -R — ファイルを再帰的にリストアップする。これは -l と組み合わせて、less のようにページャーに通すと良い。

一覧は他のものと同じようにテキストなので、例えばこのコマンドの出力を vim プロセスにパイプすることで、各ファイルが何のためにあるのかの説明を追加して inventory ファイルとして保存したり、README に追加したりすることができる:

$ ls -XR | vim -

このようなことは、ちょっとした作業でmakeによって自動化することもできる、このシリーズの後の別の記事で取り上げよう。

ファイルの検索(find)

面白いことに、カレントディレクトリの . 引数を指定して find とタイプするだけで、飾り気のない相対パスを含むファイルの完全なリストを得ることができる、ただし sort を通してパイプしたいかもしれない:

$ find . | sort
.
./Makefile
./README
./build
./client.c
./client.h
./common.h
./project.c
./server.c
./server.h
./tests
./tests/suite1.pl
./tests/suite2.pl
./tests/suite3.pl
./tests/suite4.pl

もし ls -l スタイルのリストが欲しければ、GNU find(1)find の結果のアクションとして -ls を追加することができる:

$ find . -ls | sort -k11,11
1155096    4 drwxr-xr-x   4 tom      tom          4096 Feb 10 09:37 .
1155152    4 drwxr-xr-x   2 tom      tom          4096 Feb 10 09:17 ./build
1155155    4 -rw-r--r--   1 tom      tom          2290 Jan 11 07:21 ./client.c
1155157    4 -rw-r--r--   1 tom      tom          1871 Jan 11 16:41 ./client.h
1155159   32 -rw-r--r--   1 tom      tom         30390 Jan 10 15:29 ./common.h
1155153   24 -rw-r--r--   1 tom      tom         21170 Jan 11 05:43 ./Makefile
1155154   16 -rw-r--r--   1 tom      tom         13966 Jan 14 07:39 ./project.c
1155080   28 -rw-r--r--   1 tom      tom         25840 Jan 15 22:28 ./README
1155156   32 -rw-r--r--   1 tom      tom         31124 Jan 11 02:34 ./server.c
1155158    4 -rw-r--r--   1 tom      tom          3599 Jan 16 05:27 ./server.h
1155160    4 drwxr-xr-x   2 tom      tom          4096 Feb 10 09:29 ./tests
1155161    4 -rw-r--r--   1 tom      tom           288 Jan 13 03:04 ./tests/suite1.pl
1155162    4 -rw-r--r--   1 tom      tom          1792 Jan 13 10:06 ./tests/suite2.pl
1155163    4 -rw-r--r--   1 tom      tom           112 Jan  9 23:42 ./tests/suite3.pl
1155164    4 -rw-r--r--   1 tom      tom           144 Jan 15 02:10 ./tests/suite4.pl

この場合、sortに11番目のカラムであるファイル名でソートするように指定しなければならないことに注意してください;これは -k オプションで行います。

find は複雑なフィルタリング構文を持っている。以下の例では、特定のファイルのリストを取得するために適用できる最も便利なフィルタのいくつかを示す:

  • find . -name '*.c' — シェルスタイルのパターンにマッチする名前のファイルを見つける。大文字小文字を区別せずに検索したい場合は -iname を使用する。
  • find . -path '*test*' — パスがシェルスタイルのパターンにマッチするファイルを検索する。大文字小文字を区別せずに検索するには -ipath を使用する。
  • find . -mtime -5 — 最近5日以内に編集されたファイルを検索する。 代わりに +5 を使用すると、5日前以前に編集されたファイルを検索できる。
  • find . -newer server.cserver.c よりも最近更新されたファイルを検索する。
  • find . -type d — ディレクトリを検索する。ファイルの場合は -type f を、シンボリックリンクの場合は -type l を使用する。

特に、これらすべてを組み合わせることで、たとえば、過去2日間に編集されたCのソースファイルを見つけることができることに注意:

$ find . -name '*.c' -mtime -2

デフォルトでは、 find が検索結果に対して取るアクションは、単に標準出力にリストアップするだけであるが、他にもいくつかの便利なアクションがある:

  • -ls — 上記のような ls -l スタイルのリストを提供する (GNU find(1))

  • -delete — マッチしたファイルを削除する。

  • -exec — 各ファイルに対して任意のコマンドラインを実行する。{} を適切なファイル名に置き換えて、\;で締めくくる;例えば:

      $ find . -name '*.pl' -exec perl -c {} \;
    

    1回のコマンド実行ですべての結果を得たい場合は、終端文字として + を使用することができます。私がよく使うトリックのひとつに、findを使ってファイルのリストを作成し、縦に分割されたVimウィンドウで編集するというものがある:

      $ find . -name '*.c' -exec vim {} +
    

IDE としての Unix の初期のバージョンでは、find の結果に xargs を使用することが推奨されていました。ほとんどの場合、これは実際には必要ないはずで、 -execwhile read -r ループを使って空白のあるファイル名を処理する方がより堅牢である

ファイルの検索(grep)

しかしながら、ファイルセットの属性よりも、内容に基づいてファイルを見つけたいことがよくあるため、grep、特にgrep -Rがここで役に立つのは当然である。これは現在のディレクトリツリーを再帰的に検索し、’someVar’ にマッチするものを探す:

$ grep -FR someVar .

デフォルトでは grep は大文字と小文字を区別して動作するので、大文字と小文字を区別しないフラグも忘れないでほしい:

$ grep -iR somevar .

また、grep -l を使えば、マッチしたファイルそのものを表示することなく、マッチしたファイルの一覧を表示することができる:

$ grep -lR someVar .

上記の出力を使ってスクリプトやバッチジョブを書く場合は、whileループと read を使って、ファイル名に含まれるスペースやその他の特殊文字を処理する:

grep -lR someVar | while IFS= read -r file; do
    head "$file"
done

プロジェクトでバージョン管理を使っている場合は、.svn.git.hg ディレクトリのメタデータも含まれます。これは、適切な固定文字列 (grep -F) にマッチするものを 除外 (grep -v) することで簡単に対処できます:

$ grep -R someVar . | grep -vF .svn

いくつかのバージョンの grep には --exclude オプションと --exclude-dir オプションがあり、そちらの方が便利かもしれない。

とはいえ、 ack という非常に人気のある grep の代替 があり、デフォルトでこの種のものを除外してくれる。また、多くのプログラマが愛用しているPerl互換の正規表現(PCRE)を使うこともできます。 ソースコードを扱うのに一般的に便利なユーティリティがたくさんあるので、古くからある grep を使うのも悪くないが、もし ack をインストールできるなら、それを強くお勧めする。ack-grepというDebianパッケージがあり、Perlスクリプトなのでインストールはとても簡単だ。

古典的な grep に代わる比較的新しい Perl スクリプトについて私が言及すると、Unix 純粋主義者は不愉快に思うかもしれないが、新しい問題を解決する同じ精神を持った代替ツールが利用可能なときに、同じ古典的なツールに固執することが Unix の哲学や IDE として Unix を使用することに依存するとは思わない。

ファイルのメタデータ

file ツールは、拡張子やヘッダーなどの手がかりから、今見ているファイルがどのような種類のファイルなのかを一行で要約してくれます。これは、見慣れないファイル群を調べるときに find と一緒に使うととても便利です:

$ find . -exec file {} +
.:            directory
./hanoi:      Perl script, ASCII text executable
./.hanoi.swp: Vim swap file, version 7.3
./factorial:  Perl script, ASCII text executable
./bits.c:     C source, ASCII text
./bits:       ELF 32-bit LSB executable, Intel 80386, version ...

ファイルのマッチング

このセクションの最後のヒントとして、Bashのパターン・マッチとブレース展開について少し勉強しておくことをお勧めする。これは、私が以前投稿したBashシェル展開で学ぶことができる。

以上のことから、古典的なUNIXシェルは、プログラミング・プロジェクトでファイルを管理するためのかなり強力な手段となる。

*2017年4月、find(1)呼び出しのほとんどにPOSIX互換の例を使うように編集した。

Unix as IDE: 編集

このエントリーは、シリーズ Unix as IDE の全7回のうちの第3回です。

テキストエディタはプログラマにとって核となるツールであり、それゆえにプログラマの間ではエディタの選択が舌を巻くほどの熱狂的な議論を呼び起こす。Unixは、EmacsとVi、そしてその現代版であるGNU EmacsとVimという、編集哲学は全く異なるが同等のパワーを持つ2つのエディタと、最も強く結びついているオペレーティング・システムである。

私自身はVimの異端児ですが、ここではプログラミングに欠かせないVimの機能、特にエディタに内蔵された機能を補完するためにVimの内部から呼び出されるシェルツールの使用について説明します。 ここで述べる原則のいくつかはEmacsを使用している人にも適用できますが、おそらくNanoのような非力なエディタには適用できないでしょう。

Vim のプログラマ向けツールセットは 膨大 であるため、これは非常に一般的な内容になります。要点と私が最も役に立つと思うことに焦点を当て、そのトピックをより包括的に扱った記事へのリンクを提供しようと思います。 Vimの:helpはその質の高さと便利さで、初めてエディタを使う多くの人を驚かせていることを忘れないでください。

ファイルタイプの検出

Vim には、読み込まれるファイルタイプに基づいて動作する設定、特にシンタッ クハイライトを調整する設定が組み込まれています。特に、特定の言語が通常使用するインデントスタイルを設定することができます。これは、.vimrcファイルの最初の項目のひとつになるはずだ。

if has("autocmd")
  filetype indent plugin on
endif

シンタックスハイライト

たとえ16色ターミナルで作業しているだけであっても、もしまだであれば、.vimrcに以下を含めてください:

syntax on

デフォルトの16色端末のカラースキームは、必然的にほとんどきれいなものではありませんが、彼らは仕事をし、ほとんどの言語については、非常にうまく動作する構文定義ファイルが利用可能です。利用可能なcolorschemesの膨大な配列があり、それらに合うように微調整したり、独自のものを書いたりするのは難しいことではありません。256色ターミナルやgVimを使えば、より多くの選択肢が得られます。良いシンタックスハイライティングファイルは、明確なシンタックスエラーを赤い背景で表示してくれます。

行番号付け

従来のIDEで行番号をよく使う場合、行番号を有効にする:

set number

少なくともVim 7.3を持っていて、行番号を絶対的にではなく、現在行からの相対的な行番号にしたいのであれば、これも試してみるといいだろう:

set relativenumber

タグファイル

Vim は ctags ユーティリティの出力を使って とてもうまく 動作します。これにより、プロジェクト全体で特定の識別子が使われている箇所をすばやく検索したり、同じファイル内にあるかどうかに関係なく、変数の宣言に直接移動したりすることができます。複数のファイルで構成される大規模な C プロジェクトでは、この機能によって無駄な時間を大幅に節約できます。

多くの一般的な言語のプロジェクトのルートディレクトリで :!ctags -R を実行することで、プロジェクト全体の識別子の定義と場所が詰まった tags ファイルを生成することができます。プロジェクトの tags ファイルが利用できるようになると、次のようにプロジェクト全体で適切なタグの使用を検索することができます:

:tag someClass

コマンド :tn:tp を使用すると、プロジェクト内の他の場所でタグが連続して使用されている箇所を繰り返し確認することができます。ビルトインのタグ機能で必要な機能はほとんどカバーされていますが、タグリストウィンドウのような機能については、人気のあるTaglistプラグインをインストールしてみてください。Tim PopeのUnimpairedプラグインにも、便利な関連マッピングがいくつか含まれています。

外部プログラムの呼び出し

2017年まで、Vimセッション中に外部プログラムを呼び出す方法は大きく分けて3つありました:

  • :!<command> — Vim コンテキスト内からコマンドを発行するのに便利で、特に出力をバッファに記録する場合に便利です。
  • :shell — Vim のサブプロセスとしてシェルにドロップします。対話型コマンドに適しています。
  • `Ctrl-Z — Vimを一時停止し、呼び出したシェルからコマンドを発行する。

2017年以降、Vim 8.xにはウィンドウにターミナルエミュレータのバッファを表示する:terminalコマンドが追加されました。これはConqueのような以前のプラグインベースの試みよりもうまく機能しているようです。現時点では、他の vi タイプのエディターでも動作する古い方法を使うことを強くお勧めします。

Lint プログラムとシンタックスチェッカー

外部プログラム呼び出し(例えば perl -cgcc)を使った構文チェックやコンパイルは、エディタ内から :! コマンドを使って行うのが良い呼び出しのひとつです。Perlファイルを編集する場合、このように実行することができます:

:!perl -c %

/home/tom/project/test.pl syntax OK

Press Enter or type command to continue

シンボル % はカレントバッファにロードされたファイルの省略形です。 これを実行すると、コマンドの出力があればコマンドラインの下に表示します。このチェックを頻繁に呼び出したい場合は、.vimrc ファイルにコマンドやキーの組み合わせとしてマップすることもできます。この場合、:PerlLint というコマンドを定義し、通常モードから \l で呼び出せるようにする:

command PerlLint !perl -c %
nnoremap <leader>l :PerlLint<CR>

しかし、多くの言語では、Vimに内蔵されているクイックフィックス・ウィンドウを利用する、もっと良い方法があります。適切な makeprg をファイルタイプに設定することでこれを行うことができる。この場合、Vim がクイックリストに使用できる出力を提供するモジュールと、2つのフォーマットの定義が含まれる:

:set makeprg=perl\ -c\ -MVi::QuickFix\ %
:set errorformat+=%m\ at\ %f\ line\ %l\.
:set errorformat+=%m\ at\ %f\ line\ %l

まず、このモジュールを CPAN 経由でインストールするか、Debian パッケージの libvi-quickfix-perl をインストールする必要があるかもしれない。エラーが見つかった場合は、 :copen でクイックリストウィンドウを開いてエラーを調べたり、 :cn:cp でバッファ内のエラーにジャンプすることができる。

Vim quickfix working on a Perl file

Perl ファイルで動作する Vim クイックフィックス

これはgccの出力にも使えますし、エラー出力にファイル名、行番号、エラー文字列を含むようなコンパイラの構文チェッカーであれば、どんなものでも使えます。PHPのようなウェブに特化した言語や、JavaScript用のJSLintのようなツールでも可能です。似たようなことをするSyntasticという優れたプラグインもあります。

他のコマンドからの出力の読み込み

コマンドを呼び出して、その出力を作業中のバッファに直接貼り付けるには、 :r! を使います。例えば、現在のフォルダのディレクトリ一覧をバッファに取り込むには、次のように入力します:

:r!ls

もちろん、これはコマンドに対してだけ機能するわけではない。公開鍵や独自のボイラープレートなど、:rだけで他のファイルを読み込むことができる:

:r ~/.ssh/id_rsa.pub
:r ~/dev/perl/boilerplate/copyright.pl

他のコマンドによる出力のフィルタリング

外部コマンド(範囲指定やビジュアルモードなど)を使ってバッファ内のテキストをフィルタリングし、コマンドの出力に置き換えることができます。Vim のビジュアルブロックモードは列データを扱うのに最適ですが、 columncutsortawk などのツールを使うと便利なことがよくあります。

例えば、次のように入力すると、ファイル全体を2番目の列で逆にソートすることができます:

:%!sort -k2,2r

行がパターン /vim/ にマッチする場合、選択したテキストの3列目だけを表示することができる:

:'<,'>!awk '/vim/ {print $3}'

1行目から10行目までのキーワードを、きれいな書式の列に並べることができる:

:1,10!column -t

本当にあらゆる種類のテキストフィルターやコマンドがVimでこのように操作できる。エディターができることを桁違いに拡張するシンプルな相互運用性機能だ。Vimのバッファを効果的にテキストストリームにすることで、これらの古典的なツールすべてが話すことができる言語になります。

これについては、私の“Shell from Vi” の投稿に詳細があります。

組み込みの代替手段

ソートや検索のような一般的な操作については、Vim には :sort:grep といったビルトインメソッドが用意されています、これは Windows で Vim を使う場合に便利ですが、シェル呼び出しのような適応性はありません。

差分

Vim には vimdiff という 差分 モードがあり、異なるバージョンのファイル間の差分を表示するだけでなく、3 者間マージによって競合を解決したり、テキストの範囲に対して :diffput:diffget のようなコマンドで差分を置き換えたりすることができます。 コマンドラインから vimdiff を直接呼び出すには、比較するファイルを 2 つ以上指定します:

$ vimdiff file-v1.c file-v2.c

Vim diffing a .vimrc file

Vimによる.vimrcファイルの差分表示

バージョン管理

バージョン管理のメソッドは Vim から直接呼び出すことができます。ここで覚えておくと便利なのは、% は常にバッファのカレントファイルのショートカットであるということです:

:!svn status
:!svn add %
:!git commit -a

最近、Vimを使ったGit機能の勝者として、Tim PopeのFugitiveが挙げられます。Vimを使ってGitの開発を行う人には、ぜひお勧めします。このシリーズの第7回では、Unixにおけるバージョン管理の基礎と歴史についてより包括的な扱いをします。

差異

VimがGUIベースのIDEに慣れた多くのプログラマからおもちゃや遺物のように思われている理由の一つは、それ自体がシェルのための非常に有能な編集コンポーネントではなく、サーバー上のファイルを編集するための単なるツールとみなされていることです。Unixフレンドリーなシステム上で、外部ツールとの組み合わせが可能な独自のビルトイン機能は、経験豊富なユーザーをも時々驚かせるテキスト編集の強豪となる。

Unix as IDE: コンパイル

このエントリーは、シリーズ Unix as IDE の全7回のうちの4回目です。

Unixプラットフォーム上でコードをコンパイルしたり解釈したりするために利用可能なツールはたくさんあり、それらは異なった方法で使われる傾向があります。 しかし、概念的には多くの手順は同じです。ここでは、GNU Compiler Collection の gcc を使った C コードのコンパイルと、インタプリタの例として perl の使い方を簡単に説明する。

GCC

GCCは非常に成熟したGPLライセンスのコンパイラのコレクションで、おそらくCとC++プログラムを扱うのに最もよく知られているでしょう。その自由ソフトウェアライセンスと、GNU/LinuxやBSDのような自由なUnixライクなシステムでのほぼ普遍的な存在により、このような目的では永続的に人気がありますが、ClangのようなLLVMインフラストラクチャを使ったコンパイラで、より現代的な代替が利用可能です。

GNU Compiler Collectionのフロントエンド・バイナリは、それ自体が完全なコンパイラのセットというよりは、パース、コンパイル、リンクなどのステップを実行する、個別のプログラミング・ツールのセット用のドライバと考えた方がよいでしょう。このことは、GCCを比較的簡単なコマンドラインで使って、Cソースから動作するバイナリに直接コンパイルすることができる一方で、その過程でGCCがとるステップをより詳細に検査し、それに応じて微調整することもできることを意味しています。

ここではmakeファイルの使い方については触れませんが、1つ以上のファイルからなるCプロジェクトでは、ほぼ間違いなくmakeファイルが必要になるでしょう。

オブジェクトコードのコンパイルとアセンブル

Cのソースファイルからオブジェクトコードをコンパイルするには、次のようにします:

$ gcc -c example.c -o example.o

有効なCプログラムであると仮定すると、カレントディレクトリにexample.oというリンクされていないバイナリオブジェクトファイルが生成される。アセンブラの内容は objdump ツールで調べることができる:

$ objdump -D example.o

あるいは、-S パラメータを使って gcc にオブジェクトの適切なアセンブリコードを直接出力させることもできる:

$ gcc -c -S example.c -o example.s

この種のアセンブリー出力は、ソースコードそのものとインラインで出力されると、特に有益で、少なくとも興味深いものになる、と言える:

$ gcc -c -g -Wa,-a,-ad example.c > example.lst

プリプロセッサ

C プリプロセッサ cpp は一般的にヘッダーファイルをインクルードしたり、マクロを定義したりするために使用される。これは gcc コンパイルの通常の部分であるが、 cpp を直接呼び出すことで、それが生成する C コードを見ることができる:

$ cpp example.c

これは、インクルードや関連するマクロを適用して、コンパイルされたコード全体を出力します。

オブジェクトのリンク

1つ以上のオブジェクトは、以下のようにすれば適切なバイナリにリンクできる:

$ gcc example.o -o example

この例では、GCCはGNUリンカであるldへの呼び出しを抽象化する以上のことはしていない。このコマンドはexampleという実行可能なバイナリを生成する。

コンパイル、アセンブル、リンク

これらの作業は、次のようにして一度に行うことができる:

$ gcc example.c -o example

これは少し単純だが、オブジェクトを個別にコンパイルすることで、不必要にコードを再コンパイルしなくて済むという、実際的なパフォーマンス上の利点があることが判明した、次回の記事で説明しよう。

インクルードとリンク

Cファイルとヘッダーは、-Iパラメーターで明示的にコンパイル呼び出しにインクルードできる:

$ gcc -I/usr/include/somelib.h example.c -o example

同様に、もし ncurses のような /lib/usr/lib のような一般的な場所にあるコンパイル済みのシステムライブラリと動的にリンクする必要がある場合は、-l パラメータでそれを含めることができます:

$ gcc -lncurses example.c -o example

コンパイル過程で必要なインクルードやリンクが多い場合は、これを環境変数に入れるのが理にかなっている:

$ export CFLAGS=-I/usr/include/somelib.h
$ export CLIBS=-lncurses
$ gcc $CFLAGS $CLIBS example.c -o example

この非常に一般的なステップも、Makefileが抽象化してくれるように設計されている。

コンパイル計画

gccがどのような呼び出しで何を行っているかをより詳細に調べるには、-vスイッチを追加して、コンパイル計画を標準エラー出力するように指示することができる:

$ gcc -v -c example.c -o example.o

オブジェクトファイルやリンクされたバイナリを実際に生成させたくない場合は、代わりに -### を使った方がすっきりすることがある:

$ gcc -### -c example.c -o example.o

これは、gccバイナリがあなたのためにどのようなステップを抽象化しているのかを確認するのに役立つことがほとんどだが、特定のケースでは、コンパイラが必ずしも望んでいないようなステップを踏んでいることを確認するのに役立つこともある。

より冗長なエラーチェック

gcc の呼び出しに -Wall オプションや -pedantic オプションを追加することで、必ずしもエラーになるとは限らないが、エラーになる可能性があるものに対して警告を出すようにすることができる:

$ gcc -Wall -pedantic -c example.c -o example.o

これは Makefile や Vim の makeprg 定義に含めるのに適しています。前の記事で説明したクイックフィックス・ウィンドウとうまく連動し、エラーについてより広範囲に警告してくれるので、より読みやすく、互換性があり、エラーの起こりにくいコードを書くことができます。

コンパイル時間のプロファイリング

フラグ -timegcc に渡すと、各ステップにかかる時間を示す出力を生成できる:

$ gcc -time -c example.c -o example.o

最適化

gccに一般的な最適化オプションを渡すと、コンパイル時間を犠牲にして、より効率的なオブジェクトファイルやリンクバイナリをビルドしようとする。通常、-O2が本番環境で使用するコードには最適です:

  • gcc -O1
  • gcc -O2
  • gcc -O3

他のBashコマンドと同様に、これらのコマンドはすべて”Vim内から”呼び出すことができます:

:!gcc % -o example

インタプリタ

Unixライクなシステムでのインタプリタ・コードへのアプローチは大きく異なります。 この例ではPerlを使いますが、これらの原則のほとんどは、例えばPythonやRubyのインタプリタ・コードにも適用できます。

インライン

Perl コードの文字列をインタプリタに直接入力するには、次のような方法がある、 この例では、”Hello, world. “という1行を画面に出力し、その後に改行を入れる。 最初の方法は、おそらくPerlで作業する最も簡単で標準的な方法です; 2番目はheredoc文字列を使用し、3番目は古典的なUnixシェルパイプを使用します。

$ perl -e 'print "Hello world.\n";'
$ perl <<<'print "Hello world.\n";'
$ echo 'print "Hello world.\n";' | perl

もちろん、コードをファイルに保存し、直接実行できるようにするのが一般的だ:

$ perl hello.pl

どちらの場合でも、-cスイッチを使えば、実際に実行しなくてもコードの構文をチェックすることができる:

$ perl -c hello.pl

しかし、スクリプトを論理バイナリとして使用し、スクリプトが何であるかを知ったり気にしたりすることなく直接起動できるようにするには、ファイルに「シェバング(shebang)」と呼ばれる特別な最初の行を追加して、そのファイルを実行するインタプリタを指定するマジックを使うことができる。

#!/usr/bin/env perl
print "Hello, world.\n";

スクリプトは chmod を呼び出して実行可能にする必要がある。スクリプトはロジックバイナリの形になっているので、拡張子を削除するために名前を変更するのも良い方法です:

$ mv hello{.pl,}
$ chmod +x hello

その後は、コンパイルされたバイナリのように直接呼び出すことができる:

$ ./hello

これは非常に透過的に動作するので、useraddのフロントエンドであるadduserなど、最近のGNU/Linuxシステムで一般的なユーティリティの多くは、実際にはPerlやPythonスクリプトである。

次の投稿では、Rubyのrakeを使った新しいアイデアに触れながら、IDEに匹敵する方法でビルドプロジェクトを定義し、自動化するためのmakeの使い方について説明する。

Unix as IDE: Building

このエントリーは、シリーズ[ Unix as IDE] (https://blog.sanctum.geek.nz/series/unix-as-ide/ “Unix as IDE”) の全7章のうちの第5章です。

プロジェクトのコンパイルは複雑で繰り返しの多いプロセスであるため、優れたIDEはソフトウェアのビルドを抽象化、単純化、さらには自動化する手段を提供する。Unixとその子孫は、ソースファイルとオブジェクトファイルから実行可能ファイルを生成するための標準フォーマットで規定されたレシピである Makefile を使ってこのプロセスを達成する。

makeについて注意すべき興味深い点は、一般的にコンパイルされたソフトウェアのビルドの自動化に使用され、そのためのショートカットがたくさんあるが、実際には、別のファイルからあるファイルセットを生成する必要があるあらゆる状況で効果的に使用できるということである。たとえば、ウェブ用に最適化されたグラフィックをソース・ファイルから生成してウェブサイトに配置するような使い方や、オンザフライでページを生成するのではなく、コードから静的なHTMLページを生成するような使い方だ。Rubyのrakeのようなツールは、あらゆる種類のコードやファイルを生成したりインストールしたりする一般的な作業を自動化し、人気を博している。

Makefile の解剖学

Makefileの一般的なパターンは、変数のリストと ターゲット のリスト、そしてそれらを提供するために使用するソースやオブジェクトです。ビルドしたファイルをシステムにインストールする install や、ビルドしたファイルをソースツリーから削除する clean などです。

コンパイラによって実行される典型的な構文解析、前処理、適切なコンパイルとリンクのステップだけでなく、テストの実行(make test)、ドキュメントのソースファイルを1つまたは複数の適切なフォーマットにコンパイルすること、または生産システムへのコードのデプロイを自動化すること(例えば、git pushまたは同様のコンテンツ追跡メソッドによるウェブサイトへのアップロードなど)です。

単純なソフトウェアプロジェクトの Makefile の例は以下のようなものです:

all: example

example: main.o example.o library.o
    gcc main.o example.o library.o -o example

main.o: main.c
    gcc -c main.c -o main.o

example.o: example.c
    gcc -c example.c -o example.o

library.o: library.c
    gcc -c library.c -o library.o

clean:
    rm *.o example

install: example
    cp example /usr/bin

上記はこのプロジェクトに最適な Makefile ではありませんが、make とタイプするだけでリンクされたバイナリをビルドしてインストールする手段を提供します。各 ターゲット 定義には、それに続くコマンドに必要な 依存関係 のリストが含まれている。これは、定義がどのような順番で現れてもよく、make の呼び出しは適切な順番で関連するコマンドを呼び出すことを意味する。

上記の多くは、不必要に冗長であったり、繰り返しが多い。例えば、オブジェクトファイルが同じ名前の1つのCファイルから直接ビルドされる場合、ターゲットをインクルードする必要は全くなく、makeが私たちのために物事を整理してくれる。同様に、繰り返される呼び出しのいくつかを変数に入れれば、コンパイラやフラグの選択が変わっても個別に変更する必要がなくなる。より簡潔なバージョンは以下のようになる:

CC = gcc
OBJECTS = main.o example.o library.o
BINARY = example

all: example

example: $(OBJECTS)
    $(CC) $(OBJECTS) -o $(BINARY)

clean:
    rm -f $(BINARY) $(OBJECTS)

install: example
    cp $(BINARY) /usr/bin

より一般的な make の使い方

しかし、自動化のためには、コードのコンパイルやリンクだけでなく、もう少し一般的な使い方を考えることが有益である。例えば、PHPをライブのウェブサーバにデプロイするような単純なウェブプロジェクトです。これは通常 make を使用するタスクとは結びつかないが、原理は同じである。

PHPファイルはもちろんコンパイルを必要としませんが、ウェブアセットはしばしばコンパイルを必要とします。 Web開発者にはおなじみの例として、Webに展開するためにベクターソースファイルから拡大縮小され最適化されたラスター画像を生成することがあります。あなたはオリジナルのソースファイルを保持し、バージョン管理します。

この特定のプロジェクトでは、サイト全体で使用する4つのアイコンセットを64×64ピクセルのサイズにするとしよう。SVGベクター形式のソースファイルは手元にあり、バージョン管理下に安全に保管されている。そのため、ターゲットのiconsを定義し、依存関係を設定し、実行するコマンドを入力することができる。Unixのコマンドラインツールは、Makefile構文と一緒に使うことで、本当に輝き始める:

icons: create.png read.png update.png delete.png

create.png: create.svg
    convert create.svg create.raw.png && \
    pngcrush create.raw.png create.png

read.png: read.svg
    convert read.svg read.raw.png && \
    pngcrush read.raw.png read.png

update.png: update.svg
    convert update.svg update.raw.png && \
    pngcrush update.raw.png update.png

delete.png: delete.svg
    convert delete.svg delete.raw.png && \
    pngcrush delete.raw.png delete.png

上記が完了した状態で、make iconsとタイプすると、Bashループで各ソースアイコンファイルを調べ、ImageMagickのconvertを使ってSVGからPNGに変換し、pngcrushを使って最適化し、アップロード可能な画像を生成します。

同様のアプローチは、例えばMarkdownソースからHTMLファイルを生成するなど、様々な形式のヘルプファイルを生成するために使用できる:

docs: README.html credits.html

README.html: README.md
    markdown README.md > README.html

credits.html: credits.md
    markdown credits.md > credits.html

そしておそらく最終的には、git push webでウェブサイトをデプロイすることになるだろう。ただし、アイコンのラスタライズとドキュメントの変換が終わってからだ:

deploy: icons docs
    git push web

ある接尾辞を持つファイルを別の接尾辞に変換するための、よりコンパクトで抽象的な式は、.SUFFIXESプラグマを使って特別なシンボルを使って定義することができます。この場合、$<はソースファイル、$*は拡張子なしのファイル名、$@はターゲットファイルを指します。

icons: create.png read.png update.png delete.png

.SUFFIXES: .svg .png

.svg.png:
    convert $< $*.raw.png && \
    pngcrush $*.raw.png $@

Makefile を構築するためのツール

GNU Autotools ツールチェインには、特に autoconfautomake のような、大規模なソフトウェアプロジェクトの configure スクリプトや make ファイルをより高いレベルで構築するための様々なツールが存在します。これらのツールを使用することで、非常に大規模なソースベースをカバーする configure スクリプトと make ファイルを生成することができます。

この複雑なプロセスをカバーすることは、それ自体が一連の記事になるであろうし、この調査の範囲外である。

*コメントで.SUFFIXESを提案してくれたユーザーsamwyse氏に感謝する。

Unix as IDE: デバッグ

このエントリーは、シリーズ Unix as IDEのパート6です。

プログラムで予期せぬ動作に気づいたとき、GNU/Linuxは問題を診断するための様々なコマンドラインツールを提供します。GNUデバッガであるgdbや、あまり知られていないPerlデバッガのような関連ツールの使用は、コードにブレークポイントを設定したり、実行中のプログラムの状態を調べるためにIDEを使用している人にはおなじみでしょう。しかし、プログラムがシステムとどのように相互作用し、そのリソースをどのように使用しているかをより詳細に観察するために、興味のある他のツールが利用可能である。

gdb によるデバッグ

gdbは、EclipseやVisual Studioのような最近のIDEに内蔵されているデバッガと非常によく似た方法で使うことができます。コンパイルしたばかりのプログラムをデバッグする場合、バイナリに デバッグシンボル を追加してコンパイルするのが理にかなっていますし、それは -g オプションを含む gcc コールで行うことができます。あるコードに問題がある場合、-Wall を使ってエラーを表示することもできます:

$ gcc -g -Wall example.c -o example

gdbの古典的な使い方は、CまたはC++でコンパイルされた実行中のプログラムのシェルとして、プログラムがクラッシュするまでの状態を検査することである。

$ gdb example
...
Reading symbols from /home/tom/example...done.
(gdb)

(gdb)プロンプトでrunと入力してプログラムを起動すると、セグメンテーションフォールトなどのエラーの原因について、問題が発生したソースファイルや行番号など、より詳細な情報を提供してくれるかもしれない。上記のようにデバッグシンボルを使ってコードをコンパイルし、その実行状態を検査することができれば、特定のバグの原因を突き止めるのが非常に簡単になります。

(gdb) run
Starting program: /home/tom/gdb/example 

Program received signal SIGSEGV, Segmentation fault.
0x000000000040072e in main () at example.c:43
43     printf("%d\n", *segfault);

(gdb)シェル内でエラーが発生してプログラムが終了した後、backtraceとタイプすると、呼び出し元の関数が何であったかを確認することができる、これにはクラッシュの原因に関係すると思われる特定のパラメータを渡すことができる。

(gdb) backtrace
#0  0x000000000040072e in main () at example.c:43

breakを使って gdb にブレークポイントを設定すると、マッチする行番号や関数呼び出しに到達したときにプログラムの実行を停止させることができる:

(gdb) break 42
Breakpoint 1 at 0x400722: file example.c, line 42.
(gdb) break malloc
Breakpoint 1 at 0x4004c0
(gdb) run
Starting program: /home/tom/gdb/example 

Breakpoint 1, 0x00007ffff7df2310 in malloc () from /lib64/ld-linux-x86-64.so.2

それ以降は、stepを使って連続したコード行をステップするのが便利です。これは他の gdb コマンドと同じように、Enter キーを何度も押して1行ずつステップ実行を繰り返すことができる:

(gdb) step
Single stepping until exit from function _start,
which has no line number information.
0x00007ffff7a74db0 in __libc_start_main () from /lib/x86_64-linux-gnu/libc.so.6

プロセスIDを見つけてgdbに渡せば、gdbをすでに実行中のプロセスにアタッチすることもできる:

$ pgrep example
1524
$ gdb -p 1524

これは、実行に予想外に時間がかかっているタスクの出力のストリームをリダイレクトするのに便利である。

valgrind によるデバッグ

ずっと新しいvalgrindも同様の方法でデバッグツールとして使うことができます。このプログラムが実行できるチェックやデバッグ方法には多くの種類がありますが、最も便利なものの1つがMemcheckツールで、バッファオーバーフローのような一般的なメモリエラーを検出するのに使えます:

$ valgrind --leak-check=yes ./example
==29557== Memcheck, a memory error detector
==29557== Copyright (C) 2002-2011, and GNU GPL'd, by Julian Seward et al.
==29557== Using Valgrind-3.7.0 and LibVEX; rerun with -h for copyright info
==29557== Command: ./example
==29557== 
==29557== Invalid read of size 1
==29557==    at 0x40072E: main (example.c:43)
==29557==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==29557== 
...

gdbvalgrind ツールは 一緒に使うことができる ので、プログラムの実行状況を徹底的に調べることができる。Zed ShawのLearn C the Hard Way には、わざと壊れたプログラムを使った valgrind の初歩的な使い方の紹介が載っている。

ltrace によるシステムコールとライブラリコールのトレース

straceltrace ツールは、それぞれ実行中のプログラムのシステムコールとライブラリコールを監視し、画面やより便利なファイルにログを記録できるように設計されている。

ltraceを実行し、監視したいプログラムを唯一のパラメータとして与えるだけで、このように監視したいプログラムを実行させることができる。そうすると、終了するまでのシステムコールとライブラリコールのリストが表示されます。

$ ltrace ./example
__libc_start_main(0x4006ad, 1, 0x7fff9d7e5838, 0x400770, 0x400760 
srand(4, 0x7fff9d7e5838, 0x7fff9d7e5848, 0, 0x7ff3aebde320) = 0
malloc(24)                                                  = 0x01070010
rand(0, 0x1070020, 0, 0x1070000, 0x7ff3aebdee60)            = 0x754e7ddd
malloc(24)                                                  = 0x01070030
rand(0x7ff3aebdee60, 24, 0, 0x1070020, 0x7ff3aebdeec8)      = 0x11265233
malloc(24)                                                  = 0x01070050
rand(0x7ff3aebdee60, 24, 0, 0x1070040, 0x7ff3aebdeec8)      = 0x18799942
malloc(24)                                                  = 0x01070070
rand(0x7ff3aebdee60, 24, 0, 0x1070060, 0x7ff3aebdeec8)      = 0x214a541e
malloc(24)                                                  = 0x01070090
rand(0x7ff3aebdee60, 24, 0, 0x1070080, 0x7ff3aebdeec8)      = 0x1b6d90f3
malloc(24)                                                  = 0x010700b0
rand(0x7ff3aebdee60, 24, 0, 0x10700a0, 0x7ff3aebdeec8)      = 0x2e19c419
malloc(24)                                                  = 0x010700d0
rand(0x7ff3aebdee60, 24, 0, 0x10700c0, 0x7ff3aebdeec8)      = 0x35bc1a99
malloc(24)                                                  = 0x010700f0
rand(0x7ff3aebdee60, 24, 0, 0x10700e0, 0x7ff3aebdeec8)      = 0x53b8d61b
malloc(24)                                                  = 0x01070110
rand(0x7ff3aebdee60, 24, 0, 0x1070100, 0x7ff3aebdeec8)      = 0x18e0f924
malloc(24)                                                  = 0x01070130
rand(0x7ff3aebdee60, 24, 0, 0x1070120, 0x7ff3aebdeec8)      = 0x27a51979
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

また、すでに実行されているプロセスにアタッチすることもできる:

$ pgrep example
5138
$ ltrace -p 5138

一般的に、これによって生成されるテキストは画面一杯分よりもかなり多いので、-oオプションを使って呼び出しのログを出力するファイルを指定すると便利だ:

$ ltrace -o example.ltrace ./example

このトレースはVimのようなテキストエディタで見ることができ、ltrace出力のシンタックス・ハイライトが含まれている:

Vim session with ltrace
output

Vimセッションとltrace出力

ltrace は、動的リンク時のライブラリの検索や、/etcにある設定ファイルのオープン、/dev/random/dev/zeroのようなデバイスの使用状況を出力してくれるので、不適切なリンクが原因かもしれない問題のデバッグや、chroot環境に必要なリソースがない場合のデバッグにとても便利だ。

lsof で開いているファイルを追跡する

実行中のプロセスが開いているデバイス、ファイル、ストリームを表示したい場合は、 lsof を使用する:

$ pgrep example
5051
$ lsof -p 5051

例えば、私のホームサーバーで実行されているapache2プロセスの最初の数行はこうだ:

# lsof -p 30779
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME
apache2 30779 root  cwd    DIR    8,1     4096       2 /
apache2 30779 root  rtd    DIR    8,1     4096       2 /
apache2 30779 root  txt    REG    8,1   485384  990111 /usr/lib/apache2/mpm-prefork/apache2
apache2 30779 root  DEL    REG    8,1          1087891 /lib/x86_64-linux-gnu/libgcc_s.so.1
apache2 30779 root  mem    REG    8,1    35216 1079715 /usr/lib/php5/20090626/pdo_mysql.so
...

興味深いことに、あるプロセスのオープンファイルをリストアップするもう一つの方法 は、ダイナミックな /proc ディレクトリにあるそのプロセスに対応するエントリを チェックすることである:

# ls -l /proc/30779/fd

これは、ファイルロックで混乱した状況や、プロセスが必要のないオープンファイルを保持しているかどうかを識別するのに非常に便利である。

pmap でメモリ割り当てを見る

最後のデバッグのヒントとして、特定のプロセスのメモリ割り当てを pmap で見ることができる:

# pmap 30779 
30779:   /usr/sbin/apache2 -k start
00007fdb3883e000     84K r-x--  /lib/x86_64-linux-gnu/libgcc_s.so.1 (deleted)
00007fdb38853000   2048K -----  /lib/x86_64-linux-gnu/libgcc_s.so.1 (deleted)
00007fdb38a53000      4K rw---  /lib/x86_64-linux-gnu/libgcc_s.so.1 (deleted)
00007fdb38a54000      4K -----    [ anon ]
00007fdb38a55000   8192K rw---    [ anon ]
00007fdb392e5000     28K r-x--  /usr/lib/php5/20090626/pdo_mysql.so
00007fdb392ec000   2048K -----  /usr/lib/php5/20090626/pdo_mysql.so
00007fdb394ec000      4K r----  /usr/lib/php5/20090626/pdo_mysql.so
00007fdb394ed000      4K rw---  /usr/lib/php5/20090626/pdo_mysql.so
...
total           152520K

これは、共有メモリにあるものも含めて、実行中のプロセスがどのライブラリを使用しているかを表示します。下部に表示される合計は、ロードされた共有ライブラリの場合、実行中のプロセスだけがメモリを使用しているとは限らないので、少し誤解を招きやすい。指定されたプロセスの「実際の」メモリ使用量を決定するのは、共有ライブラリが追加されることで、見た目よりも少し深くなります。

Unix as IDE: 変更履歴

このエントリーは、シリーズ Unix as IDE のパート7です。

EclipseやVisual StudioのようなGUI IDEは、バージョン管理を受け入れ、業界標準のバージョン管理システムをサポートするようになりました。しかし、現代のバージョン管理システムの系譜は diffpatch といったプログラムの Unix の概念にまで遡る。

この Unix as an IDE series の最後の記事では、最初のバージョン管理ツールの一つである diffpatch の基本的なコンセプトから、一般的なオープンソースのバージョン管理システムの進化を追っていこうと思う。

diff, patch, and RCS

バージョン管理システムの中心的なコンセプトは、統一された差分であり、ファイルやファイルに加えられた一連の変更を人間やコンピュータが読みやすい言葉で表現したファイルである。diffコマンドは1974年にDouglas McIlroyによってUnixの第5版で初めてリリースされた。

最も一般的で相互運用可能なフォーマットである unified diff は、以下の構文で2つのバージョンのファイルを比較することで生成できます:

$ diff -u example.{1,2}.c
--- example.1.c    2012-02-15 20:15:37.000000000 +1300
+++ example.2.c    2012-02-15 20:15:57.000000000 +1300
@@ -1,8 +1,9 @@
 #include <stdio.h>
+#include <stdlib.h> 

 int main (int argc, char* argv[]) { printf("Hello, world!\n");
-    return 0;
+    return EXIT_SUCCESS; }

この例では、2番目のファイルにヘッダーファイルが追加され、return の呼び出しが main() の戻り値としてリテラル値 0 ではなく標準の EXIT_SUCCESS を使用するように変更されている。diff の出力には、変更されたファイル名や各ファイルの最終更新時刻などのメタデータも含まれることに注意してほしい。

大規模なコードベースに対するバージョン管理の原始的な形は、開発者が diff の出力を交換することで、この文脈では パッチ と呼ばれ、patch ツールを使ってお互いのコードベースに適用できるようにすることだった。上記の diff の出力を次のようにパッチとして保存することができる:

$ diff -u example.{1,2}.c > example.patch

そして、このパッチをまだ古いバージョンのファイルを持っている開発者に送り、開発者は自動的にそのパッチを適用することができる:

$ patch example.1.c < example.patch

パッチはサブディレクトリ内を含む複数のファイルからの diff 出力を含むことができるため、ソースツリーに変更を適用するための非常に実用的な方法を提供します。

変更を追跡するために diff 出力を使用する操作は十分に規則的であったため、ファイルのその場での履歴を保持するために、ソースコード管理システム と、それにほぼ取って代わる リビジョン管理システム が開発された。RCSは、システムから「チェックアウト」されている間は、他の人が編集できないようにファイルを「ロック」することを可能にし、より発展したバージョン管理システムにおける他の概念への道を開いた。

RCSには、使い方が非常に簡単という利点がある。既存のファイルをバージョン管理下に置くには、ci <ファイル名>とタイプし、ファイルに適切な説明を与えるだけでよい:

$ ci example.c
example.c,v  <--  example.c
enter description, terminated with single '.' or end of file:
NOTE: This is NOT the log message!
>> example file
>> .
initial revision: 1.1
done

これにより、同じディレクトリにexample.c,vというファイルが作成され、変更を追跡する。このファイルに変更を加えるには、チェックアウトして変更を加え、再びチェックインする:

$ co -l example.c
example.c,v  -->  example.c
revision 1.1 (locked)
done
$ vim example.c
$ ci -u example.c
example.c,v  <--  example.c
new revision: 1.2; previous revision: 1.1
enter log message, terminated with single '.' or end of file:
>> added a line
>> .
done

プロジェクトの履歴は rlog で見ることができる:

$ rlog example.c

RCS file: example.c,v
Working file: example.c
head: 1.2
branch:
locks: strict
access list:
symbolic names:
keyword substitution: kv
total revisions: 2; selected revisions: 2
description:
example file
----------------------------
revision 1.2
date: 2012/02/15 07:39:16;  author: tom;  state: Exp;  lines: +1 -0
added a line
----------------------------
revision 1.1
date: 2012/02/15 07:36:23;  author: tom;  state: Exp;
Initial revision
=============================================================================

また、rcsdiff -u で2つのリビジョン間の統一された diff フォーマットのパッチを取得することができる:

$ rcsdiff -u -r1.1 -r1.2 ./example.c 
===================================================================
RCS file: ./example.c,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- ./example.c 2012/02/15 07:36:23 1.1
+++ ./example.c 2012/02/15 07:39:16 1.2
@@ -4,6 +4,7 @@
 int main (int argc, char* argv[])
 {
     printf("Hello, world!\n");
+    printf("Extra line!\n");
     return EXIT_SUCCESS;
 }

単純なパッチがバージョン管理の方法として使われなくなったというのは、誤解を招くだろう;単純なパッチは、上記のような形で今でも非常によく使われているし、中央集権型と分散型の両方のバージョン管理システムでも、重要な位置を占めている。

CVS と Subversion

複数の開発者によってコードベースに加えられた変更を解決するという問題に対処するために、集中型バージョンシステムが開発された。最初に開発されたのはConcurrent Versions System (CVS)で、後に少し進んだSubversionが開発された。 これらのシステムの中心的な特徴は、リポジトリを含むセントラルサーバーを使用することで、そこから任意の特定の時間やリビジョンにおけるコードベースの権威あるバージョンを取得することができます。これらはコードの作業コピーと呼ばれる。

これらのシステムでは、システムの基本単位は チェンジセット であり続け、ユーザーに対してこれらを表現する最も一般的な方法は、以前のシステムで使われていた典型的な diff フォーマットでした。どちらのシステムも、状態から状態への実際のファイルそのものではなく、これらのチェンジセットの記録を保持することで機能する。

この世代のシステムで導入された他のコンセプトは、プロジェクトを分岐させることで、同じプロジェクトの別々のインスタンスを同時に作業し、適切なテストとレビューを行いながらメインライン、つまりトランクにマージできるようにすることであった。同様に、ソフトウェアのリリース時にコードベースの状態を表す特定のリビジョンにフラグを付けるために、タグの概念が導入された。merge という概念も導入され、ファイルに加えられた相反する変更を手作業で調整するようになった。

Git と Mercurial

次世代のバージョン管理システムは、分散型または分散型システムであり、コードの作業コピー自体にプロジェクトの完全な履歴が含まれているため、プロジェクトに貢献するための中央サーバーに依存しない。オープンソースでUnixフレンドリーな環境では、GitとMercurial、そしてそのクライアントプログラムであるgithgが傑出したシステムです。

この2つのシステムでは、pushpullmergeという操作でチェンジセットをやり取りします。この分散型システムによって、非常に複雑でありながら厳密に制御された開発のエコシステムが可能になります。Gitはもともと、Linuxカーネルの開発を管理できるオープンソースのDVCSを提供するために、リーナス・トーバルズによって開発されました。

GitとMercurialはどちらも、CVSやSubversionとは操作の基本単位がチェンジセットではなく、圧縮を使って保存された完全なファイル(blob)であるという点で異なります。このため、1つのファイルのログ履歴や2つのリビジョン間の差分を見つけるのに若干手間がかかりますが、git log --patch の出力は、diff が最初に使われてから40年ほど経った今でも、おなじみの各リビジョンに対する統一された diff の出力を保持しています:

commit c1e5559ddb09f8d02b989596b0f4100ad1aab422
Author: Tom Ryder <tom@sanctum.geek.nz>
Date:   Thu Feb 2 01:14:21 2012

Changed my mind about this one.

diff --git a/vim/vimrc b/vim/vimrc index cfbe8e0..65a3143 100644
--- a/vim/vimrc
+++ b/vim/vimrc
@@ -47,10 +47,6 @@
 set shiftwidth=4
 set softtabstop=4
 set tabstop=4

-" Heresy
-inoremap <C-a> <Home>
-inoremap <C-e> <End>
-
 " History
 set history=1000

この2つのシステムは、機能やコマンドセットでさえもかなり重複しており、どちらを使うべきかという問題はかなりの議論を引き起こします。 それぞれの入門書としては、Scott Chacon著のPro GitとJoel Spolsky著のHg Initがあります。

おわりに

これは Unix as IDEシリーズの最後の投稿です。GNU/Linuxのシェルの中だけで利用できる基本的なツールについて、プロフェッショナルなIDEが提供するすべての基本的な機能の迅速な調査を提供しようとしました。しかし、GNU/Linuxマシンでの開発に不慣れな人には、シェルがいかに包括的な開発環境となりうるか、そしてすべてがフリーで非常に成熟した標準的なソフトウェアツールであることを、ある程度理解してもらえたと思います。