はじめに
この環境では uname
コマンドで Darwin と出力されたから、FreeBSD 版のコマンドを使っているはずだ。そう考えてはいないでしょうか? 残念ながらそれは間違いです。uname
コマンドを使って環境の情報を調べることはできませんし、環境がわかってもコマンドの種類はわかりません。そもそも uname
コマンドで知ることができるのはカーネルの情報です。環境の名前ではありません。
Ubuntu上のDockerでAlmaLinuxを起動してみろ!
uname
コマンドで知ることができるのはカーネルの情報です。Docker はその仕組み上ホストのカーネルを共有しています。つまり Ubuntu で AlmaLinux を起動してもカーネルは Ubuntu のままです。
$ uname -a
Linux server 5.15.0-112-generic ↩
↪ #122-Ubuntu SMP Thu May 23 07:48:21 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
$ docker run -it almalinux
[root@1dd18009780f /]# uname -a
Linux 1dd18009780f 5.15.0-112-generic ↩
↪ #122-Ubuntu SMP Thu May 23 07:48:21 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
↑
Ubuntuを使っている
uname
コマンドは環境の名前を知るためのコマンドではありません。
Alpine Linux というディストリビューションがあります。これは主に BusyBox を使っており GNU コマンドを使用していません。もちろん Alpine Linux を起動しても Ubuntu だと判断されますが、使われているコマンドは GNU コマンドよりも大きく簡略化された機能しか持ち合わせていません。
$ docker run -it alpine
# uname -a
Linux d220b64aebdc 5.15.0-112-generic ↩
↪ #122-Ubuntu SMP Thu May 23 07:48:21 UTC 2024 x86_64 Linux
# ps --help
BusyBox v1.36.1 (2023-11-07 18:53:09 UTC) multi-call binary.
Usage: ps [-o COL1,COL2=HEADER] [-T]
Show list of processes
-o COL1,COL2=HEADER Select columns for display
-T Show threads
少し面白いディストリビューションとして Chimera Linux というものがあります。これはカーネルに Linux を使っているものの、ユーザーランドはすべて FreeBSD のものとなっています。
$ docker run -it chimeralinux/chimera
# uname -a
Linux 2ed1dc55bfe2 5.15.0-112-generic #122-Ubuntu SMP Thu May 23 07:48:21 UTC 2024 x86_64
# sort --version
2.3-FreeBSD
# sort --help
Usage: sort [-bcCdfigMmnrsuz] [-kPOS1[,POS2] ... ] [+POS1 [-POS2]] [-S memsize]
[-T tmpdir] [-t separator] [-o outfile] [--batch-size size] [--files0-from file]
[--heapsort] [--mergesort] [--radixsort] [--qsort] [--mmap] [--parallel thread_no]
[--human-numeric-sort] [--version-sort]] [--compress-program program] [file ...]
# date --help
date: unrecognized option: -
usage: date [-jnRu] [-I[date|hours|minutes|seconds|ns]] [-f input_fmt]
[ -z output_zone ] [-r filename|seconds] [-v[+|-]val[y|m|w|d|H|M|S]]
[[[[[[cc]yy]mm]dd]HH]MM[.SS] | new_date] [+output_fmt]
歴史的な理由から、FreeBSD 版の sort
コマンドは、GNU 版と同じように --version
オプションや --help
オプションを持っているという点も目の付け所です。
よりマシな os-release ファイル
環境の名前を知る場合、os-release
ファイルを参照するのがよりマシな方法です。os-release
ファイルのパスは /etc/os-release
または /usr/lib/os-release
のどちらかです。前者がある場合は前者を優先して使い、後者と内容をマージしてはいけません。詳細は https://www.freedesktop.org/software/systemd/man/latest/os-release.html を参照してください。
os-release
は systemd 関連のファイルですが、単なるテキストファイルなので FreeBSD や Solaris 11 でも採用されています。ナンセンスな lsb_release
コマンド(Debian版はPythonスクリプト)に代わるものです。ただしすべての環境に存在するわけではないことに注意してください。
os-release
はシェルスクリプトで読み込めるようになっており、先のリンク先でも以下のようなサンプルが提示されているので、.
コマンドで読み込んでも構わないでしょう。/etc/os-release
もしくは /usr/lib/os-release
に限ればシステムファイルなので不正に書き換えられる心配はありません(もし不正に書き換えられているのであれば、すでにシステムは侵入されておりそれどころではない)。他の言語での読み込み方も書いてあります。
#!/bin/sh -eu
# SPDX-License-Identifier: MIT-0
test -e /etc/os-release && os_release='/etc/os-release' || os_release='/usr/lib/os-release'
. "${os_release}"
echo "Running on ${PRETTY_NAME:-Linux}"
if [ "${ID:-linux}" = "debian" ] || [ "${ID_LIKE#*debian*}" != "${ID_LIKE}" ]; then
echo "Looks like Debian!"
fi
しっかし、このコード無駄にややこしいし、os-release
ファイルが存在しないシステムのことが考慮されてないですね。代わりに以下のように書き換えました。こういうのでいいんだよ、こういうので。
#!/bin/sh
set -eu
if [ -e /etc/os-release ]; then
. /etc/os-release
elif [ -e /usr/lib/os-release ]; then
. /usr/lib/os-release
fi
echo "Running on ${PRETTY_NAME:-Linux}"
case "${ID:-linux}:${ID_LIKE:-}" in
*debian*) echo "Looks like Debian!"
esac
さて、os-release
ファイルは環境を調べるのに便利なファイルですが、os-release
ファイルを使って使うコマンドを調べてもいけません。uname
コマンドを使うよりもマシなだけです。
各環境のコマンドの種類を判別する方法
そもそも各環境で使われているコマンドは、OSの名前なんかと関係ありません。macOS に GNU コマンドをインストールできますし、ユーザーは自由にコマンドをインストールできます。環境変数 PATH
を変更することで、macOS なのにまるで Linux のような GNU コマンドベースの環境を作ることも可能でしょう。
つまり、uname
コマンドや os-release
ファイルを使って、GNU コマンドか BSD コマンドかを判別してはいけないということです。それに現在 BSD コマンドなんてものはもう存在しないんです。BSD Unix という名前の Unix の開発は終了しました。FreeBSD 版、NetBSD 版、OpenBSD 版、全部違います。macOS 版は FreeBSD 版に近いですが、独自の修正が行われているため、FreeBSD 版と同じという前提もできません。
Alpine Linux は GNU コマンドではなく BusyBox コマンドです。
Chimera Linux は GNU コマンドではなく FreeBSD コマンドです。
じゃあ、どうやって各環境で使われているコマンドを判別するのか?
おわりに
正月で記事を書くのがめんどくさいので、おしまい。
いや別にこれらの方法を使っても構わないんですよ。今どき「どの環境でも動くシェルスクリプト」の価値はそれほど高くありません。自分が使う環境でだけ動けば十分です。ただ uname
コマンドで雑に環境を判別しておいて、どの環境でも動くシェルスクリプトを書いたという主張が、あまりにも「移植性のあるシェルスクリプト」の基本を知らないと言いたかっただけです。
移植性の高いシェルスクリプトを書くには、シェル関数を使って移植性問題を解決するコードと、本質的な処理を行うコードを分離します。移植性問題を解決するコードを本質的な処理を行うコードの中に混ぜ込んで、あちこちに処理を分岐するコードを書いてはいけません。シェル関数を使って移植性問題を封じ込め、本質的な処理を行うコードはまっすぐに処理を実行できるようにします。シェル関数を使って移植性問題を局所化しておけば、当初に想定していない環境にあとから対応するのも簡単です。シェル関数を使わなければ全体を精査しなくてはならなくなります。
POSIX に準拠した移植性のあるシェルスクリプトを書くにはシェルプログラミングの知識が必要です。