Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
34
Help us understand the problem. What is going on with this article?
@ko1nksm

スクリプト言語としてみた各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

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

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

2008年に初版リリース。POSIX に準拠しており、拡張機能も実装している。小数の計算も行える。開発者が日本人なのもあり公式の日本語ドキュメントが用意されているのでシェルの文法など勉強するには便利かもしれない。bash や zsh よりも POSIX に厳密に準拠しているらしく実際大きな問題がでた記憶がない。バージョン番号は $YASH_VERSION から取得可能。

互換性上の注意点

LANG が UTF-8 でない状態で UTF-8 文字列が含まれたスクリプトを実行するとエラーになる。文字を厳密に扱っているためで動作としては問題ないのだが、他のシェルではそうはならないので注意が必要。

■ bosh - Schily Bourne Shell

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} から取得可能

互換性上の注意点

特に大きな問題はない

■ osh (oil)

POSIX シェル(正確には bash)と互換性がある osh シェル と より良いシェルである oil シェルの2つのシェルを実装している。まだ開発中ではあるが osh はかなりの部分が実装されており(2021-01-23時点 の masterは)POSIX シェルとしては十分なレベルに達している(と私は判断している)。既存の POSIX シェルにあるシェルスクリプトとしての言語上の問題点(間違いやすい罠)を検出するための機能を多く備えており、bash から osh、osh から oil への移行パスを提供することでシェルを進化させようとしており非常に興味深く野心的なプロジェクト。開発は活発。現状はかなり遅いのが難点だが言語として完成させるのが当面の目標に見える。

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

互換性上の注意点

デフォルトで他のシェルよりも多くの状態をエラー・警告として報告したり、さらに shopt -s strict:allsetopt -s simple_* を有効にすることでより厳格なモードに設定できる。ただし全てのオプションを有効にすると厳格すぎるため既存のコードの多くはそのままでは動かないと思われる。互換性としては注意点にあたるが、開発者が定義する悪いコーディングパターンを防ぐためであり欠点というわけではない。

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

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 のよく使われる拡張も一部実装されている。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

最近知ったのでよく知らないが問題なく動作している。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

POSIX シェルの始祖の直系、バージョン 93q よりオープンソース化された。遅いサブシェルを最適化しており、条件次第で子プロセスを生成しないため場合によっては他のシェルよりも高速化することがある。ただしその最適化処理が原因(?)でメタプログラミング的なことをすると不可思議なバグ(最悪 Segmentation fault など)がたびたび発生する。AT&T Software Technology(AST) の一部として提供されている。最終バージョンは 93u+ (2012年)、ベータ版として 93v- (2014年、壊れすぎていて修正できないという意見があるレベル)。バージョン番号は ${.sh.version} で取得可能。(93u+ は $KSH_VERSION でも取得可能)

2021-01-19追記

https://github.com/ksh93/ksh で AT&T最終版(93u+ 2012-08-01)を元にバグ修正のみを行った 93u+m が開発中。(こちらでは ksh2020 の開発は失敗とみなしている。)

互換性上の注意点

・サブシェル内で関数を再定義しても、トップレベルの関数が呼び出される。(2021-01-19追記 93u+m で修正されている。)

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

コミュニティの手により大幅にリファクタリングされたバージョン。しかし 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

名前の通り、当時はオープンソースではなかった 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 以外を見たことがない。)

追記 2021-05-29

loksh の CONTRIBUTORS に細かいバージョンの情報があったので追記

  • ? - 3.2 (1987 - 1989?) - Eric Gisin (Charles Forsyth の public domain V7 shell をベースに開発)
  • 3.3? (date?) - John R MacMillan
  • 4.0 - 4.9 (1991/11 - 1994/7) - Simon J. Gerraty
  • 5.0 - 5.2 (1994/7 - ) - Michael Rendell

互換性上の注意点

・メモリ管理にバグがあるらしく Bus Error, Bad address, Memory fault など原因不明のエラーで度々落ちる。この手のエラーが出たときの原因追求は困難(書き方を少し変えただけで解決したりエラーになったりする。)

set -u
: "$@"  # => pdksh: @: parameter not set というエラーになる

set -- ab a
echo ${1#$2} # => 空文字

■ OpenBSD ksh

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 で更新されてない)

OpenBSD 以外(Linux、macOS等)への移植版が複数存在する。

  • oksh - Portable OpenBSD ksh, based on the Public Domain Korn Shell (pdksh)
    • 多くの環境で容易にビルドできるよう移植性を重視しており対応しているプラットフォームが多い
  • loksh - A Linux port of OpenBSD's ksh
    • 上流からの変更を最小限に留め Linux のみを対象とすることでセキュリティリスク等を軽減させる方針
  • ksh - Port of the OpenBSD ksh shell
    • 醜いハックにより Linux と macOS でかろうじて動く(と書いてある)

互換性上の注意点

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

set -eu
eval "[ ]" &&: # 本来は中断してほしくない
eval "[ ] &&:" &&: # ワークアラウンド

■ NetBSD ksh

NetBSD に含まれてる /bin/ksh 。詳しくは調べていないが OpenBSD ksh とは異なりオリジナルの pdksh と同じようなバグが含まれているためあまりメンテナンスされていない感じがする。バージョン番号は $KSH_VERSION から取得可能。(バージョン番号は更新されておらず @(#)PD KSH v5.2.14 99/07/13.2 のまま)

互換性上の注意点

set -u
: "$@"  # => ksh: @: parameter not set というエラーになる

set -- ab a
echo ${1#$2} # => 空文字

■ mksh - MirBSD Korn Shell

元々は 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

目標は素晴らしいが実力不足。バグが多く /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 に準拠してないが将来的に期待できそうなものたち。(※ POSIX に準拠しているかどうかの基準は ShellSpec を動作させることが可能かどうかです。POSIX 準拠率の高さとは関係ありません。)

■ busybox hush

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

■ toysh (toybox)

BusyBox の置き換えを目指す ToyBox プロジェクトの中の一つ。シェル (toysh) に関しては多くの機能がまだ実装されていない。開発は活発だが、開発しているのは toysh だけではないので toysh の完成までそれなりに時間がかかると思う。

■ gosh (mvdan/sh, shfmt)

フォーマッターとして有名だがシェルインタプリタの機能も持っている。Proof of Concept とされており実装は最小限。開発は活発だが POSIX シェルとしての優先度は低いと思われる。

■ mrsh - A minimal POSIX shell.

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

■ gash - Guile as Shell - Summary

算術式展開が未実装(実装中)Development Status: Alpha ということなのでまだまだ開発中と見られるが開発は停滞中

■ nsh

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

■ rash - the Rust Bourne Shell

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

■ gsh - A POSIX shell for Windows

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

34
Help us understand the problem. What is going on with this article?
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
ko1nksm
おそらくウェブアプリエンジニア。フロントやったりサーバーやったりたまにインフラ。好きなもの:シンプルで無駄のないコード、リファクタリング。嫌いなもの:技術的負債、レガシーコード

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
34
Help us understand the problem. What is going on with this article?