🐚

shell の互換性を意識する: POSIX 互換について

に公開

この記事はドワンゴ Advent Calendar 2025 の 21 日目です。年末に .zshrc などをいじる前に読んでもらえると役に立つかと思います。

近年、 claude code などの Coding Agent の登場によって、 shell script の互換性が重要になってきています[1]
今回は shell の互換性に焦点を当てて、 POSIX 互換について考えてみようと思います。

1. そもそも POSIX とは?

POSIX (Portable Operating System Interface) とは、 Unix 系 OS の標準仕様として IEEE が策定した規格です。
主に意識するのは shell ですが、そもそもとしては OS レベルのシステムコールや C API (C 言語からシステムコールを呼び出すための API) 、環境変数 ENV などが定められており、これらは Unix 系 OS 間でのアプリケーションの移植性を確保するための仕様です。
(他にも /tmp などのディレクトリもいくつか仕様にありますが、これ自体が直接移植性に与える影響は少なそうに見えます。)

POSIX 準拠な OS としては macOS や Solaris が有名で、 Linux、 FreeBSD は互換です。
Linux が非準拠なのは一部未実装であるためであること、認証費用が高額であること、ディストリビューションごとの自由度が高いことなどがありますが、 OS として POSIX 準拠である必要性が無いという判断も考えられます。

POSIX にもバージョンがいくつかあったりなどするのですが、私自身が POSIX にとても詳しいわけではなく、また正誤確認しながら書いていると日が暮れてしまうのでここでは省略します。
気になる人は POSIX 原理主義 等で調べると、詳しい人のブログなどが出てきます。

2. そもそもコマンドとは

そもそも Unix 系 OS で使っているコマンドとはなんでしょうか。これらは大別すると実行可能ファイル (いわゆるバイナリ) として存在するものと、 shell により提供されるもの があります。
例えば ls/bin/ls に実行可能ファイルとして存在します。一方 shell により提供されるものはビルトインのコマンド (cd など) や関数、エイリアスなどがあります。
(shebang を書いて実行可能となっているファイルは、ややこしいのですが shell script ではなくシステムコール経由でカーネルが解釈しています。これを実行可能ファイルとしているものも多くあります。)

この中で、 shell に実装されているコマンドの中にも POSIX 準拠のものとそうでないもの、 ls など Unix 系に広く採用されている実行可能コマンドにも POSIX 準拠のものとそうでないものがあります。
こういった違いをあまり意識して使っていることは少ないのではないかと思うので、以降で掘り下げていきます。

3. shell と POSIX 互換

POSIX の仕様に完全に一致した実装ではないもの (私が知る限りでは、多くのものがそうである) は、 一般に POSIX 互換と呼ばれます。
例えば bash は POSIX 互換の shell ですが、例えば function キーワードすら POSIX 標準ではない独自拡張です。事実、 POSIX 完全準拠である[^2] sh には function キーワードはありません

function キーワードを仕様した shell script が /bin/sh で動いたことがあるけど、という人もいるかもしれませんが、 OS によっては bash などへの symlink になっています。なので、手元では #/bin/sh の shebang で動いたのに Docker では動かない、ということが起こります。
bash にも zsh にも POSIX 互換モードはありますが、これを有効にすると function キーワードや [[ (これは実は [[ <式> ]] という構文ではなく、 [[ という bash の組み込みコマンド) さえ使えなくなってしまうため、多くの人は (私も含めて) 有効にしていないと思います。 (/bin/sh で動く bash は POSIX 互換モードになっている OS もあります)

他にも BusyBox の ash や Debian 系の dash は POSIX 準拠です。

一方、普段使いの shell として有名な bash や zsh は POSIX 互換であり、 fish に至っては基本文法から異なるため、互換がないと言って良いでしょう。
(準拠以外の場合の互換性ってどこまで、という話はありますが、ひとまず POSIX のスクリプトがほぼ動くかどうか、という観点で見ると、基本文法からして fish や PowerShell は互換でない、と言ってよいかと思います。)
zsh は bash の機能の多くを踏襲しており互換性が高いですが、完全に互換というわけではありません。特に POSIX 準拠でない bash の仕様のいくつかは zsh とは異なり、構文エラーとなるならばまだよく、異なる解釈・実行結果をもたらすこともあります。

この関係性を、 sh と bash と zsh において、表に整理すると以下のようになります。

実行環境 \ script sh bash zsh
sh
bash
zsh

この表から、 bash も zsh でも動く sh 以外は相互互換ではいかないことが分かります。

4. bash でも zsh でも動く shell script の難しさ

ここからが本題というか、私が一番伝えたい、できればみなさんに意識してほしいところです。
「普段使いは zsh だが、 claude code は bash で動くので、両方で動く rc ファイルを作りたい」「手元が zsh だが bash で動く想定の shell script を書く時に事前に違いを意識したい」のようなことがあると思います。
このときに調べるべきこととして、 bash や zsh それぞれの固有のビルトインでないか調べるというのもありますが、まず POSIX 互換であるかどうかを知っていると、格段に楽になります。

例えば私は nohup を使う関数を .zshrc に書こうとしたとき、「これは bash/zsh のビルトインなのか」「GNU coreutils なのか」「POSIX 準拠なのか」を確認し、「POSIX 標準」の「GNU coreutils」であることが分かりました。これは POSIX 標準なので GNU coreutils のない環境 (FreeBSD 等) でも動く(だろう)ことを確認しました。

5. まとめ

今回は shell に限った POSIX 互換の話をしましたが、 POSIX 互換でない GNU coreutils のコマンドもあります。また、 lsxargs などの有名なコマンドでも GNU と BSD でオプションが異なることもあります。
このような環境ごとの違いを意識して shell script を記述できると移植性が高まるため、余裕があれば気にかけられると良いでしょう。

脚注
  1. 筆者が勝手に言っているだけです ↩︎

Discussion