はじめに
この記事は私の主観かつ知っていることをまとめたものです。古くからのシェルの専門家とかではなくここ数年の間に必要になった時にその都度調べたものなので間違いとかもあると思います。またタイトルの通りスクリプト言語としての比較です。つまりインタラクティブシェルの機能についての比較はしていません。(そもそも使い込んでるわけじゃないので知らないです。)POSIX シェルに限定しているのも私が他のシェルを詳しく知らないからです。もし今も使われていて(もしくは開発中で)ここに書かれていない POSIX シェルがありましたらコメントで教えて下さい。(※ csh, tcsh, fish は POSIX シェルではありません。)
シェルの系統は少し古いですが「What does it mean to be “sh compatible”?」に投稿されている図が詳しくてわかりやすいです。またこちら「~sven_mascheck/」でも多くのシェルの細かい亜種(variants) についてまとめられています。
独自系統
■ bash - Bourne Again SHell
https://www.gnu.org/software/bash/
1989年に初版リリース。/bin/shやユーザー用の(インタラクティブ)シェルとして広く使われている。配列などプログラミングを行う上で便利な拡張機能を実装している。それらは(しばしば悪い意味で)bashism と呼ばれているが多くは ksh からの移植である。バージョン番号は $BASH_VERSION変数にて取得が可能。
CentOS や macOS の /bin/shの実体は bash である。macOSの bash は最新(現時点で Catalina)であっても バージョン 3.2.57 と古いので注意(ライセンス上の理由と言われている。)/bin/shとして実行した場合は POSIX 互換モードとなるためユーザーシェルとして bash を使用している場合とは多少動きが異なる。さらに macOS では少し改変されているらしく echo -nで-nがそのまま出力されるといった違いがある。
互換性上の注意点
・スクリプトで aliasを使う場合は expand_aliasesを有効にする必要がある
shopt-s expand_aliases
・macOS で /bin/shとして使う場合 POSIXLY_CORRECTを unsetしないと echo -nで -nがそのまま出力される。
$/bin/sh
$echo-n foo
-n foo
$unset POSIXLY_CORRECT
$echo foo
foo
■ zsh - Z shell
1990年に初版リリース。bash の上位互換的な存在だがデフォルト設定では POSIX とは異なるところが多いので注意が必要。macOS Catalina でユーザーのデフォルトシェルとなった。(ただし /bin/shは 以前と変わらず bash である。)bash / ksh だけでなく csh の機能まで取り込んでいるらしい。小数の計算も可能。POSIX シェルの中で最も高機能。バージョン番号は $ZSH_VERSION変数にて取得が可能。
互換性上の注意点
・POSIX 標準の方法で変数の単語分割を行う場合は shwordsplitを有効にする。
chars="a b c"printf'%s\n'$chars# => a b c <LF> # 他のシェルと分割の仕方が異なる
setopt shwordsplit # 他のシェルと同じ動きになるprintf'%s\n'$chars# => a<LF>b<LF>c<LF># ${=var} を使用すれば shwordsplit を有効にしなくても他のシェルと同じ動きになるが# ${=var} 自体が POSIX 準拠ではなく、シェルによっては構文エラーとなる場合があるprintf'%s\n'${=chars}・関数の中で $0がシェルスクリプト名ではなく関数名となる。
echo"$0"# => test.sh
foo(){echo"$0"#=> foo}
foo
・配列のインデックスが 1 から。(そもそも配列は POSIX 準拠ではないが。)setopt ksharraysを使うことで bash や ksh と同じく 0 から始めることが出来る。
array=(a b c)echo"${array[1]}"# => a
setopt ksharrays
echo"${array[0]}"# => a・statusや pathが特殊変数。一時的な変数名として使いがちなので注意。
・他多数。emulate -R shを実行すると POSIX 互換の設定になる。(emulate -R kshや emulate -R cshなどもある)
■ yash - yet another shell
2008年に初版リリース。POSIX に準拠しており、拡張機能も実装している。小数の計算も行える。開発者が日本人なのもあり公式の日本語ドキュメントが用意されているのでシェルの文法など勉強するには便利かもしれない。bash や zsh よりも POSIX に厳密に準拠しているらしく実際大きな問題がでた記憶がない。バージョン番号は $YASH_VERSIONから取得可能。
互換性上の注意点
LANGが UTF-8 でない状態で UTF-8 文字列が含まれたスクリプトを実行するとエラーになる。文字を厳密に扱っているためで動作としては問題ないのだが、他のシェルではそうはならないので注意が必要。
■ bosh - Schily Bourne Shell
http://schilytools.sourceforge.net/bosh.html
Schily Tools プロジェクトで提供されているツールの一つとしてシェル(bosh)が提供されている。OpenSolaris の Bourne Shell をベースにバグ修正や POSIX 準拠対応を行っているらしい。シェルは2008年3月頃からソースコードに含まれており比較的新しいシェル。パッケージとして提供されたのは私が知る限り NetBSDと OpenBSDのバージョン 2018-10-30 が最初。コンパイルすれば Linux でも使える。
POSIX 準拠が目標で拡張機能は殆ど取り入れていない。開発は活発でシェルだけの更新ではないが月1回程度リリースされている。細かいバグがまだ残っているかもしれないが対応も早く、私が報告したいくつかのバグもすぐに修正された。bosh の他に最低限の POSIX 準拠実装である pbosh と POSIX 以前の obosh (OpenSolaris Bourne Shell)も提供されている。バージョン番号は ${.sh.version}から取得可能
互換性上の注意点
特に大きな問題はない
Almquist shell 系
ここのまとめが詳しい。https://www.in-ulm.de/~mascheck/various/ash/
■ ash - Almquist shell
1989年に Kenneth Almquist によって作成された ash 系の始祖。本家はメンテナンスされてないがその亜種が広く使われており、インタラクティブシェルとしての機能は少ないが POSIX 準拠で高速であるため ash (の亜種が) /bin/shの実体として採用されていることも多い。バージョン番号は $SHELLVERSで取得できたらしい。
互換性上の注意点
古すぎて使ってないので不明
■ NetBSD sh
1993年頃に /bin/shとして ash が採用。その後 NetBSD がメンテナンスをしているらしい。バージョン番号は $NETBSD_SHELLで取得可能
互換性上の注意点
・POSIX で規定されている cdコマンド の -Lオプションが実装されていない。(NetBSD 9.0 で確認)
■ FreeBSD sh
1996年頃に /bin/shとして ash が採用。その後 FreeBSD がメンテナンスをしているらしい。バージョン番号を取得する方法はない。
互換性上の注意点
特に大きな問題はない
■ dash - Debian Almquist shell
1997年頃に Herbert Xu によって NetBSD sh から Debian 向けに移植され 2002年に dash (Debian Almquist shell) と名前が変わった。その後 Ubuntu 6.10(2006年)、Debian 6 (2011年)以降の /bin/shとして採用されることになる。バージョン番号を取得する方法はない。
互換性上の注意点
・LINENOは実装されているが Debian / Ubuntu ではパフォーマンス上の理由でデフォルトで無効になっている。(Cygwinなどでは使える。)
■ busybox ash
BusyBox は 主に組み込み向けにサイズの小さな1つのバイナリによく使うコマンドのサブセットを詰め込んだもの。その中の一つとして 2001 年頃に dash が組み込まれ、以降 BusyBox がメンテナンスしている。BusyBox プロジェクト自体がそうであるが、POSIX 準拠ではなく他のシェルの軽量な代替が目的なので、[[ ... ]]や ${parameter//pattern/string}といった bash のよく使われる拡張も一部実装されている。 バージョン番号を取得する方法はない。(ただし ash を含む各コマンドの --helpオプションの出力にバージョン番号が含まれている)
互換性上の注意点
・[[ ... ]]の動きが bashと違う
[[ ... ]]は仕様的にビルトインでないと完全に実装できないはずだが外部コマンドとして実装されているため挙動が違う。将来は修正されるのかもしれない。
v="a b c"[[$v="a b c"]]# sh: b: unknown operand[["$v"=~ "a b c"]]# sh: =~ unknown operand■ GWSH
https://github.com/hvdijk/gwsh
最近知ったのでよく知らないが問題なく動作している。Issue も Pull Request も受け付けてないのになぜこんなに contributors が多いんだろうと思って過去のコミットを見てみたら 2016年頃の dash 0.5.9.1 からのフォークだった。contributors の大半はフォーク以前のもの。その後2018年9月に名前を変更して開発が続けられてる。
互換性上の注意点
特に大きな問題はない
KornShell 系
■ ksh88
AT&T が開発した POSIX シェルの始祖。ksh 88 を元に POSIX シェルの仕様が作られている。オープンソースではない。Solaris 10 の /usr/bin/ksh、Solaris 11 の /usr/sunos/bin/kshが ksh88 である。バージョン番号は set -o emacsしたあと Ctrl-V または ESC-V で画面上に表示される。(この文字列は変数にキャプチャできないんだろうか? /bin/shを stringsコマンド + grepコマンド等で取得可能なのは知っているが速い方法が欲しい)
互換性上の注意点
・aliasが実装されていない。
・set -u + 位置パラメーターなし + "$@"の参照でエラーになる。地味に対応が面倒。
set-u--echo"$@"# => ksh: parameter not set# 有名なワークアラウンド https://www.in-ulm.de/~mascheck/various/bourne_args/echo${1+"$@"}# ただし zsh 3.1.9 あたりで期待したどおりに動かないeval echo${1+'"$@"'}# 上記の問題に対応したワークアラウンド■ ksh93
POSIX シェルの始祖の直系、バージョン 93q よりオープンソース化された。サブシェルを最適化しており条件次第では他のシェルよりも高速化することがある。ただしその最適化処理が原因(?)でメタプログラミング的なことをすると不可思議なバグ(最悪 Segmentation fault など)がたびたび発生する。AT&T Software Technology(AST)の一部として提供されている。最終バージョンは 93u+ (2012年)、ベータ版として 93v- (2014年)。バージョン番号は ${.sh.version}で取得可能。(93u+ は $KSH_VERSIONでも取得可能)
互換性上の注意点
・サブシェル内で関数を再定義しても、トップレベルの関数が呼び出される
foo(){echo foo;}(
foo(){echo FOO;}
foo # => foo)# 関数名を直接書かずに間接的に参照すればOK。eval や 変数に入れるなど
bar(){echo bar;}(
bar(){echo BAR;}eval"bar"# => BAR)# aliasを使ったワークアラウンド(aliasで間接的な呼び出しを行う別関数に置き換えている)alias baz="invoke baz"
invoke(){"$@";}
baz(){echo baz;}(
baz(){echo BAZ;}
baz # => BAZeval"baz"# => BAZ eval経由で呼び出してもOK)■ ksh2020
https://github.com/ksh-community/ksh
コミュニティの手により大幅にリファクタリングされたバージョン。しかし AST の一部である ksh のみが変更され他システムと互換性問題が生じたため AT&T の手によって 数年に渡る 3000 以上のコミットがロールバックされた。(AT&Tとしては 93u+ が最終安定バージョン。)ロールバックされるまでの間 93u+ の後継としてみなされてパッケージがすでに配布されていたためどれがどれなのか混乱する。ksh 2020 を引き継ぐであろうksh-communityが新しく作成されたが現時点(2020年5月)でプロジェクトは活発ではなく今後どうなるかは不透明(どなたかもう少し経緯を詳細にまとめて下さい・・・)
互換性上の注意点
大きな互換性問題はないように思えるが、AT&Tが撤回したぐらいだから細かい問題があるのだろう。(個人的には数値計算の浮動小数点誤差が以前と違う気がするぐらい。)
Public Domain Korn shell 系
■ pdksh - Public Domain Korn shell
名前の通り、当時はオープンソースではなかった ksh のパブリックドメイン実装版。ksh とは無関係。1999年頃を最後にメンテナンスされておらず、現在は pdksh の亜種のみが使われている。Debian では Debian 6.0 (2011年)まで含まれている。Debian 7.0 (2013年)にも含まれているように見えるが、これは mksh を より pdksh に近い形でビルドした lksh。バージョン番号は $KSH_VERSIONで取得可能。(ちなみに私はバージョン番号 @(#)PD KSH v5.2.14 99/07/13.2以外を見たことがない。)
互換性上の注意点
・メモリ管理にバグがあるらしく Bus Error, Bad address, Memory fault など原因不明のエラーで度々落ちる。この手のエラーが出たときの原因追求は困難(書き方を少し変えただけで解決したりエラーになったりする。)
■ OpenBSD sh
pdksh をフォークし OpenBSD 2.1(1997年)から /bin/shとして採用された。pdksh を元に OpenBSD がメンテナンスをしているらしい。/bin/shで起動した場合は POSIX モードとなる。バージョン番号は POSIX モードで起動した場合は $SH_VERSIONで取得可能。/bin/kshで起動した場合は $KSH_VERSIONで取得可能。(どちらもバージョン番号は同じ @(#)PD KSH v5.2.14 99/07/13.2で更新されてない)
Linux・macOSへの移植版が複数存在する。
- oksh - https://github.com/ibara/oksh/
- loksh - https://github.com/dimkr/loksh
- ksh - https://github.com/tamentis/ksh/
互換性上の注意点
・set -eのとき evalの中でエラー終了になると中断する。
(Debian 3.0 の pdksh も同じだが 3.1 以降では修正されている。)
set-eueval"[ ]"&&: # 本来は中断してほしくないeval"[ ] &&:"&&: # ワークアラウンド■ mksh - MirBSD Korn Shell
http://www.mirbsd.org/mksh.htm
元々は MirOS BSD 用として開発(2002年頃?)。pdksh からフォークしたシェル。Android 4.0 以降のデフォルトシェルらしい。他のシェルが 32bit OS でも 64bit演算が行える中、64bit OSであっても POSIX で保証された 32bit 演算しか行わない。mksh に含まれる lksh (Legacy Korn shell built on mksh) を使えば 64bit 演算は出来る。バージョン番号は $KSH_VERSIONで取得可能。
互換性上の注意点
・整数演算が 32bit
■ posh - Policy-compliant ordinary shell
https://salsa.debian.org/clint/posh
目標は素晴らしいが実力不足。バグが多く /bin/shの代わりに使うのは危険。pdksh をベースに POSIX に必要のない機能を削除する所からプロジェクトがスタートしており、そのせいもあってかクリティカルなバグを入れてしまったり、本家でははるか昔に解決されたバグが未だに残っていたりする。開発中と考えたほうがいいレベルだが 0.13.2 で開発が停止している・・・と思ったらこの記事を書いているうちに1年半ぶりに更新された。(すでに 0.14.1 としてタグが打たれてるのでまたしばらく更新されない気がする。)バージョン番号は $POSH_VERSIONから取得可能
互換性上の注意点
・aliasが存在しない。意図的に pdksh から削除されている。(alias は主にインタラクティブシェルで使われるものなのでわからなくはないが、他のシェルでは ksh88 を除いてサポートされており、シェルスクリプトとしても面白い使い方ができるのでちょっと残念。)
・set -u + func "$@"で落ちる。スクリプトの書き方全体に影響するのでひたすら迷惑。なお古い ksh にも同様のバグが存在する。古き良きワークアラウンドである foo ${1+"$@"}が使える。
・posh 0.10.2 あたりでシグナルが全て動かない致命的なバグが有る。(trap : TERMや kill -9が機能しない)またこのバージョンの $POSH_VERSIONの値は POSH_VERSIONという文字列になってる。
・typeと command -vが実装されていない
・他変数展開やグロブ周り等に多数のバグが有る。
その他
開発中。まだ POSIX に準拠してないが将来的に期待できそうなものたち。
■ osh (oil)
https://github.com/oilshell/oil
POSIXシェル(および bash)と互換性がある osh シェル と oil シェルの2つのシェルを実装している。(osh は)かなり実装されているように見える。シェルスクリプトを言語としてみなし、既存のPOSIXシェル(osh)から改善されたシェル(oil)へと言語を進化させようとしており、個人的に興味があるプロジェクト。開発は活発。
■ busybox hush
BusyBox に組み込まれたもう一つのシェル(ビルド設定によっては組み込まれていない。)ashと同様 POSIX 準拠で ash よりもさらに軽いものを目指しているようだが実装はまだ不十分に思える。(過去には他に lash, msh というシェルも実装されていたようだが廃止)
■ mrsh - A minimal POSIX shell.
https://github.com/emersion/mrsh
work in progress と書いてあるようにまだ開発中。開発は継続中。プロジェクトステータス上はかなり実装されているように見えるが、バグも結構残っている感じがする。
■ gosh (mvdan/sh, shfmt)
フォーマッターとして有名だがシェルインタプリタの機能も持っている。Proof of Concept とされており実装は最小限。開発は活発だが POSIX シェルとしての優先度は低いと思われる。
■ gash - Guile as Shell - Summary
https://savannah.nongnu.org/projects/gash
算術式展開が未実装(実装中)Development Status: Alphaということなのでまだまだ開発中と見られる。
■ toysh (toybox)
https://github.com/landley/toybox
BusyBox の置き換えを目指す ToyBox プロジェクトの中の一つ。シェル (toysh) に関しては多くの機能がまだ実装されていない。開発は活発だが、開発しているのは toysh だけではないのでそれなりに時間がかかると思う。
■ rash - the Rust Bourne Shell
https://github.com/absurdhero/rash-shell
プロジェクトステータスみて重要な機能が未実装(未完成)なようなので試していない。開発は停滞気味。