MacとLinuxのコマンドは同じようで違っている

LinuxとMacは、どちらもUNIX系の技術を利用したOSである。最初からデプロイされているコマンドはよく似ているし、その動作も似たようなものだ。しかし、細かい違いも多い。その部分が、LinuxとMacを両方使う場合に気になってくる。

例えば、Linuxディストリビューションでは最初からデプロイされている基本的なコマンド群は、「GNU Core Utilities」というパッケージによって提供されていることが多い。インストールされているパッケージが同じであるため、Linuxディストリビューションではほぼ同じコマンドを最初から使うことができる。

一方、MacはFreeBSDのベースコマンドを基本的なコマンド群として取り込んで使っている。GNU Core Utilitiesで提供されているコマンドも、FreeBSDのベースコマンドも、POSIXが定めるところはほぼ同じなのだが、拡張された機能がそれぞれ異なっている。つまり、同じコマンド名なのだが拡張機能が異なるのだ。

本連載ではこうした違いを吸収する方法をいくつか紹介してきた。Mac用に書き換える方法もあるし、Homebrew経由でGNU Core Utilitiesをインストールして使う方法もある。Linuxをベースに考えるなら、GNU Core Utilitiesをインストールしてこちらを使うように中身を書き換えるほうが使いやすいだろう。うまく行けば、環境変数PATHを書き換えるだけで全てが問題なく動くようになる。

MacとLinuxで処理を切り分ける

同じコマンドが動くようにしたとしても、MacとLinuxとではほかにも違いがある。例えばファイルの配置やディレクトリ構造がLinuxとMacでは異なっている。同じコマンドが動くようにGNU Core UtilitiesやGNU系コマンドをインストールして環境変数PATHを細工したとしても、パスが異なっていればシェルスクリプトはうまく動かないかもしれない。

こうした違いはUNIX系のOSには珍しいことではないので、複数のOSやディストリビューションで動作するシェルスクリプトにはちょっとした“細工”が入っていることが多い。いくつかやり方はあるが、切り分けの基準としてuname(1)コマンドが使われることが多い。

例えば次のスクリーンショットはそれぞれmacOS MontereyとWSL / Windows 11でunameコマンドを実行した結果だ。「uname -a」で長い情報が、「uname -s」で短い情報が得られる。

  • macOS Montereyでunameコマンドを実行したサンプル

    macOS Montereyでunameコマンドを実行したサンプル

  • WSL / Windows 11でunameコマンドを実行したサンプル

    WSL / Windows 11でunameコマンドを実行したサンプル

uname(1)はOSの情報を出力するコマンドだ。出力される内容はOSごとに異なっている。大抵はカーネル名、カーネルバージョン、カーネルリリース、ハードウェア名、プロセッサ種類、ハードウェアアーキテクチャ名、OS名、ノード名などが出力される。オプション「-a」を指定することで、これらの情報が全て表示される仕組みになっていることが多い。

macOS Montereyで「uname -a」を実行

% uname -a
Darwin Mac-mini-M1-2020.local 21.3.0 Darwin Kernel Version 21.3.0: Wed Jan  5 21:37:58 PST 2022; root:xnu-8019.80.24~20/RELEASE_ARM64_T8101 arm64
% 

WSL / Windows 11で「uname -a」を実行

$ uname -a
Linux XPS-13-9305 5.10.60.1-microsoft-standard-WSL2 #1 SMP Wed Aug 25 23:20:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
$ 

unameコマンドは引数なし、またはオプション「-s」を指定するとカーネルの名称のみが出力されるようになっていることが多い。次のような感じだ。

macOS Montereyで「uname -s」を実行

% uname -s
Darwin
% 

WSL / Windows 11で「uname -s」を実行

$ uname -s
Linux
$ 

このコマンドの出力でOSの切り替えができるというわけだ。シェルスクリプトに次のような切り分け処理を加えれば、MacとLinuxでそれぞれ異なる部分の処理を吸収できるようになる。

MacとLinuxで処理を切り替える書き方

case $(uname -s) in
Darwin)
    # Macの処理
    ;;
Linux)
    # Linuxの処理
    ;;
*)
    # それ以外の処理
    ;;
esac

LinuxとLinuxで処理を切り分ける

MacとLinuxという違いに比べれば小さいものだが、Linuxディストリビューションにも結構な違いがある。使っているコマンドは共通であることが多いが、システムを管理するためのコマンドはディストリビューションごとに違っているし、配置されているファイルが違っていたり、パッケージ管理システムが異なっていたりする。同じLinuxであっても上記と同じような切り分けが必要になることもあるのだ。

そんな場合は、次のような感じでLinuxの中でさらに処理を切り分けるような処理を加える。

Linuxディストリビューションでも処理を切り替える書き方

case $(uname -s) in
Darwin)
    # Macの処理
    ;;
Linux)
    # Linuxの処理
    case $(cat /etc/issue 2>&1 /dev/null) in
    Ubuntu*)
        # Ubuntuの処理
        ;;
    *)
        # それ以外の処理
        ;;
    esac
    ;;
*)
    # それ以外の処理
    ;;
esac

Linuxディストリビューションによっては、他のファイルなどを切り分けのきっかけとする必要がある。この辺りは、ケースバイケースで書き換える必要がある。

大切なのは「なるべくシンプルにしておくこと」

OSやLinuxディストリビューションを切り分ける方法はいくらでもあるので、多くのUNIX系OSで動作するシェルスクリプトを書くことはできる。同じOSであってもバージョンによって動きを変える必要のあるものは、同じOSでもインストールされているパッケージなどによって動作を変える必要があるものもある。

ただし、あまり深入りして複雑にするのは考えものだ。シェルスクリプトは短くシンプルで見通しよくしておいたほうが、後で楽になることも多いからだ。複雑にしすぎると、数年後に自分が書き換える段階で頭を抱えることにもなりかねない。状況を見つつ、やりすぎない程度に対応するくらいにしておくと、ほど良く楽ができるようになるだろう。「なるべくシンプルにしておく」――これが一つの指針だ。