3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

unameコマンドでGNUコマンドかBSDコマンドかを判別してはいけない理由

Last updated at Posted at 2025-01-02

はじめに

この環境では 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 に準拠した移植性のあるシェルスクリプトを書くにはシェルプログラミングの知識が必要です。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?