Help us understand the problem. What is going on with this article?

スクリプト言語としてみた各POSIXシェルの特徴と互換性上の注意点まとめ

はじめに

この記事は私の主観かつ知っていることをまとめたものです。古くからのシェルの専門家とかではなくここ数年の間に必要になった時にその都度調べたものなので間違いとかもあると思います。またタイトルの通りスクリプト言語としての比較です。つまりインタラクティブシェルの機能についての比較はしていません。(そもそも使い込んでるわけじゃないので知らないです。)POSIX シェルに限定しているのも私が他のシェルを詳しく知らないからです。もし今も使われていて(もしくは開発中で)ここに書かれていない POSIX シェルがありましたらコメントで教えて下さい。(※ csh, tcsh, fish は POSIX シェルではありません。)

シェルの系統は少し古いですが「What does it mean to be “sh compatible”?」に投稿されている図が詳しくてわかりやすいです。またこちら「~sven_mascheck/」でも多くのシェルの細かい亜種(variants) についてまとめられています。

追記 なんかこの記事が結構見られているようなので宣伝
なぜこれだけの POSIX シェルを調べたかと言うと、私が開発している ShellSpec というシェルスクリプト用のテスティングフレームワークは、本当にすべての POSIX 準拠シェルに対応していると言えるようにするためです。最後の「その他」に書いた POSIX にまだ準拠してないシェルをのぞいて実際に ShellSpec 自身のテストを実行して動作確認しています。どうせ動くだけで機能が少ないから実現できてるんでしょ?と思ったあなた、ShellSpec は動くだけじゃありません。当初より RSpec のシェルスクリプト版を目標としており、すでに他のシェルスクリプト用テスティングフレームワークの機能を大きく上回っていると自負しております。(参考 Bash test framework comparison 2020 - Dávid Csákváriさんによる比較記事・・・に書かれてない機能もたくさん)インストールせずにブラウザ上で実行できるデモも用意していますので興味がある方はぜひ使ってみてください。(Star がつくと喜びます。)

独自系統

■ 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_CORRECTunset しないと echo -n-n がそのまま出力される。

$ /bin/sh
$ echo -n foo
-n foo
$ unset POSIXLY_CORRECT
$ echo foo
foo

■ zsh - Z shell

http://zsh.sourceforge.net/

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 がシェルスクリプト名ではなく関数名となる。

test.sh
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

statuspath が特殊変数。一時的な変数名として使いがちなので注意。さらに pathPATH と紐付いているため path=""などとすると外部コマンドが実行できなくなってハマるのでさらに注意。

・他多数。emulate -R sh を実行すると POSIX 互換の設定になる。(emulate -R kshemulate -R csh などもある)

■ yash - yet another shell

https://yash.osdn.jp/

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月頃からソースコードに含まれており比較的新しいシェル。パッケージとして提供されたのは私が知る限り NetBSDOpenBSD のバージョン 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

参考 おそらくオリジナルは POSIX に準拠してないはずです。

1989年に Kenneth Almquist によって作成された ash 系の始祖。本家(どこ?)はメンテナンスされてないがその亜種が広く使われており、インタラクティブシェルとしての機能は少ないが(ash の亜種は)POSIX 準拠で高速であるため /bin/sh の実体として採用されていることも多い。バージョン番号は $SHELLVERS で取得できたらしい。

互換性上の注意点

古すぎて使ってないので不明

■ NetBSD sh

http://cvsweb.netbsd.org/bsdweb.cgi/src/bin/sh/

1993年頃に /bin/sh として ash が採用。その後 NetBSD がメンテナンスをしているらしい。バージョン番号は $NETBSD_SHELL で取得可能

互換性上の注意点

・POSIX で規定されている cd コマンド の -L オプションが実装されていない。(NetBSD 9.0 で確認)

■ FreeBSD sh

https://www.freebsd.org/cgi/man.cgi?sh(1)

1996年頃に /bin/sh として ash が採用。その後 FreeBSD がメンテナンスをしているらしい。バージョン番号を取得する方法はない。

互換性上の注意点

特に大きな問題はない

■ dash - Debian Almquist shell

https://git.kernel.org/pub/scm/utils/dash/dash.git

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

https://git.busybox.net/busybox/tree/shell/

BusyBox は 主に組み込み向けにサイズの小さな1つのバイナリによく使うコマンドのサブセットを詰め込んだもの。その中の一つとして 2001 年頃に dash が組み込まれ、以降 BusyBox がメンテナンスしている。BusyBox プロジェクト自体がそうであるが、POSIX 準拠よりも他のシェル(または各コマンド)の軽量な代替実装を目的としているので、[[ ... ]]${parameter//pattern/string} といった bash のよく使われる拡張も一部実装されている。BusyBoxの各コマンドは必要最小限の実装だが、ashに限って言えば、ashの亜種の中で最も高機能かもしれない。バージョン番号を取得する方法はない。(ただし ash を含む各コマンドの --help オプションの出力にバージョン番号が含まれている)

余談だが BusyBox には Windowsへのネイティブ移植版である busybox-w32 が存在する。1 実行ファイルにシェル以外の各コマンドも含まれており、それらはビルトインコマンドのようにシェルスクリプトの中から実行可能なため、クロスプラットフォームのスクリプトを作成するのには便利そう。(ただし日本語文字、Unicodeの扱いは難あり #61 #174

互換性上の注意点

[[ ... ]] の動きが 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 シェルの始祖。ksh88 を元に POSIX シェルの仕様が作られている。オープンソースではない。Solaris 10 の /usr/bin/ksh、Solaris 11 の /usr/sunos/bin/ksh が ksh88 である。バージョン番号は set -o emacs したあと Ctrl-V または ESC-V で画面上に表示される。(この文字列は変数にキャプチャできないんだろうか? /bin/shstrings コマンド + grep コマンド等で取得可能なのは知っているが速い方法が欲しい)

互換性上の注意点

alias が実装されていない。

set -u した状態で位置パラメーターがないときに、"$@"を参照するとエラーになる。pdksh や posh でも同様のバグが有る。地味に対応が面倒。

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

http://kornshell.org/

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 # => BAZ
  eval "baz" # => BAZ   eval経由で呼び出してもOK
)

・サブシェルを強制的に別プロセスに(してバグを回避)する方法

(func) # サブシェルが別プロセスにならない
(ulimit -t unlimited; func) # 強制的に別プロセスになる

# ulimitは別プロセスを作らないと実現できないため最適化が阻止されるという理屈だろう

modernish プロジェクトより

NONFORKSUBSH: as a performance optimisation, subshells are implemented without forking a new process, so they share a PID with the main shell. (AT&T ksh93; it has many bugs related to this, but there's a nice workaround: ulimit -t unlimited forces a subshell to fork, making those bugs disappear! See also BUG_FNSUBSH.)

・ローカル変数を作るときのキーワードが local ではなく typeset

そもそも local は POSIX 準拠ではないが、ksh を除けば殆どのシェルでサポートされている。(他に local が使えないのは pbosh、yash 2.48より前、posh 0.5.4あたりとその前)

# 殆どのシェルは local が使える
func() {
  local var
}

# ksh は typeset の上、function を使って関数を定義しなければ有効にならない
function func {
  typeset var
}

■ ksh2020

https://github.com/ksh-community/ksh

コミュニティの手により大幅にリファクタリングされたバージョン。しかし AST の一部である ksh のみが変更され他システムと互換性問題が生じたため AT&T の手によって 数年に渡る 3000 以上のコミットがロールバックされた。(AT&Tとしては 93u+ が最終安定バージョン。)ロールバックされるまでの間 93u+ の後継としてみなされてパッケージがすでに配布されていたためどれがどれなのか混乱する。ksh 2020 を引き継ぐであろうksh-community が新しく作成されたが現時点(2020年5月)でプロジェクトは活発ではなく今後どうなるかは不透明(どなたかもう少し経緯を詳細にまとめて下さい・・・)

互換性上の注意点

大きな互換性問題はないように思えるが(ksh93と同等)、AT&Tが撤回したぐらいだから細かい問題があるのだろう。(個人的には数値計算の浮動小数点誤差が以前と違う気がするぐらい。)

Public Domain Korn shell 系

■ pdksh - Public Domain Korn shell

https://web.archive.org/web/20160918190548/http://www.cs.mun.ca:80/~michael/pdksh/

名前の通り、当時はオープンソースではなかった 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 ksh

https://man.openbsd.org/ksh.1

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 - Portable OpenBSD ksh, based on the Public Domain Korn Shell (pdksh)
  • loksh - A Linux port of OpenBSD's ksh
  • ksh - Port of the OpenBSD ksh shell

互換性上の注意点

set -e のとき eval の中でエラー終了になると中断する。
(Debian 3.0 の pdksh も同じだが 3.1 以降では修正されている。)

set -eu
eval "[ ]" &&: # 本来は中断してほしくない
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 : TERMkill -9 が機能しない)またこのバージョンの $POSH_VERSION の値は POSH_VERSION という文字列になってる。

typecommand -v が実装されていない

・他変数展開やグロブ周り等に多数のバグが有る。

その他

開発中。まだ POSIX に準拠してないが将来的に期待できそうなものたち。

■ osh (oil)

https://github.com/oilshell/oil

POSIXシェル(および bash)と互換性がある osh シェル と oil シェルの2つのシェルを実装している。(osh は)かなり実装されているように見える。シェルスクリプトを言語としてみなし、既存のPOSIXシェル(osh)から改善されたシェル(oil)へと言語を進化させようとしており、個人的に興味があるプロジェクト。開発は活発。

なお同名で「osh - Port of the Sixth Edition UNIX shell」というBourne シェル(POSIX 以前)があるのでこちらと混同しないよう注意。

■ busybox hush

https://git.busybox.net/busybox/tree/shell/

BusyBox に組み込まれたもう一つのシェル(ビルド設定によっては組み込まれていない。Busybox の Docker イメージで使える。)ashと同様 POSIX 準拠で ash よりもさらに軽いものを目指しているようだが実装はまだ不十分に思える。(過去には他に lash, msh というシェルも実装されていたようだが廃止)

■ mrsh - A minimal POSIX shell.

https://github.com/emersion/mrsh

work in progress と書いてあるようにまだ開発中。開発は継続中。プロジェクトステータス上はかなり実装されているように見えるが、バグも結構残っている感じがする。

■ gosh (mvdan/sh, shfmt)

https://github.com/mvdan/sh

フォーマッターとして有名だがシェルインタプリタの機能も持っている。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 だけではないのでそれなりに時間がかかると思う。

■ nsh

https://github.com/nuta/nsh

最終的には POSIX 準拠で bash の拡張機能を備えた fish のようなインタラクティブシェルを目指しているっぽい。まだアルファで実装されてない機能も多いようだが、活発に開発が行われている。

■ rash - the Rust Bourne Shell

https://github.com/absurdhero/rash-shell

プロジェクトステータスみて重要な機能が未実装(未完成)なようなので試していない。開発は停滞気味。

■ gsh - A POSIX shell for Windows

https://github.com/AdaCore/gsh

Windows 用に作られた POSIX シェルらしい。Issues みて set -e が実装されていないようなので試していない。Windows に対応する場合 ls コマンドなども実装しないと実用的にならないはずで、それを外部コマンドで用意するなら busybox-w32 でいいかなと思ってしまうが興味深いシェル。開発は停滞気味。

ko1nksm
おそらくウェブアプリエンジニア。フロントやったりサーバーやったりたまにインフラ。好きなもの:シンプルで無駄のないコード、リファクタリング。嫌いなもの:技術的負債、レガシーコード
https://blog.nksm.name/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした