Mac のターミナルに `CAL` と打ち込んだときの挙動を追った話

みなさん、こんにちは。

最近、曜日や日付の感覚とかがあまり意識できない不健康な生活をしております。 @takano32 です。

そんな私は月曜日とか休日明けとかによくターミナルに `cal` って打ち込んで日付を確認したりします。
`date` でもいいし、 Mac なら右上に日付が出ていたりするわけですが、習慣なんだからしょうがない。

ところがですね。今日、Mac の仮想端末に Caps Lock が入ったまま `CAL` と打ってしまったんですよ。
「ノー、サッチコマ…えっ？なんかカレンダーでてる！」というね。

そこまでなら「あー、HFS+ のファイルシステムってデフォルトのフォーマットでは大文字と小文字を区別しないから、小文字の `cal` が呼ばれたのかな」って思うわけですが、よく見ると変なんですよ。

# 小文字 `cal` と 大文字 `CAL` の出力

小文字 `cal` のカレンダーはこんな感じ

```
$ cal
     11月 2016
日 月 火 水 木 金 土
       1  2  3  4  5
 6  7  8  9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
```

大文字 `cal` のカレンダーはこんな感じ

```
$ CAL
    11月 2016
月     7 14 21 28
火  1  8 15 22 29
水  2  9 16 23 30
木  3 10 17 24
金  4 11 18 25
土  5 12 19 26
日  6 13 20 27
```

なんと、 `cal` と `CAL` ではカレンダーが転置するという知らなかった挙動をしているもんで、ビビりまくりでしたわ。
訳わかんなくて、取りあえず `CAL` を探してみました。

# 大文字の `CAL` はどこにある？

```
$ which -a CAL
/usr/bin/CAL
```

なるほど？いや、でもおかしいでしょ。`/bin` に大文字のコマンドなんか普通ない。
小文字の `cal` についても念のため確認してみます。

```
$ which -a cal
/usr/bin/cal
```

ほう。じゃあ、なんだ、大文字の `CAL` があるのか？と `man CAL` してみました。

```
$ man CAL
No manual entry for CAL
```

は？ないの？

・・・

落ち着いて `/usr/bin/CAL` から読めそうな部分を以下のような形で出してみました。

```
$ strings /usr/bin/CAL | less
```

先頭の方を読んでみると

```
$FreeBSD: src/lib/libcalendar/calendar.c,v 1.4 2001/09/30 21:09:57 dillon Exp $
$FreeBSD: src/lib/libcalendar/easter.c,v 1.5 2001/09/30 21:09:57 dillon Exp $
@(#)PROGRAM:cal  PROJECT:misc_cmds-33
setlocale
POSIX
ASCII
US-ASCII
Jejm:ops:wy
%s: invalid country code
year %d not in range 1..9999
%s is neither a month number (1..12) nor a name
%c%s %-15s%4d-%02d-%02d     %c%s %-15s%4d-%02d-%02d
```

ん、これは小文字の `cal` と同じライブラリを使っているということか？と思ったりしたわけですが、後半まで読むと何かおかしいことに気づきました。

```
usage: cal [-jy] [[month] year]
       cal [-j] [-m month] [year]
       ncal [-Jjpwy] [-s country_code] [[month] year]
       ncal [-Jeo] [year]
```

いやいや、これは `man cal` するとでてくる小文字 `cal` のヘルプだぞ。

再び HFS+ は大文字と小文字を区別しないな、ということを思い出し、もしかして `ls` がファイルシステムから探してきたときに大文字でも小文字でもヒットしてしまうのか、という予感がしてきます。

それを確認します。

```
% ls -al /usr/bin/CAL
-r-xr-xr-x  1 root  wheel  31824 10 21 18:07 /usr/bin/CAL
```

大文字のバイナリがリストされました。

```
$ ls -al /usr/bin/cal
-r-xr-xr-x  1 root  wheel  31824 10 21 18:07 /usr/bin/cal
```

ここでバイナリのサイズが同じことに気づきました。 i-node 番号が同じなんでは？という推測になりました。

```
$ ls -i /usr/bin/cal
47343084 /usr/bin/cal
$ ls -i /usr/bin/CAL
47343084 /usr/bin/CAL
```

ビンゴ。
しかし、ハードリンクしているのか？気持ち悪くないか？という疑念に駆られます。
まあ、見れば分かることよ、と見てみると。

```
$ ls -li /usr/bin/cal
47343084 -r-xr-xr-x  1 root  wheel  31824 10 21 18:07 /usr/bin/cal
```

1 です。つまり、ハードリンクはされていない、ということですね。
じゃあ`CAL`よ、お前はどこの誰でどういうことなんだ、というと「HFS+ は大文字と小文字を区別しない」ので同じものなんですね。つまり **大文字の`CAL`を呼び出したときは小文字の`cal`が呼ばれている**ってことですね。すなわち **HFS+ の標準的なフォーマットで大文字のコマンド呼び出しと小文字のコマンド呼び出しは区別されず、コマンドが呼ばれる** ってだけです。まあ、これは Mac のターミナルで `LS` とか打てば分かります。ちゃんと `ls` と同じ出力がでます。そして、今回ここまで調べてしまった元凶としては **Mac の `cal(1)` は大文字で呼び出すと小文字の呼び出しとは挙動が変化する**ということだったんですね。何その機能…

えっ、でもわざわざこんな挙動が用意されているということは `CAL` って普通の Linux でも使えるのか？と思ったので、今度は Ubuntu で `CAL` を使おうとしてみました。

```
$ CAL
-bash: /usr/bin/CAL: No such file or directory
```

ないです。そんなモノはないと言っております。

ふーむ。もしかして、自身のファイル名によって挙動が異なるバイナリという busybox のような挙動になっているのでは？と思ったりしたので試してみました。Ubuntu の `cal(1)` も `man cal` すると BSD 由来であることが分かりますのでその可能性はなくはなさそうです。

```
$ sudo ln /usr/bin/{cal,CAL}
$ CAL
    11月 2016
日      6 13 20 27
月      7 14 21 28
火   1  8 15 22 29
水   2  9 16 23 30
木   3 10 17 24
金   4 11 18 25
土   5 12 19 26
```

なんと Mac で `CAL` をしたときと同じ挙動をしました。

その後、Ubuntu に作成した大文字のハードリンク `CAL` はスタッフがおいしく `sudo rm -f` しました。

# 追記

コメントにて `CAL` ではなくても `cal` を `cal` ではない名前で起動すると `ncal` という `cal` を転置するコマンドの動作になるということを教えていただきました。

# 所感

大文字と小文字を区別しない状態でフォーマットした HFS+ は便利なんだか不便なんだか分からないし、転置された `cal(1)` の出力が同じバイナリを大文字で呼び出すと出てくるって、誰か得してるんですかね…ってか、どこかで使われてるの？

あと、私、ここまで久しぶりにターミナルの挙動で「？？？」ってなったの、すごい久しぶりなんですけど、世の中の普通のエンジニアはこういうことが発生してもビビらないくらい賢いんでしょうかね…なんか、自分の将来が心配になってきます…

ヒマなときに FreeBSD あたりで `cal(1)` のコードを読んでみようと思います。