#!/ bin / sh vs#!/ bin / bashの移植性を最大限に引き出す

cherouvim 07/30/2017. 3 answers, 2.886 views
linux bash shell sh

私は通常、symlink /bin/sh/bin/dashと理解しているUbuntu LTSサーバで作業します。 symlink /bin/shから/bin/bash至るまで、他の多くのディストリビューション

これから私は、スクリプトが#!/bin/shを使用している場合、すべてのサーバーで同じ方法で実行されないことがあることを理解していますか?

サーバー間でこれらのスクリプトを最大限移植できるようにするには、シェルでスクリプトに使用する推奨練習はありますか?

1 Comments
Thorbjørn Ravn Andersen 08/01/2017
さまざまなシェルにはわずかな違いがあります。 移植性が最も重要な場合は、 #!/bin/shを使い、元のシェルが提供していたもの以外を使用しないでください。

3 Answers


Gordon Davisson 08/01/2017.

シェルスクリプトの移植可能性には、およそ4つのレベルがあります(シェバン行に関する限り)。

  1. ほとんどの移植性: #!/bin/shシバンを使用し、 POSIX標準で指定されている基本的なシェル構文onlyを使用します。 これはPOSIX / unix / linuxシステムのほとんどすべてで動作するはずです。 (まあ、実際の旧Bourneシェルを持っていたSolaris 10以前のもので、POSIXに準拠していないので、 /bin/shと同じです。)

  2. 2番目に移植性が高い: #!/bin/bash (または#!/usr/bin/env bash )シバン行を使用し、bash v3の機能に固執する。 これはbash(予想される場所にある)を持つシステム上で動作します。

  3. 3番目に移植性が高い: #!/bin/bash (または#!/usr/bin/env bash )シバン行を使用し、bash v4の機能を使用します。 これは、bash v3を持つシステムでは失敗します(例えば、ライセンス理由のためにそれを使用しなければならないmacOS)。

  4. 最低限の移植性: #!/bin/shシバンを使用し、POSIXシェル構文のbash拡張を使用します。 これは、/ bin / sh用のbash以外のもの(最近のUbuntuのバージョンなど)を持つシステムでは失敗します。 これをしないでください。 それは単に互換性の問題ではない、それは単なる間違っている。 残念ながら、それは多くの人々が作るエラーです。

私のお勧め:スクリプトに必要なすべてのシェル機能を提供する最初の3つの中で最も控えめなものを使用してください。 最大の移植性のために、オプション#1を使用してください。私の経験では、配列のようないくつかのbash機能は、#2と一緒に行くほど役立ちます。

あなたができる最悪のことは、間違ったシバンを使って#4です。 基本POSIXとbash拡張機能の機能が不明な場合は、bashシバン(オプション#2)を使用するか、スクリプトを非常に基本的なシェル(Ubuntu LTSサーバーのダッシュなど)で完全にテストしてください。 Ubuntu wikiには注意が必要なbashismsの良いリストがあります。

UnixとLinuxの質問のシェルの歴史と相違点について、いくつかの非常に良い情報があります。「sh互換性はどういう意味ですか? Stackoverflowの質問"shとbashの違い"です。

また、シェルが異なるシステム間で異なる唯一のものではないことに注意してください。 もしあなたがLinuxに慣れていれば、あなたは他のUNIXシステム(例えばbsd、macOS)では見つからないかもしれない非標準的な拡張がたくさんあるGNUコマンドに慣れています。 残念ながら、ここには単純なルールはありません。使用しているコマンドのバリエーションの範囲を知っているだけです。

移植性の点で最も厄介なコマンドの1つは最も基本的なecho 1つです。 任意のオプション( echo -necho -e )で使用したり、文字列内のエスケープ(バックスラッシュ)を使って印刷する場合はいつでも、異なるバージョンでは異なる処理が行われます。 後に改行を入れずに文字列を出力したい場合や、文字列にエスケープをつけたい場合は、代わりにprintfを使います(どのように動作するかはechoより複雑です)。 psコマンドも混乱しています。

もう一つの一般的なことは、コマンドオプション構文の最近の/ GNU拡張です:古い(標準)コマンドフォーマットは、コマンドの後にオプション(ダッシュを1つ付け、各オプションは1文字)コマンド引数。 最近の(そしてしばしば移植不可能な)亜種には長いオプション(通常は--で始まります)、オプションの後ろにオプションを付けること、そして--を使ってオプションからオプションを切り離すことができます。

5 comments
12 pabouk 07/30/2017
第4の選択肢は単に間違った考えです。 それを使用しないでください。
3 Gordon Davisson 07/30/2017
@pabouk私は完全に同意するので、私はこれをもっと明確にするために私の答えを編集しました。
1 jlliagre 07/30/2017
あなたの最初の声明は少し誤解を招く。 POSIX標準では、シバンについては何も指定していないが、これを使用すると、不特定の動作につながってしまう。 さらに、POSIXはposixシェルの場所を指定するのではなく、その名前( sh )だけを指定するので、 /bin/shは正しいパスであるとは保証されません。 もっとも移植性の高いものは、シバンをまったく指定しないか、シバンを使用するOSに適合させることではありません。
2 Michael Kjörling 07/30/2017
私は最近、私のスクリプトを使って#4に落ちたが、それがなぜ機能していないのか分からなかった。 結局のところ、同じコマンドがしっかりと動いていて、私がシェルで直接試してみたときに、彼らがやりたがっていたこととまったく同じでした。 #!/bin/sh#!/bin/bashに変更するとすぐに、スクリプトは完全に機能しました。 (私の防衛のために、そのスクリプトは実際にはsh-ismsが必要なものから、bashのような振る舞いに依存するものまで、時間の経過とともに進化しました。)
2 jlliagre 07/30/2017
@MichaelKjörling(本物の)Bourneシェルは、Linuxディストリビューションにはほとんどバンドルされておらず、POSIX準拠ではありません。 POSIX標準シェルは、Bourneではなくksh動作から作成されました。 FHSに続くほとんどのLinuxディストリビューションは、POSIXとの互換性を提供するために選択したシェルへのシンボリックリンク(通常はbashまたはdash )を/bin/shに持たせています。

Kaz 07/31/2017.

ビルドのためにTXR言語を準備する./configureスクリプトでは、移植性を高めるために以下のプロローグを書いています。 #!/bin/shがPOSIXに準拠していない古いBourneシェルであっても、スクリプトは自動的にブートストラップします。 (私はすべてのリリースをSolaris 10 VM上に構築します)。

#!/bin/sh

# use your own variable name instead of txr_shell;
# adjust to taste: search for your favorite shells

if test x$txr_shell = x ; then
  for shell in /bin/bash /usr/bin/bash /usr/xpg4/bin/sh ; do
    if test -x $shell ; then
       txr_shell=$shell
       break
    fi
  done
  if test x$txr_shell = x ; then
    echo "No known POSIX shell found: falling back on /bin/sh, which may not work"
    txr_shell=/bin/sh
  fi
  export txr_shell
  exec $txr_shell $0 ${@+"$@"}
fi

# rest of the script here, executing in upgraded shell 

ここでのアイデアは、私たちが実行しているシェルよりも優れたシェルを見つけ出し、そのシェルを使ってスクリプトを再実行することです。 txr_shell環境変数が設定され、再実行されたスクリプトが再実行された再帰インスタンスであることがわかります。

(私のスクリプトでは、 txr_shell変数もまさしく2つの目的のために使用されます:まず、スクリプトの出力に有益なメッセージの一部として出力され、次にSHELL変数としてMakefileにインストールされます。 makeはこのシェルもレシピの実行に使用します)。

/bin/shがダッシュのシステムでは、上記のロジックが/bin/bashを見つけてそれを使ってスクリプトを再実行することがわかります。

Solaris 10の場合、Bashが見つからなければ/usr/xpg4/bin/shが起動します。

プロローグは、ファイル存在testためのテストといくつかの壊れた古いシェルを扱う引数を拡張するための${@+"$@"}トリックを使用して、控えめなシェルの方言で書かれています(これは単に"$@" POSIX準拠のシェルで)。

2 comments
Charles Duffy 07/31/2017
この引用符で囲まれた状況が、 -aまたは-o複数のテストを組み合わせた非推奨のテスト呼び出しを囲んでいるため、適切な引用が使用されていればx hackeryは必要ありません。
Kaz 07/31/2017
@CharlesDuffy確かに。 私がそこで犯しtest x$whateverいることはtest x$whateverワニスにタマネギのように見える。 引用符で囲まれた古いシェルを信頼できない場合、 ${@+"$@"}の最後の試みは無意味です。

zwol 07/31/2017.

Bourneシェル言語のすべてのバリエーションは、Perl、Python、Ruby、node.js、さらには(おそらく)Tclのような現代のスクリプト言語と比べて客観的にひどいものです。 少し複雑でも何かをやらなければならない場合は、シェルスクリプトの代わりに上記のいずれかを使用すれば、長期的にはもっと幸せになれます。

シェル言語がまだそれらの新しい言語より優れている唯一の利点は、 /bin/sh呼び出すsomethingがUnixであると主張するものに存在することが保証されていることです。 しかし、それはPOSIX準拠ではないかもしれません。 従来の独自仕様のUnixの多くは、Unix95が要求する変更(yes、Unix95、20年前と数えて)の前に、 /bin/shによって実装された言語とデフォルトのPATHのユーティリティを凍らせました。 あなたが運が良ければ、デフォルトのPATH(例えば/usr/xpg4/bin )にnotディレクトリにあるUnix95やPOSIX.1-2001のセットが存在するかもしれませんが、それらは存在することは保証されていません。

しかし、Perlの基本は、Bashよりも任意に選択されたUnixインストールに存在するmore likelyです。 (「Perlの基礎」とは、 /usr/bin/perlが存在し、おそらくかなり古いバージョンのPerl 5であることを意味します。もしあなたが運が良ければ、そのバージョンのインタプリタと共に出荷されたモジュールも利用可能です。)

したがって:

Unixであると主張するeverywhereで動作しなければならないもの( "configure"スクリプトなど)を書く場合は、 #! /bin/shを使用する必要があります#! /bin/sh #! /bin/shであり、何も拡張子を使用しないでください。 今日私はこの状況でPOSIX.1-2001準拠のシェルを書いていますが、誰かが錆びた鉄のサポートを求めたらPOSIXismにパッチを当てる用意があります。

しかし、あなたがどこでもうまくいくものを書いていnotならば、あなたがBashismをまったく使用したくない瞬間に、より良いスクリプト言語で全体を止めて書き直すべきです。 あなたの将来の自己はあなたに感謝します。

(Bashエクステンションを使用するのが適切なのisいつですか?最初の順序に:決して2次:Bashの対話的な環境を拡張するだけです - スマートなタブ補完とファンシープロンプトを提供するなど)

Related questions

Hot questions

Language

Popular Tags