【UNIX】
ドットファイルは隠しファイル?
(条件式が作った慣例)

関連記事

1. ドットファイルは隠しファイル?

ホームディレクトリを ls で見ると、.bashrc.ssh.git も表示されません。

ls -a にして初めて姿を現します。
このふるまいを「隠しファイル」と呼びますが、ファイルシステムに「隠す」という属性はどこにもありません。

なぜ . で始まる名前が「見えない」のか。
その答えは、意図された設計ではなく、一行の省略にありました1

1.1. ... という疑似ファイルエントリ

UNIX に階層型ファイルシステムが導入されたとき、各ディレクトリには特別な2つのエントリが作られました2

.   現在のディレクトリ自身
..  親ディレクトリ

cd .. でひとつ上に戻れるのは、このエントリがあるからです。

ところが、ls で一覧を出すとときに、 ... まで表示されてしまうのが邪魔で、隠すことになりました。
本来やるべきことは、

// 本来あるべき条件
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
    continue;Code language: Arduino (arduino)

1.2. アセンブラでの簡易判定

ところが、初期の実装はアセンブラで書かれ、「名前の最初の文字がドットなら、表示しない」という簡略化した判定で済ませていました3

つまり、見ていたのは1文字目だけです。

// 動作していた条件の意味
if (name[0] == '.')
    continue;Code language: Arduino (arduino)

... の両方がこの条件に引っかかるので、動作上は正しく見えます。
ただし、条件の意味は変わっていて、「. で始まる名前を全部除く」に拡張されてしまいました。
この一行の条件式は、二段階でUNIXシステム全体に広がっていくことになります。

2. 暗黙の規約化

一つは、他のプログラムが ls の挙動を真似たことです。

find、シェルのグロブ展開、バックアップツール、ファイル選択ダイアログ。
どれも「. で始まる名前は通常の処理から除く」という動作を暗黙の規約として取り込みました4
特定のプログラムの実装詳細が、UNIXシステム全体の共通認識になっていきました。

2.1. 設定ファイルを隠しておく

もう一つは、利用者側がこのふるまいに意味を見いだしたことです。

「普段は表示されないが、必要なときには普通に扱える場所」として、設定ファイルをホームディレクトリに置くようになったのです。

~/.profile
~/.bashrc
~/.vimrc
~/.ssh/
~/.git/
~/.config/

ドットで始まるファイルは ls で表示されないため日常の作業では邪魔にならず、必要なときは cat ~/.bashrcvim ~/.vimrc で普通に開けるため、設定ファイルに好都合だったのです。

追加の属性もデータベースもなく、ファイル名の先頭一文字だけが規約として定着しました。

2.2. 現在の ls.c に残る痕跡

この歴史は GNU coreutils の ls.c に今も構造として残っています。

ソースコードには ignore_mode という列挙型が定義されています5

enum
{
  IGNORE_DEFAULT,
  IGNORE_DOT_AND_DOTDOT,
  IGNORE_MINIMAL
} ignore_mode;Code language: Arduino (arduino)

lsは、デフォルトのときには IGNORE_DEFAULT が使われ、. で始まる名前をすべて無視します。
-A を付けると IGNORE_DOT_AND_DOTDOT になり、... だけを除いて残りのドットファイルは表示します。
-aIGNORE_MINIMAL で、すべてを表示します。

つまり、50年以上経ったいまも、「省略された実装」がデフォルトとして生きていて6... だけを除く動作は -A オプションで明示する必要があります。

Rob Pike 自身は、特別なつもりがなかった実装の「手抜き」が、隠しファイルという概念を偶然に生み、ホームディレクトリに大量の見えないファイルが積み重なる文化を作ったとして、この経緯を批判的に語っています。

3. 単純な規則を活用する「エレガンス」

設計者から見れば後悔の対象のようですが、ドットファイルという慣習には、一つの「美しさ」があります。

「特別な仕組みを増やさない。
既存の単純な規則で済ませる」。

これは、UNIXの多くのやり方と一致しています。

たとえば、Windowsの隠しファイルは、ファイルシステムのメタデータとして「hidden属性」を持ちます。
ファイルの実体とは別の場所に情報を持つわけです7

一方、UNIXのドットファイルは属性を一切持ちません。
名前の先頭一文字の違いだけなので、cpmvtarrsync、などのツールからも普通のファイルとして、特別扱いのための特別な処理が要りません。

3.1. 専用の仕組みを作らないスタイル

UNIXには似た例が多くあります。

デバイスファイルは /dev/sda/dev/null として、ふつうのファイル名前空間にあり、readwrite でアクセスできます。

パイプも stdinstdout という単純な入出力として抽象化されているので、プログラムは互いを知らなくてもつながります。

終了ステータスは整数一つ。
ヌル終端文字列は \0 で終わる char の配列というだけです。

どれも「専用の仕組みを作らず、既存の概念に乗せる」という発想からきています。
少ない規則で多くの場合をカバーし、ツール同士が知り合わなくても組み合わせられる。
これが、UNIXの「てこの原理」になっています。

3.2. 欠陥が慣習になり、慣習が仕様になる

name[0] == '.' は正確な条件ではありませんでした。

でも、その不正確さが意図せず「準非表示」という名前空間を作り、利用者がその空間に意味を持ち込みました。
設計者の意図と利用者の解釈がずれたまま共存して、気づいたときには仕様になっていました。

ソフトウェアのこういう側面は、図面通りに建つ構造物とは違います。
コードは動き始めた後も、使われ方によって意味が変わります。
バグが慣習になり、慣習がエレガントな規約に見えてくることがある。

UNIXのドットファイルは、その例かもしれません。
設計者が後悔する一行の省略が、「単純な規則で余計な仕組みを増やさない」というUNIX的思考と、たまたまぴったり重なりました。
だから、残ったのです8

  1. この経緯はRob Pikeが2012年にGoogle+へ投稿した文章に由来します。Google+のサービス終了により元の投稿は現在リンク切れですが、Xah Leeによる転載が広く参照されています。 – Origin of Unix Dot File Names (Rob Pike. 2012)
  2. Ken ThompsonとDennis Ritchieらは1969年にPDP-7上で階層型ファイルシステムを実装しました。Rob Pikeの記述では、.. エントリはVersion 2の書き直し時に加わったとされています。 – History of Unix – Wikipedia
  3. Rob Pike が Google+ に書いた投稿によると、「KenかDennisのどちらかがテストを加えた。当時はアセンブラで書かれていた」と記しています。どちらが実装したかは明記されていません。 – Origin of Unix Dot File Names (Rob Pike. 2012)
  4. bashのグロブ展開では * はドットで始まるファイル名にマッチしません。.bashrc を展開したい場合は .* のように明示的にドットを書く必要があります。この挙動はPOSIX標準にも取り込まれています。 – Globbing – Advanced Bash-Scripting Guide
  5. GNU coreutilsはGNU/Linuxシステムの基本的なファイル・シェル・テキスト操作ユーティリティ群で、ls はその中心的なコマンドのひとつです。ignore_mode の定義はGitHub上で現在も確認できます。 – coreutils/src/ls.c at master · coreutils/coreutils
  6. GNU coreutilsのマニュアルには「デフォルトでは、.で始まる名前を持つファイルを一覧に含まない」と明記されています。-A--almost-all)オプションが「...だけを除く」本来の動作に対応します。 – ls invocation (GNU Coreutils 9.11)
  7. FATファイルシステムではディレクトリエントリの属性バイトにHidden(H)フラグが存在し、NTFSでも同様の属性フィールドがあります。ファイル名とは独立した1ビットのメタデータとして実装されているため、ファイル名を変えずに表示・非表示を切り替えられます。 – File attribute – Wikipedia
  8. ホームディレクトリへのドットファイル乱立への現代的な対応として、freedesktop.orgが策定したXDG Base Directory仕様があります。設定ファイルを$XDG_CONFIG_HOME(デフォルト~/.config)、キャッシュを$XDG_CACHE_HOME(デフォルト~/.cache)へ集約することを定めており、多くのモダンなアプリケーションが対応しています。皮肉なことに、この仕様自体もドットで始まるディレクトリを使います。 – XDG Base Directory Specification – freedesktop.org