HN 日本語サマリー

← 一覧へ戻る
プログラミング

POSIXはシェルではない

POSIX Is Not a Shell (alganet.github.io)

30 pointsby gaigalas10 コメント

要約

POSIXは仕様であり、プログラムではありません。スクリプトを実行するシェル(bash、dash、kshなど)はそれぞれPOSIXを独自に実装しており、互換性の問題が生じます。たとえば、echoコマンドのバックスラッシュエスケープの解釈はシェルによって異なり、POSIXはこれを実装依存としています。「POSIX準拠」という言葉は、実際に意図するシェルでテストされていない限り、信頼できないことがあります。著者は、自身のプロジェクトshell-docsを通じて、複数のシェルで機能の検証を行うことの重要性を強調し、真のポータビリティを確保するためには、実際にターゲットとするシェル全体でテストする必要があることを説いています。

全文翻訳

alganet POSIXはシェルではない Alexandre Gomes Gaigalas – 2026年6月28日 誰かが「移植性のためにPOSIXシェルで書け」と言うとき、彼らは良い意図を持っています。POSIXは仕様です。プログラムではありません。実際にスクリプトを実行するのは、bash、dash、ash、ksh、yash、あるいは他の多数のシェルです。それらはそれぞれ、独自のギャップ、拡張機能、歴史的な偶然とともにPOSIXを実装しています。 ここに小さな実験があります。1行、フラグなし、関数なし、何も珍しいものはありません: ```bash #!/bin/sh echo "C:\new" ``` bash、ksh、その他いくつかのシェルでは、入力したものがそのまま返されます: ``` C:\new ``` dash(Debian、Ubuntu、Alpineの/bin/shです)では、同じ行が次のように出力されます: ``` C: ew ``` dashのechoは`\n`を改行として解釈します。bashはそうしません。シェルバージョン全体で実行すると、シェルはほぼ半分に分かれます。約半分はバックスラッシュをリテラルに扱い、残りの半分は展開します。 ```bash docker run --rm alganet/shell-versions:all -c 'echo "C:\new"' ``` POSIXはこの決着をつけません。標準は、echoのバックスラッシュエスケープの扱いを明示的に実装定義のままにし、代わりにprintfを使用することを推奨しています。したがって、「POSIX echo」はスクリプトを書く際の振る舞いではありません。それは文書化された不一致です。 これはこじつけられたエッジケースではありません。echoは誰もが最初に学ぶコマンドです。「POSIX準拠」が実際に意味するのは、仕様が実際に規定している部分に、たまたま出荷されたバージョンで準拠しているということです。 方言の問題 自然言語には方言があります。ブラジルポルトガル語とヨーロッパポルトガル語はどちらもポルトガル語です。一方の母語話者は努力すればもう一方を理解できますが、同じではありません。ポルトガル語で書いて、どこでも同じように機能することを期待することはできません。 シェルスクリプトにも同じ問題があります。Bashはシェルではありません。それは特定の動作セットを持つシェルであり、その多くはPOSIXに含まれておらず、中には(dashのように)技術的にはより準拠している実装と矛盾するものもあります。コミュニティはそうではないふりをします。私たちは「shスクリプト」と言いながら「-e -u -o pipefail付きのbash」を意味します。私たちは「POSIX準拠」と言いながら「CIで動いた」を意味します。私たちは「ポータブル」と言いながら「試した2台のマシンで動いた」を意味します。 検証の実際の様子 私は、文書化された各機能を14のシェル全体で検証するクロスシェルリファレンスであるshell-docsを構築してきました。プロセスは機械的です: 例を作成し、すべてのシェルで実行し、出力を記録し、相違点をチェックします。ほとんどの機能は同じように動作します。一部はそうではありません。 `$#` - 位置パラメータの数。どこでも動作し、驚きはありません。 `local` - POSIXにはまったくありません。すべてのシェルに存在しますが、スコープ規則が異なります。 `$(( ))` - 算術展開、普遍的にサポートされています。ゼロ除算: 実装によって異なります。 `[[ ]]` - POSIXではありません。dashには存在しません。スクリプトが`#!/bin/sh`の下でそれを使用している場合、/bin/shがdashであるすべてのシステム(Debian、Ubuntu、Alpine)でサイレントに失敗します。 相違点があるものはバグではありません。それらは、仕様の歴史におけるわずかに異なる時点(1988年、2001年、2017年)をターゲットとした、数十年にわたる独立した実装からの累積された決定です。 「ポータブル」の正直なバージョン ポータブルとは、実際に実行されるシェル全体でテストされていることを意味します。ツールshell-versionsは、主要なディストリビューションにどのバージョンのシェルが同梱されているかを追跡します。執筆時点では、/bin/shは次のとおりです。 - Ubuntu 24.04ではdash 0.5.12 - macOSではbash 3.2.57(15年間変更なし; GPL v3によりbash 4.xがベースシステムから除外された) - Alpineではbusybox ash - 一部のエンタープライズLinuxディストリビューションではksh93 これらは同じプログラムではありません。同じ古さですらありません。`#!/bin/bash`と書いてそれを意図しているなら、それで問題ありません。少なくとも正直です。`#!/bin/sh`と書くなら、検証すべきことを約束しています。 検証は習慣です shell-docsの検証ハーネスは、各例を14のシェルすべてで分離して実行し、stdoutと終了コードをキャプチャし、既知の良いテーブルと比較します。新しいシェルバージョンがリリースされたら、再度実行します。これは複雑ではありません。ただ、デフォルトでは行われない作業です。その見返りとして、「POSIX shで動作する」ということが具体的な意味を持つようになります: 私がテストした14のシェルで動作し、結果はここにあります。これは、「bashで実行したら問題なさそうに見えた」という主張とは異なります。 POSIXはシェルではありません。それはシェルが何をすべきかについての約束です。検証は、どのシェルがそれを守ったかを見つける方法です。CC BY-NC-SA 4.0