#!/ bin / sh対#!/ bin / bashにより移植性が最大化


36

I私はシンボリックリンクを理解して何からUbuntuのLTSサーバと通常の作業/bin/sh/bin/dash。への他の多くのディストリビューション/bin/shへのシンボリックリンク/bin/bash

それから、スクリプトが#!/bin/sh上で使用する場合、すべてのサーバーで同じように実行されない可能性があることを理解していますか?

サーバー間でスクリプトの移植性を最大限にしたい場合に、スクリプトに使用するシェルに関する推奨プラクティスはありますか?


さまざまなシェルにはわずかな違いがあります。移植性が最も重要な場合は、#!/bin/sh元のシェルが提供したもの以外は使用しないでください。
トールビョールンラヴンアンデルセン

回答:


65

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

  1. 最も移植性が高い:#!/bin/shシェバンを使用し、POSIX標準で指定されている基本的なシェル構文のみを使用する。これは、ほとんどすべての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/shshebangを使用し、POSIXシェル構文にbash拡張機能を使用します。これは、/ bin / shのbash以外のもの(最新のUbuntuバージョンなど)があるシステムでは失敗します。これを絶対にしないでください。それは単なる互換性の問題ではなく、単に間違っているだけです。残念ながら、多くの人が犯すエラーです。

私の推奨事項:スクリプトに必要なすべてのシェル機能を提供する最初の3つの中で最も保守的なものを使用します。移植性を最大限に高めるには、オプション#1を使用しますが、私の経験では、いくつかのbash機能(配列など)が十分役立つので、#2に進みます。

あなたができる最悪のことは、間違ったシバンを使用する#4です。どの機能が基本的なPOSIXで、どの機能がbash拡張機能であるかわからない場合は、bash shebangに固執する(つまり、オプション#2)か、非常に基本的なシェル(Ubuntu LTSサーバーのダッシュなど)でスクリプトを徹底的にテストします。Ubuntu wikiには、注意すべきバシズムの良いリストがあります。

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

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

移植性の面で最も厄介なコマンドの1つは、最も基本的なものの1つですecho。オプション(echo -nまたはecho -e)、または印刷する文字列内のエスケープ(バックスラッシュ)で使用すると、バージョンが異なると動作が異なります。文字列をその後に改行なしで、または文字列にエスケープを付けて印刷したい場合は、printf代わりに使用します(そして、それがどのように機能するかを学びます-それechoはもっと複雑です)。psコマンドがあるにも混乱

注目すべきもう1つの一般的なことは、コマンドオプション構文の最近/ GNUishの拡張です。古い(標準)コマンド形式は、コマンドの後にオプション(単一のダッシュ、各オプションが単一の文字)が続き、コマンド引数。最近の(多くの場合、移植できない)バリアントには、長いオプション(通常は)が含まれ、--引数の後にオプションを追加できるようにし、--オプションを引数から分離するために使用します。


12
4番目のオプションは、単に間違った考えです。使用しないでください。
pabouk

3
@pabouk私は完全に同意したので、これを明確にするために回答を編集しました。
ゴードン

1
あなたの最初の声明は少し誤解を招くものです。POSIX規格は、それを使用すると不特定の動作につながると伝える外部のシバンについては何も指定していません。さらに、POSIXはposixシェルの場所を指定せず、その名前(sh)のみを指定するため/bin/sh、正しいパスであるとは限りません。最もポータブルなのは、シバンをまったく指定しないこと、または使用するOSにシバンを適合させることです。
-jlliagre

2
私はごく最近、私のスクリプトで#4に落ちましたが、なぜそれが機能しなかったのかわかりませんでした。結局のところ、同じコマンドがしっかりと機能し、シェルで直接試してみたときに、私が望んでいたことを正確に実行しました。に変更#!/bin/shするとすぐ#!/bin/bashに、スクリプトは完全に機能しました。(私の防御のために、そのスクリプトは、sh-ismのみを本当に必要とするものから、bashのような振る舞いに依存するものへと時間とともに進化しました。)
CVn

2
@MichaelKjörling(true)BourneシェルはLinuxディストリビューションにバンドルされることはほとんどなく、とにかくPOSIX準拠ではありません。POSIX標準シェルはksh、Bourneではなく動作から作成されました。何FHS以下のほとんどのLinuxディストリビューションを持っているん/bin/sh通常、彼らはPOSIX互換性を提供するために選択し、シェルへのシンボリックリンクであることbashdash
jlliagre

5

./configureTXR言語を構築するためのスクリプトでは、移植性を高めるために次のプロローグを書きました。#!/bin/shPOSIXに準拠していない古いBourne Shellであっても、スクリプトはそれ自体をブートストラップします。(すべてのリリースを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ボックスでは、/usr/xpg4/bin/shBashが見つからない場合に起動します。

プロローグは保守的なシェル方言で書かれており、testファイル存在テスト、および${@+"$@"}壊れた古いシェルに対応する引数を拡張するためのトリック(単純に"$@"POSIX準拠のシェルであった場合)を使用します。


一つは必要はありませんx引用適切にしているイディオムサラウンド現在では非推奨テストの呼び出しを義務付け状況以来、使用されていた場合には牛車を-aまたは-o複数のテストを組み合わせます。
チャールズダフィー

@CharlesDuffy Indeed; test x$whatever私は、ワニス中のタマネギのようにそこにルックスを犯してること。壊れた古いシェルがクォートを行うことを信頼できない場合、最後の${@+"$@"}試みは無意味です。
カズ

2

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

シェル言語がこれらの新しい言語よりも優れている唯一の利点は、それ自体を呼び出すもの/bin/shが、Unixであると称するものすべてに存在することが保証されることです。ただし、その何かはPOSIX準拠でさえないかもしれません。レガシーのプロプライエタリなUnixの多くは、Unix95が要求する変更(はい、Unix95、20年前と数えられる)の前/bin/shに、デフォルトのPATH 実装された言語とユーティリティを凍結しました。そこUNIX95の集合であるかもしれない、または運がよけれ場合でもPOSIX.1-2001は、ディレクトリ内のツールはありませんデフォルトのPATHに(例えば/usr/xpg4/bin)が、それらが存在することが保証されていません。

ただし、Perlの基本は、Bashよりも任意に選択されたUnixインストール上に存在する可能性高くなります。(私は意味「のPerlの基礎」とは、/usr/bin/perl存在しているいくつかのPerl 5の、おそらくかなり古い、バージョン、およびあなたしている幸運ならば通訳のそのバージョンに同梱されていることをモジュールのセットもご用意しています。)

したがって:

あなたが仕事に持っている何かを書いている場合はどこでも趣旨は、(例えば、「のconfigure」スクリプトなど)のUnixすることを、あなたが使用する必要がある#! /bin/sh、とあなたはいかなる拡張機能を使用しないようにする必要があります。最近では、この状況でPOSIX.1-2001準拠のシェルを作成しますが、さびた鉄のサポートを求められた場合はPOSIXismsを修正する準備ができています。

しかし、どこでも動作しなければならない何かを書いていない場合、Bashismを使用したいと思った瞬間に、代わりに全体を停止して、より良いスクリプト言語で書き直す必要があります。あなたの将来の自己はあなたに感謝します。

(だから、Bash拡張機能を使用するのが適切なのいつですか?最初の順序に:しない。2番目の順序に:Bashの対話型環境を拡張するだけです。


先日私はこんなことをするスクリプトを書かなければなりませんでしたfind ... -print0 |while IFS="" read -rd "" file; do ...; done |tar c --null -T -。bashisms(read -d "")およびGNU拡張機能(find -print0tar --null)なしでは、正しく動作させることはできません。Perlでその行を再実装すると、はるかに長く不格好になります。これは、bashismsやその他の非POSIX拡張機能を使用するのが適切な場合の一種の状況です。
ミチャウ

@michauこれらの省略記号の内容によっては、ワンライナーとしては適格ではないかもしれませんが、私が意図したとおりに「より良いスクリプト言語で全体を書き換える」ことはしていないと思います。私のやり方はperl -MFile::Find -e 'our @tarcmd = ("tar", "c"); find(sub { return unless ...; ...; push @tarcmd, $File::Find::name }, "."); exec @tarcmd、バシズムを必要としないだけでなく、GNU tar拡張機能も必要ないことに注意してください。(コマンドラインの長さが懸念される場合、またはスキャンとtarを並行して実行する場合は、必要tar --null -Tです。)
zwol

問題は、find私のシナリオでは50,000を超えるファイル名が返されるため、スクリプトが引数の長さの制限に達する可能性が高いことです。非POSIX拡張機能を使用しない正しい実装では、tar出力をインクリメンタルにビルドするか、PerlコードでArchive :: Tar :: Streamを使用する必要があります。もちろん、それを行うことはできますが、この場合、Bashスクリプトは書くのがはるかに速く、定型文が少ないため、バグの余地が少なくなります。
michau

ところで、Bashの代わりにPerlをすばやく簡潔に使用できますfind ... -print0 |perl -0ne '... print ...' |tar c --null -T -。しかし、質問は次のとおりです。1)私は使用find -print0しているtar --nullとにかく、read -d ""まったく同じ種類のものであるbashismを回避するポイントは何ですか(Perlとは異なり、いつかPOSIXのものになる可能性のあるヌルセパレータを処理するためのGNU拡張機能) )?2)Perlは実際にはBashよりも普及していますか?私は最近Dockerイメージを使用しており、それらの多くにはBashがありますがPerlはありません。新しいスクリプトは、古代の宇宙よりもそこで実行される可能性が高くなります。
ミッチャウ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.