POSIXコマンドは「どの環境にもあるコマンド」ではないよという話

## はじめに

POSIX コマンドはどの環境にもある（追加インストールの必要がない）コマンドだと思われがちですがこれは間違いです。POSIX コマンドにどの環境にもあるという性質は有りません。POSIX コマンドの中でどの環境にもあるコマンドは実際には半分程度しかありません。

**関連記事** [POSIX準拠 とは本当はどういうことなのか？「POSIXで規定されたものだけを使う」ではありません](https://qiita.com/ko1nksm/items/08228276ce9335592989)

**補足** Linux は POSIX に準拠してないからだという意見もあるかとは思いますが、現実に使われている環境を無視して「どの環境にもある」と主張しても意味はありません。

## 本当にどの環境にもあるコマンドとは？

### 全 POSIX コマンドは 160 個

POSIX コマンドは全部で 160 個あります。そのうち 22 個はシェルにビルトインされているコマンドなのでどの環境にもあると言えます。残りは 138 個のコマンドですが、これらはどの環境にもあるとは限りません。

```sh
# （一般的に）シェルにビルトインされているコマンド（22 個）
alias bg cd command echo false fc fg getopts hash jobs kill
printf pwd read test true type ulimit umask unalias wait

# 外部コマンド （138 個）
admin ar asa at awk basename batch bc c99 cal cat cflow chgrp
chmod chown cksum cmp comm compress cp crontab csplit ctags
cut cxref date dd delta df diff dirname du ed env ex expand
expr file find fold fort77 fuser gencat get getconf grep head
iconv id ipcrm ipcs join lex link ln locale localedef logger
logname lp ls m4 mailx make man mesg mkdir mkfifo more mv
newgrp nice nl nm nohup od paste patch pathchk pax pr prs ps
qalter qdel qhold qmove qmsg qrerun qrls qselect qsig qstat qsub
renice rm rmdel rmdir sact sccs sed sh sleep sort split strings
strip stty tabs tail talk tee time touch tput tr tsort tty
uname uncompress unexpand unget uniq unlink uucp uudecode
uuencode uustat uux val vi wc what who write xargs yacc zcat
```

### 必須コマンドは 111個

実は POSIX コマンドにはオプションとされているコマンドがあります。 たとえば C コンパイラである [`c99`](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/c99.html) コマンドは [C-Language Development Utilities](https://pubs.opengroup.org/onlinepubs/9699919799/help/codes.html#CD) であり、この機能はオプションであると書かれています。すなわち POSIX に準拠した環境であっても C コンパイラが必ずあるとは限らないということです。

> [CD] C-Language Development Utilities
> The functionality described is optional.
> 
> Where applicable, utilities are marked with the CD margin legend in the SYNOPSIS section. Where additional semantics apply to a utility, the material is identified by use of the CD margin legend.

C コンパイラがなくてもクロスコンパイラを使えばターゲット環境用のバイナリを作ることが出来ます。容量も必要でコンパイルに時間がかかってしまうため、性能の低い組み込み環境で C コンパイラを動かしたいと思う人は少ないでしょう。パソコン上でコンパイルしてターゲットマシンにアップロードした方が快適です。C コンパイラは POSIX 準拠の環境で必ず必要と言えるようなものでは有りません。従ってオプションとなっているのだと考えられます。

このようなオプションのコマンドを取り除いた必須コマンドは以下の 89 個とシェルビルトインコマンドの 22 個の合計 111 個です。

```sh
# POSIX で必須とされている外部コマンド（89 個）
ar at awk basename batch bc cat chgrp chmod chown cksum cmp
comm cp crontab csplit cut date dd df diff dirname du ed env
expand expr file find fold gencat getconf grep head iconv id
join ln locale localedef logger logname lp ls m4 mailx man
mesg mkdir mkfifo mv newgrp nice nohup od paste patch pathchk
pax pr ps renice rm rmdir sed sh sleep sort split strings stty
tabs tail tee time touch tput tr tsort tty uname unexpand uniq
uudecode uuencode wc who write xargs
```

### 最小構成で動かないコマンド

「どの環境にもある」というからには Linux を考慮しないわけにはいきません。また組み込み環境も考慮しなければいけないでしょう。実際の環境を考慮せずに POSIX で規定されているからどの環境にもあるはずだと言った所で机上の空論です。以下のコマンドは POSIX で必須とされているコマンドのうち**最小構成ではインストールされていないコマンド**です。

なお**最小構成の環境として公式の Docker イメージを使っています**。これは OS を普通にインストールした時の最小構成とは異なり、さらに小さい構成となっているはずです。少し特殊な環境と言えるかもしれませんが現実的によく使われている環境です。よく使われている以上これを無視することはできません。

```sh
# debian:11 でインストールされていないコマンド
ar at batch bc crontab ed file gencat lp m4 mailx man
patch pax ps strings time uudecode uuencode write
```

```sh
# centos:8 でインストールされていないコマンド
at batch bc cmp crontab diff ed file lp m4 mailx man
patch pax tabs time tput uudecode uuencode
```

```sh
# busybox:1.35 のアプレットではないコマンド
at batch csplit file gencat getconf iconv join locale localedef
lp m4 mailx newgrp pathchk pax pr tabs tput tsort write
```

```sh
# alpine:3.15 でインストールされていないコマンド
at batch csplit file gencat join locale localedef logname
lp m4 mailx newgrp pathchk pax pr tabs tput tsort who write
```

POSIX 準拠環境と言えるかは微妙ですが Windows での Git 環境 (GitBash) では以下のコマンドも使えません。

```sh
# GitBash でインストールされていないコマンド
logger mesg renice
```

### すべての環境で動くコマンドは 73 個

どの環境（最小構成）でも動く POSIX コマンドは 51 個とシェルビルトインコマンドの 22 個の合計 73 個です。（**注意** これらは私が調べた範囲での話です。ここに上げたコマンドであっても使えない環境もあると思います）

```sh
# （一般的に）シェルにビルトインされているコマンド（22 個）
alias bg cd command echo false fc fg getopts hash jobs kill
printf pwd read test true type ulimit umask unalias wait

# どの環境でもインストールされているだろうと思われるコマンド（51 個）
awk basename cat chgrp chmod chown cksum comm cp cut date
dd df diff dirname du env expand expr find fold grep head
id ln ls mkdir mkfifo mv nice nohup od paste rm rmdir sed
sh sleep sort split stty tail tee touch tr tty uname
unexpand uniq wc xargs

# インストールされていない環境があるコマンド（87 個）
admin ar asa at batch bc c99 cal cflow cmp compress crontab
csplit ctags cxref delta ed ex file fort77 fuser gencat get
getconf iconv ipcrm ipcs join lex link locale localedef
logger logname lp m4 mailx make man mesg more newgrp nl nm
patch pathchk pax pr prs ps qalter qdel qhold qmove qmsg 
qrerun qrls qselect qsig qstat qsub renice rmdel sact sccs
strings strip tabs talk time tput tsort uncompress unget
unlink uucp uudecode uuencode uustat uux val vi what who
write yacc zcat
```

## POSIX コマンドのインストールは POSIX で義務付けられていない

POSIX コマンド全 160 個のうち 87 個のコマンドはどの環境にもあるコマンドでは有りません。もちろんインストールすれば大部分は使うことが出来るでしょう。しかし必ずしもインストールされているとは限りません。

実は POSIX コマンドは必ずインストールされていなければならないとは POSIX で決まっていません。少なくとも私はそのような記述を見たことが有りません。インストールされていなければならないと書いていない以上インストールされてなくても不思議では有りません。

### デフォルトで POSIX に準拠しない構成があっても良い

実は POSIX 準拠の OS であっても、**POSIX に準拠していな環境に設定できても良い**ということが示唆されています。（[参照](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap02.html#tag_02_01_03_01)）

> POSIX System Interfaces
>
> Although all implementations conforming to POSIX.1-2017 support all the features described below, there may be system-dependent or file system-dependent configuration procedures that can remove or modify any or all of these features. Such configurations should not be made if strict compliance is required.

POSIX Shell and Utilities ではなく POSIX System Interfaces の話なので少し違う可能性がありますが、システム依存の設定手順が存在し、これら（POSIX）の機能の一部または全部を削除または変更できる可能性があると書かれています。もちろん厳密に POSIX に準拠させたい場合は適切な設定を行わなければいけませんが、それをデフォルトにしろとは書かれていません。

### 実際に Solaris はデフォルトで POSIX に準拠していない

Solaris は Unix 認証の延長を行っていないため UNIX を名乗ることは出来ませんが、過去に Unix の認証を受けており POSIX に準拠しているということは証明されています。その Solaris のデフォルトが Unix 互換モードの設定となっていることから、デフォルトを POSIX 準拠モードにしなければいけないという要件がないのは明らかです。POSIX 準拠モードに関しては以下のリンク先を参照してください。商用 Unix を使用する時は POSIX 準拠モードにしていないと古い Unix コマンドを使うことになり無駄に互換性問題に苦しめられることになります。

- [「え？UNIXなのにPOSIXに準拠してないの！？」シェルスクリプト実行環境をPOSIX準拠モードに変更して互換性を上げる方法 (Solaris, AIX, HP-UX, Linux, macOS, BSD)](https://qiita.com/ko1nksm/items/6ebdcba7050da2f3c585)

また Solaris 11 の環境では `c99`（`c89`, `cc`）がインストールされていなかったので、オプション機能だけかもしれませんが POSIX コマンドをデフォルトでインストールしなければいけないという要件はなさそうです。おそらく POSIX または UNIX 認証の条件は、適切に設定されている環境でテストが通ることであって、デフォルト環境ではないと思われます。もしデフォルトで POSIX に準拠していなければならないという要件があったとしたら、OS のアップデートで後方互換性が保てずそれまでのソフトウェアが動かなくなってしまうのでそのような要件があるとは思えません。

## さいごに

もし環境への追加のコマンドインストールなしで動いて欲しいシェルスクリプトがある場合、この記事であげた 73 個コマンドしか使ってはいけません。POSIX コマンドのうち、特に注意が必要なコマンドを以下に挙げます。

```sh
at bc c99 cal compress crontab csplit file getconf iconv join
logger make man more patch pax ps strings tput uncompress vi
```

これらは OS の最小構成では動かない可能性があるコマンドです。C 言語のコンパイルや `make` はできません。圧縮展開もできません。`at` コマンドや `crontab` での時間を指定しての実行はできません。`iconv` を使った文字コード変換、`ps` コマンドを使ったプロセス一覧の確認、`join` を使った RDBMS 的な処理、テキストエディタもありません。

単純にインストールすればよいだけの話ではありますが、インストールするならばもっと多くのコマンドが利用可能です。またどっちみちシェルスクリプトをターゲットマシンで動かすには、ターゲットマシンにシェルスクリプトをコピーしなければいけないわけで、それならバイナリの実行ファイルをコピーしても同じことです。そう考えると POSIX コマンドに固執することに大きなメリットがあるとも思えません。POSIX コマンドと言うか、この記事で挙げたどの環境にもあるコマンドだけを使えば、動かすまでの手順が少し楽になる程度のものです。

## 追記 問題の解決方法はインストールするだけ

**2022-03-27 追記**

この記事は単に POSIX コマンドは「どの環境にもあるコマンドではないよ」と伝えることだけが目的だったのですが、これを読んだ初心者は不安になるのでは？という意見があったため追記します。この記事は POSIX コマンドが最小構成でインストールされているものだと「初心者が勘違いしないため」に書いているため、勘違いしてしまうようではこの記事の目的から大きく外れてしまいます。

結論を先に書くと、この記事をみて「あるコマンドが動かない環境があるかもしれないなんて困った」と思うのであれば、その**解決方法は単にコマンドをインストールするだけ**なので**不安になる必要はありません**。

自分でシェルスクリプトを作ったのであれば自分で足りないコマンドをインストーするだけですし、そうでない人でも各自で足りないコマンドをインストールだけで事足ります。必要なのはインストール手順ぐらいなもんです。もしコマンドをインストールする権限がない場合は管理者や担当者にお願いすればすみます。サーバー系アプリとは異なり大抵のコマンドは設定不要でインストールするだけなので、OS 標準のパッケージでインストールできるのであれば断られることも少ないでしょう。

参考までにコマンドのインストールがどれくらい簡単なのかを例示しておきます。これ以外の OS でも似たようなものです。

```sh
# Debian
apt-get update # パッケージ情報を新しくする
apt-get install gcc # gcc パッケージをインストールする

# CentOS
dnf install gcc
# または yum install gcc 
```

この記事の範囲における罠はコマンドがないから動かないというだけです。この程度の罠よりも POSIX コマンドの非互換性の方がインストールするだけでは解決しないのでもっと大変です。POSIX コマンドの非互換性についての話は私が書いている別の記事を参照してください。多すぎてここで説明しきれるものではありません。一つだけアドバイスするなら POSIX コマンドに非互換性に対応するよりも可能であれば GNU Coreutils （事実上どの環境でも動きます）をインストールしてそれを使ったほうが良いです。殆どのケースにおいて POISX コマンドに非互換性の罠を回避するためのバッドノウハウを駆使するのは時間の無駄です。

どうしてもコマンドをインストールするという手段が使えない場合、この記事で書いた「動かないかもしれないコマンド」を避ければだいたいの環境で動くでしょう。というかインストール出来ないのですから避ける以外の選択肢がありません。ただし「動かないかもしれないコマンド」を避けたとしても、すべての環境で動くという保証はありません。環境にインストールされているコマンドは違う上に、それはわからないのですから 100 % の環境で動くという保証はできません。

もう一つ重要な話として「インストールすれば問題解決」するのだから **POSIX コマンドに縛られる必要はない**という話があります。POSIX コマンドでなくともインストールすれば使えるというのは同じことです。POSIX という標準規格が誕生し多くのアプリケーションは多くの環境で動くようになりました。これが POSIX の成果ですから利用しない理由はありません。**都合がいいことに POSIX コマンド以外の多くは開発している所が一箇所であるため互換性問題が（基本的に）発生しません**。依存コマンドのインストールを容易にするためにシェルスクリプトが依存している全ての外部コマンドをドキュメントに書いておくのが良いと思います。POSIX コマンドであってもインストールされていな可能性があるので万全を期すのであれば書いたほうが良いです。シェルスクリプトの最初でインストールされているか不安なコマンドの有無をチェックしたりすればもっと親切でしょう。

参考として「どうしてもコマンドをインストールすることが出来ないが、未知の環境も含め可能な限りどの環境で動くシェルスクリプトを書きたい」という人へのアドバイスです。それは可能な限り外部コマンドの使用を避け、シェルスクリプトとシェルにビルトインされているコマンドでできることは、すべてシェルスクリプトだけで実装するという方法です。依存コマンドが少なければ少ないほど、どこでも動く可能性は高くなります。この考え方を採用すると `sed` や `awk` ですら不要になります。もちろんこれは大変な方法ですが、実現不可能な話では有りません。[実例](https://github.com/shellspec/shellspec#requirements)を紹介します。ただし**これは推奨する方法ではありません**。デスクトップやサーバーでは足りないコマンドはインストールして解決できるはずです。

繰り返しますが **POSIX コマンドであろうがなかろうが、単に必要なコマンドをインストールするだけで問題は解決**します。だからこの記事を読んでも不安になる必要はないのです。あとは動かす環境のことを十分把握していれば OK です。