Closed11

macOSのターミナル.appで新規のウィンドウ/タブを開いた時に表示される最終ログイン日時の書式は変更できない

四ツ山伊吹四ツ山伊吹

Q: なんのこと?

A: この赤枠のメッセージ日時の部分こと。

"Last login: Tue Sep 12 14:07:59 on ttys001" と表示されたターミナルの画面
Last login: Tue Sep 12 14:07:59 on ttys001と表示されたターミナルの画面

このメッセージを例えば、

  • Last login: 2023/09/12
  • Last login: 2023-09-12 14:07:59 on ttys001

といった具合に設定ファイルなどを使って変更できないということ。

四ツ山伊吹四ツ山伊吹

Q: なんで変更できないの?

A: login(1) のコードに組み込まれているから。

やや長い機序の説明

macOSのターミナル.appは新規のセッションを開始する時(つまり、新しいウィンドウまたはタブを開いた時)、login(1) に適当な引数を渡してシェルを起動するとみられている。タイトルにて示した場面では、シェル(特にUnix shell)がlogin(1) の引数として渡され、ログインシェルとして起動される。


シェルを実行しているターミナル.appのインスペクタ

コマンドの例(赤枠内)
login -pf <user> /bin/sh

大事なのは、ターミナル.appがシェル(ここでは/bin/sh)を直接起動するのではなく、login(1) がシェルを起動させているということ。言い換えれば、login(1) のプロセスにシェルのプロセスが子プロセスとして属するということになる。

ここで、login(1) の実装を見てみる(枝はmacOS 13.5を指しているが、やっていることはどれも大筋で変わりはないはず)。

https://github.com/apple-oss-distributions/system_cmds

https://github.com/apple-oss-distributions/system_cmds/blob/system_cmds-953.140.2/login.tproj/login.c#L1348-L1382

リンク先の、dolastlog関数の中で、

(void)printf("Last login: %.*s ",
    24-5, (char *)ctime(&lastlog.ll_tv.tv_sec));

となっているのが該当の処理だろう。日付の書式は (char *)ctime の部分で決め打ちされてしまっている感じであり、実行時に外部から変更できる余地は無さそうに見える。

このdolastlog関数はmain関数の中にふつう必ず呼び出される位置にあるため、ターミナル.appがシェルを起動するたびに画面にメッセージが表示されるし、日時の書式も変更できないというわけになる。

四ツ山伊吹四ツ山伊吹

類似のコードで実験

ctime.c
#include <stdio.h>
#include <time.h>

int main(int argc, char const *argv[])
{
    time_t now = time(NULL);
    (void)printf("Last login: %.*s\n", 24 - 5, (char *)ctime(&now));
    return 0;
}
ターミナル(コンパイル)
$ make ctime
cc     ctime.c   -o ctime
ターミナル(実行)
$ LC_ALL="C" ./ctime
Last login: Wed Sep 13 17:01:26

$ LC_ALL="ja_JP.UTF-8" ./ctime 
Last login: Wed Sep 13 17:06:06

$ LC_ALL="zh_TW.UTF-8" ./ctime 
Last login: Wed Sep 13 17:13:00

ローケールが何であろうと日時の書式に変わりはない。

四ツ山伊吹四ツ山伊吹

ChatGPTによる解説

提供されたC言語のコードを確認しました。これはシンプルなプログラムで、UNIXタイムスタンプを使用して"Last login"メッセージを表示するものです。プログラムの構造と基本的な要素について説明します。

  1. #include ディレクティブ:

    • #include <stdio.h>: 標準入出力関連のヘッダーファイルをインクルードしています。
    • #include <time.h>: 時刻関連のヘッダーファイルをインクルードしています。
  2. int main(int argc, char const *argv[]):

    • このプログラムは main 関数から始まります。Cプログラムのエントリーポイントです。
    • argcargv はプログラムに渡されたコマンドライン引数の数と値を格納するための引数です。このプログラムでは使用されていません。
  3. time_t now = time(NULL);:

    • time_t 型の変数 now を宣言し、現在のUNIXタイムスタンプを取得して格納しています。time(NULL) は現在の時間を表す数値を返します。
  4. (void)printf("Last login: %.*s ", 24 - 5, (char *)ctime(&now));:

    • printf 関数を使用してフォーマット付きのテキストを出力します。
    • "Last login: %.*s ": フォーマット文字列で、%.*s は可変長文字列を受け取るための指定子です。%.*s の前の 24 - 5 部分は文字列の幅を指定しており、この場合、19文字の幅を持つ文字列が表示されます。
    • (char *)ctime(&now): ctime 関数はUNIXタイムスタンプを人間が読みやすい日付と時刻に変換し、文字列として返します。この文字列が %.*s に挿入されます。
  5. return 0;:

    • main 関数からの正常な終了を示すために0を返します。

このプログラムは、ユーザーの最後のログイン日時を表示する簡単な例です。C言語の基本的な要素、フォーマット指定子、関数の呼び出し、標準ライブラリの使用など、プログラミングの基本的な概念を示しています。プログラムの実行時には、現在の日付と時刻に合わせた "Last login" メッセージが表示されます。

四ツ山伊吹四ツ山伊吹

マニュアル

login(1) のマニュアルにも、ユーザーの最終ログイン日時を表示する旨の記述があることがわかる。

Immediately after logging a user in, login displays the system copyright notice, the date and time the user last logged in, the message of the day as well as other information.

四ツ山伊吹四ツ山伊吹

Q: なんとかいじれないの?

A: 限定的にできる。

先ほどのマニュアルによれば、これに続けて

If the file .hushlogin exists in the user’s home directory, all of these messages are suppressed.

とある通り、.hushlogin というファイルをユーザーのホームディレクトリに配置することでメッセージ自体を隠すことができる。これはStack Overflowほか多くの質問サイトやブログなどで示されている方法である。

また、login(1) は /etc/motd を関連ファイルとして挙げている(motd とは message-of-the-day の略語)。motd(5) によれば、

NAME

motd -- file containing message(s) of the day

DESCRIPTION

The file /etc/motd is normally displayed by login(1) after a user has logged in but before the shell is run. It is generally used for important system-wide announcements.

Individual users may suppress the display of this file by creating a file named “.hushlogin” in their home directories.

とあるように、/etc/motd ファイルの内容をログイン後に表示できる。これもまたいくつかのサイトでみられる解説。ただしその内容は平文で書かれている通りであるから、冒頭で紹介した "Last login: ..." のように、その日その時のメッセージを逐次生成……という風にはいかない。

四ツ山伊吹四ツ山伊吹

ソースコードがXcodeのプロジェクトとしてそっくり公開されているので、該当箇所を自分好みに修正して自力でビルドし直して再インストールする手はありそう。

四ツ山伊吹四ツ山伊吹

Q: (タイトルで示された以外の)XXXの環境でもそのメッセージ出るよ

A: login(1) を経由して起動し、かつその実装が似た感じであればある

四ツ山伊吹四ツ山伊吹

Q: ログインシェルとして起動してるのにそのメッセージでないよ

A: ログインシェルは必要条件でもないし十分条件でもない。

四ツ山伊吹四ツ山伊吹

Q: Unixシェル以外ではどうなるの?

A: メッセージは表示されない。

ところでUnixシェル以外のシェルというと、さまざまなプログラミング言語の対話環境とかREPLがこれに当たる。実例として、irb (Interactive Ruby Shell) でやってみよう。irb とはマニュアルによれば、「irb is the REPL(read-eval-print loop) environment for Ruby programs.」とのこと。

ターミナル.appで新しいセッションでシェルを起動するには、[メニュー]>[シェル]>[新規コマンド…]から irb の絶対パスを入力して[実行]ボタンを押下する。こうすることで、適当なコマンドをターミナル.appがlogin(1) を介して起動するよう指示したことになる。言い換えれば、login(1) プロセスに適当なコマンドの子プロセスが直に属する格好になる。冒頭の画像もこののやり方で検証している。

macOS 10.14 Mojaveでは /usr/bin/irb をここに指定してやれば良い。


irbを新しいセッションで起動する

そうすると、新しいウィンドウが開き、irb のプロンプトがあらわれる。最終ログインに関するメッセージは見当たらない。


irbを起動したところ

インスペクタを確認してみると、


irbを起動した時のインスペクタ

login(1)(赤枠内)
login -pfq <user> /usr/bin/irb

こうあるように、-q フラグが追加されているのがわかる。このフラグは、「This forces quiet logins, as if a .hushlogin is present.」

このスクラップは2023/09/20にクローズされました