回答:
シェルスクリプトの移植性には、おおよそ4つのレベルがあります(シバンの行に関する限り)。
最も移植性が高い:#!/bin/sh
シェバンを使用し、POSIX標準で指定されている基本的なシェル構文のみを使用する。これは、ほとんどすべてのPOSIX / unix / linuxシステムで動作するはずです。(まあ、本当のレガシーBourneシェルを備えたSolaris 10以前を除き、POSIXよりも前に準拠していたため、/bin/sh
。)
2番目に移植性が高い:(#!/bin/bash
または#!/usr/bin/env bash
)シェバンラインを bash v3の機能に固執します。これは、bashがあるシステム(予想される場所)で機能します。
3番目に移植性が高い:(#!/bin/bash
または#!/usr/bin/env bash
)シェバンラインを使用し、bash v4機能を使用します。これは、bash v3(macOSなど、ライセンス上の理由で使用する必要がある)があるシステムでは失敗します。
最小の移植性:#!/bin/sh
shebangを使用し、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の拡張です。古い(標準)コマンド形式は、コマンドの後にオプション(単一のダッシュ、各オプションが単一の文字)が続き、コマンド引数。最近の(多くの場合、移植できない)バリアントには、長いオプション(通常は)が含まれ、--
引数の後にオプションを追加できるようにし、--
オプションを引数から分離するために使用します。
sh
)のみを指定するため/bin/sh
、正しいパスであるとは限りません。最もポータブルなのは、シバンをまったく指定しないこと、または使用するOSにシバンを適合させることです。
#!/bin/sh
するとすぐ#!/bin/bash
に、スクリプトは完全に機能しました。(私の防御のために、そのスクリプトは、sh-ismのみを本当に必要とするものから、bashのような振る舞いに依存するものへと時間とともに進化しました。)
ksh
、Bourneではなく動作から作成されました。何FHS以下のほとんどのLinuxディストリビューションを持っているん/bin/sh
通常、彼らはPOSIX互換性を提供するために選択し、シェルへのシンボリックリンクであることbash
かdash
。
./configure
TXR言語を構築するためのスクリプトでは、移植性を高めるために次のプロローグを書きました。#!/bin/sh
POSIXに準拠していない古い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/sh
Bashが見つからない場合に起動します。
プロローグは保守的なシェル方言で書かれており、test
ファイル存在テスト、および${@+"$@"}
壊れた古いシェルに対応する引数を拡張するためのトリック(単純に"$@"
POSIX準拠のシェルであった場合)を使用します。
x
引用適切にしているイディオムサラウンド現在では非推奨テストの呼び出しを義務付け状況以来、使用されていた場合には牛車を-a
または-o
複数のテストを組み合わせます。
test x$whatever
私は、ワニス中のタマネギのようにそこにルックスを犯してること。壊れた古いシェルがクォートを行うことを信頼できない場合、最後の${@+"$@"}
試みは無意味です。
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 -print0
、tar --null
)なしでは、正しく動作させることはできません。Perlでその行を再実装すると、はるかに長く不格好になります。これは、bashismsやその他の非POSIX拡張機能を使用するのが適切な場合の一種の状況です。
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
です。)
find
私のシナリオでは50,000を超えるファイル名が返されるため、スクリプトが引数の長さの制限に達する可能性が高いことです。非POSIX拡張機能を使用しない正しい実装では、tar出力をインクリメンタルにビルドするか、PerlコードでArchive :: Tar :: Streamを使用する必要があります。もちろん、それを行うことはできますが、この場合、Bashスクリプトは書くのがはるかに速く、定型文が少ないため、バグの余地が少なくなります。
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はありません。新しいスクリプトは、古代の宇宙よりもそこで実行される可能性が高くなります。
#!/bin/sh
元のシェルが提供したもの以外は使用しないでください。