web-dev-qa-db-ja.com

私のシバンとして「#!/ path / to / NAME」の代わりに「#!/ usr / bin / env NAME」を使用する方が良いのはなぜですか?

他の人から入手したスクリプトの中には、シバン#!/path/to/NAME他の人(同じツールNAMEを使用)がシバンを持っている#!/usr/bin/env NAME

どちらも正常に動作しているようです。チュートリアル(Pythonなど)では、後者のシバンの方が優れているという提案があるようです。しかし、なぜそうなのかよくわかりません。

最初のシバンにはこの制限がないのに対し、後者のシバンを使用するには、NAMEがパスに含まれている必要があることを理解しています。

また、NAMEの場所を正確に指定しているため、(私には)最初の方がシバンの方が良いようです。したがって、この場合、NAMEのバージョンが複数ある場合(たとえば、/ usr/bin/NAME、/ usr/local/bin/NAME)、最初のケースでどちらを使用するかを指定します。

私の質問は、なぜ最初のシバンが2番目のものより好ましいのですか?

497
TheGeeko61

客観的基準/要件:

absoluteまたはlogical(_/usr/bin/env_)シバのインタープリターへのパス、(2)の重要な考慮事項があります:

a)インタプリタはターゲットシステムで見つけることができます

b)正しいバージョンインタプリタターゲットシステムで見つけることができます

もし私たちが[〜#〜]同意する[〜#〜]その "b) "が望ましい、私たちも同意する

c)不正なインタープリターバージョンを使用して実行し、一貫性のない結果を得る可能性があるのではなく、スクリプトfailが望ましい。

我々が同意しないその「b)」が重要である場合、見つかった通訳で十分です。

テスト:

logicalpath- _/usr/bin/env_を使用しているため、シバンのインタプリタは同じスクリプトが同じインタープリターへの異なるパスを持つターゲットホストで正常に実行できるようにする最も拡張性の高いソリューションです。Pythonを使用しているので、それを使用してテストします。私たちの基準。

  1. _/usr/bin/env_は[〜#〜] popular [〜#〜]の予測可能な一貫した場所に存在しますか( "every ")オペレーティングシステム? はい

    • RHEL 7.5
    • Ubuntu 18.04
    • Raspbian 10(「バスター」)
    • OSX 10.15.02
  2. 以下Pythonスクリプトは仮想エンベロープの内側と外側の両方で実行されます(Pipenv使用済み)テスト中:

    #!/usr/bin/env pythonX.x import sys print(sys.version) print('Hello, world!')

  3. スクリプトのシバンはPython必要なバージョン番号(すべて同じホストにインストールされています)によって切り替えられました):

    • #!/ usr/bin/envpython2
    • #!/ usr/bin/envpython2.7
    • #!/ usr/bin/envpython3
    • #!/ usr/bin/envpython3.5
    • #!/ usr/bin/envpython3.6
    • #!/ usr/bin/envpython3.7
  4. 期待される結果:そのprint(sys.version) = _env pythonX.x_。インストールされている別のPython=バージョンを使用して_./test1.py_が実行されるたびに、she-bangで指定された正しいバージョンが出力されました。

  5. テストノート:

結論:

_#!/usr/bin/env python_がユーザーのパスにあるPythonの最初のバージョンを使用することはTRUEですが、_#!/usr/bin/env pythonX.x_などのバージョン番号を指定することでこの動作を緩和できます。実際、開発者は「first」がどのインタープリターであるかを気にしません。開発者が気にするのは、コードと互換性があることがわかっている特定のインタープリターを使用してコードが実行されることです。ファイルシステムに存在する可能性があるすべての場所で一貫した結果を保証します......

移植性/柔軟性の観点から、logical-_/usr/bin/env_-absoluteパスは要件のa)、b)およびc)を満たしているだけでなく、 Pythonですが、異なるオペレーティングシステムの異なるパスにある場合でも、同じバージョンのインタープリターをファジーロジックで検索できるという利点もあります。 そして[〜#〜] most [〜#〜]ディストリビューションはFHSを尊重しますが、すべてがそうではありません

したがって、バイナリが別の場所に存在する場合、スクリプトは[〜#〜]失敗[〜#〜]しますabsolute次に、Shebangで指定されたパス、logicalパス[〜#〜] [〜#〜]は、それが見つかるまで続行するため、一致するため、プラットフォーム全体で信頼性と拡張性が向上します。

1
F1Linux

必ずしも良いとは限りません。

#!/usr/bin/env pythonの利点は、ユーザーの$PATHに最初に現れるpython実行可能ファイルを使用することです。

#!/usr/bin/env pythonの-​​欠点は、ユーザーの$PATHに最初に現れるpython実行可能ファイルを使用するということです。

つまり、スクリプトの実行者によってスクリプトの動作が異なる可能性があります。 1人のユーザーの場合、OSと共にインストールされた/usr/bin/pythonを使用する可能性があります。もう1つは、正しく機能しない実験的な/home/phred/bin/pythonを使用している可能性があります。

また、python/usr/local/binにのみインストールされている場合、/usr/local/bin$PATHがないユーザーは、スクリプトを実行することもできません。 (それはおそらく現代のシステムではそれほど可能性が高いわけではありませんが、あいまいなインタープリターでは簡単に発生する可能性があります。)

#!/usr/bin/pythonを指定することで、スクリプトの実行に使用するインタープリターを正確に指定できます特定のシステムで

もう1つの潜在的な問題は、#!/usr/bin/envのトリック 引数をインタプリタに渡すことができない (暗黙的に渡されるスクリプトの名前以外)です。 通常は問題ではありませんが、問題になる場合があります。多くのPerlスクリプトは#!/usr/bin/Perl -wで記述されていますが、最近はuse warnings;が推奨される代替品です。 Cshスクリプトは#!/bin/csh -fを使用する必要があります--ただし、cshスクリプトは 非推奨 です。しかし、他の例があるかもしれません。

新しいシステムでアカウントを設定するときにインストールする個人用ソース管理システムに、多数のPerlスクリプトがあります。 #!にインストールするときに、各スクリプトの$HOME/bin行を変更するインストーラースクリプトを使用しています。 (私は最近#!/usr/bin/Perl以外のものを使用する必要がありませんでした。Perlがデフォルトでインストールされないことが多かった時代に戻ります。)

マイナーなポイント:#!/usr/bin/envトリックは、間違いなくenvコマンドの乱用であり、元々(名前が示すように)変更された環境でコマンドを呼び出すことを目的としていました。さらに、一部の古いシステム(正しく思い出せばSunOS 4を含む)には、/usr/binenvコマンドがありませんでした。これらのどちらも重要な問題になることはありません。 envはこのように機能します。多くのスクリプトは#!/usr/bin/envトリックを使用しており、OSプロバイダーはそれを壊すために何もしそうにありません。 可能性スクリプトを本当に古いシステムで実行したい場合は問題になりますが、とにかくそれを変更する必要があるでしょう。

もう1つの考えられる問題(コメントで指摘してくれたSopalajo de Arrierezに感謝)は、cronジョブが制限された環境で実行されることです。特に、$PATHは通常、/usr/bin:/binのようなものです。したがって、インタープリタを含むディレクトリがユーザーのシェルのデフォルトの$PATHにある場合でも、これらのディレクトリのいずれにもない場合、/usr/bin/envトリックは機能しません。正確なパスを指定するか、crontabに行を追加して$PATH(詳細はman 5 crontab)を設定できます。

491
Keith Thompson

/ usr/bin/envは_$PATH_を解釈できるため、スクリプトの移植性が向上します。

_#!/usr/local/bin/python
_

pythonが/ usr/local/binにインストールされている場合のみ、スクリプトを実行します。

_#!/usr/bin/env python
_

_$PATH_を解釈し、_$PATH_の任意のディレクトリでpythonを見つけます。

したがって、スクリプトはより移植性が高く、pythonが_/usr/bin/python_、または_/usr/local/bin/python_、または追加されたカスタムディレクトリ)としてインストールされているシステムで変更なしで動作します_$PATH_のように_/opt/local/bin/python_)に変換します。

ハードコーディングされたパスよりもenvを使用するほうが好ましい唯一の理由は、移植性です。

105
Tim Kennedy

絶対パスの指定は、特定のシステムでより正確です。欠点は、精度が高すぎることです。 Perlのシステムインストールがスクリプトに対して古すぎるため、代わりに独自のものを使用したい場合は、スクリプトを編集して#!/usr/bin/Perl#!/home/myname/bin/Perlに変更する必要があります。さらに悪いことに、一部のマシンでは/usr/bin、他のマシンでは/usr/local/bin、さらに他のマシンでは/home/myname/bin/PerlでPerlを使用している場合、スクリプトの3つの別々のコピーを保持して実行する必要があります。各マシンで適切なもの。

#!/usr/bin/envは、PATHが悪いと壊れますが、ほとんど何でもします。不正なPATHを使用して操作しようとすることはめったに役立ちませんが、スクリプトが実行されているシステムについてほとんどわかっていないため、絶対パスに頼ることはできません。

ほぼすべてのUNIXバリアントに依存できる場所として、/bin/sh/usr/bin/envの2つのプログラムがあります。一部のあいまいでほとんど廃止されたU​​nixバリアントには、/bin/envがなくても/usr/bin/envがありましたが、それらに遭遇することはほとんどありません。現代のシステムは、シバンで広く使用されているため、/usr/bin/envを持っています。 /usr/bin/envは信頼できるものです。

/bin/shを除いて、Shebangで絶対パスを使用する必要があるのは、スクリプトが移植可能ではない場合だけなので、インタープリターの既知の場所を頼りにすることができます。たとえば、Linuxでのみ機能するbashスクリプトは#!/bin/bashを安全に使用できます。社内でのみ使用することを意図したスクリプトは、社内通訳者の場所の規則に依存できます。

#!/usr/bin/envには欠点もあります。絶対パスを指定するよりも柔軟性がありますが、インタプリタ名を知っている必要があります。場合によっては、$PATHにないインタプリタを実行する必要がある場合があります。たとえば、スクリプトからの相対位置などです。このような場合、多くの場合、標準シェルと目的のインタープリターの両方で解釈できる polyglot script を作成できます。たとえば、Python 2スクリプトを、pythonがPython 3であり、python2がPython 2であるシステムの両方に移植可能にするには、およびpythonがPython 2でpython2が存在しないシステムの場合:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …

特に、Perlの場合、#!/usr/bin/envは2つの理由で悪い考えです。

まず、それは移植可能ではありません。一部のあいまいなプラットフォームでは、envは/ usr/binにありません。次に、 Keith Thompson が指摘したように、Shebang行で引数を渡すときに問題が発生する可能性があります。最大限にポータブルなソリューションはこれです:

#!/bin/sh
exec Perl -x "$0" "$@"
#!Perl

動作の詳細については、「perldoc perlrun」および-x引数についての説明を参照してください。

23
DrHyde

2つの間に違いがあるのは、スクリプトの実行方法が原因です。

_/usr/bin/env_の後に実行可能ファイルの名前を置くことはできないため、_/usr/bin_(一部のOSでは_#!_にはない)を使用する必要があります。絶対パス。これは、_#!_メカニズムがシェルよりも低いレベルで機能するためです。カーネルのバイナリローダーの一部です。これはテストできます。これをファイルに入れ、実行可能としてマークします。

_#!bash

echo 'foo'
_

実行しようとすると、次のようなエラーが出力されます。

_Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.
_

ファイルが実行可能としてマークされ、_#!_で始まる場合、カーネル(_$PATH_または現在のディレクトリを知らない:これらはユーザーランドの概念です)は、絶対パスを使用してファイルを探します。絶対パスの使用は問題があるため(他の回答で述べたように)、誰かがトリックを思い付きました:_/usr/bin/env_(ほとんどの場合、その場所にあります)を実行して、_$PATH_を使用して何かを実行できます。

16
Tiffany Bennett

#!/usr/bin/envの使用には、さらに2つの問題があります。

  1. それはインタプリタへのフルパスを指定する問題を解決しません、それを単にenvに移動します。

    env/usr/bin/envにあることが保証されているのは、bash/bin/bashまたはpython in /usr/bin/pythonにあることが保証されています。

  2. envは、ARGV [0]をインタープリターの名前(bashやpythonなど)で上書きします。

    これにより、スクリプトの名前がpsなどの出力に表示されなくなり(または、表示方法や表示場所が変更され)、たとえばps -C scriptname.shでそれを見つけることができなくなります。

[2016年6月4日更新]

そして3番目の問題:

  1. PATHを変更することは、スクリプトの最初の行を編集するだけではなく、もっと作業が多く、特にそのような編集のスクリプトを作成するのは簡単です。例えば:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    $ PATHにディレクトリを追加またはプリペンドするのはかなり簡単です(ただし、ファイルを編集して永続化するには、~/.profileなどを使用する必要があります。また、PATHはどこにでも設定できるため、編集するスクリプトを作成するのは簡単ではありません。 1行目ではありません)。

    PATHディレクトリの順序を変更することは非常に難しく、... #!行を編集するよりもはるかに困難です。

    そして、#!/usr/bin/envを使用することで生じる他のすべての問題がまだあります。

    @jlliagreは、コメントで#!/usr/bin/envが「PATH/PATH順序を変更するだけで」複数のインタープリターバージョンでスクリプトをテストするのに役立つことを示唆しています

    これを行う必要がある場合は、スクリプトの先頭にいくつかの#!行を追加し(最初の行を除いてコメントのみです)、使用する行を切り取り/コピーして貼り付けます最初の行に。

    viでは、カーソルを目的の#!行に移動して、dd1GPまたはY1GPと入力するだけです。 nanoと同じくらい簡単なエディタを使用しても、マウスを使用してコピーして貼り付けるのに数秒かかります。


全体として、#!/usr/bin/envを使用する利点はせいぜい最小限であり、欠点を上回らないことは確かです。 「便利さ」の利点でさえ、ほとんど幻想です。

IMO、それは、オペレーティングシステムは操作するものではないと考える特定の種類のプログラマーによって促進された愚かなアイデアであり、それらは回避される(または無視される)問題ですベスト)。

PS:複数のファイルのインタープリターを一度に変更する簡単なスクリプトを次に示します。

change-Shebang.sh

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  Shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  Shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$Shebang" . w | ed "$f"
done

たとえば、change-Shebang.sh python2.7 *.pyまたはchange-Shebang.sh $HOME/bin/my-experimental-Ruby *.rbとして実行します。

11
cas

ここに別の例を追加します。

envの使用は、たとえば、複数のrvm環境間でスクリプトを共有する場合にも役立ちます。

これをcmd行で実行すると、#!/usr/bin/env Rubyがスクリプト内で使用されている場合に、どのRubyバージョンが使用されるかがわかります。

env Ruby --version

したがって、envを使用すると、スクリプトを変更せずに、rvmを介して異なるRubyバージョンを使用できます。

9
Not Now

自分または仕事のためだけに書いていて、呼び出すインタプリタの場所が常に同じ場所にある場合は、必ずダイレクトパスを使用してください。その他の場合はすべて#!/usr/bin/envを使用します。

理由は次のとおりです。あなたの状況では、pythonインタープリターは、使用した構文に関係なく同じ場所にありましたが、多くの人にとっては、別の場所にインストールされている可能性があります。ほとんどの主要なプログラミングインタープリターは/usr/bin/にありますが、新しいソフトウェアの多くはデフォルトで/usr/local/bin/になっています。

同じインタープリターの複数のバージョンがインストールされていて、シェルのデフォルトの設定がわからない場合は、おそらく#!/usr/bin/envを使用するようにさえ主張しますが、おそらくそれを修正する必要があります。

5
Mauvis Ledford

移植性と互換性の理由から、使用することをお勧めします

#!/usr/bin/env bash

の代わりに

#!/usr/bin/bash

バイナリがLinux/Unixシステムに配置される可能性は多数あります。ファイルシステム階層の詳細については、hier(7)のマンページを確認してください。

たとえば、FreeBSDは、ベースシステムの一部ではないすべてのソフトウェアを/ usr/local /にインストールします。 bashはベースシステムの一部ではないため、bashバイナリは/ usr/local/bin/bashにインストールされます。

ほとんどのLinuxディストリビューションとFreeBSDで動作するポータブルbash/csh/Perl/whateverスクリプトが必要な場合は、#!/ usr/bin/env

また、ほとんどのLinuxインストールでは、envバイナリが/ bin/envに(ハード)リンクされているか、/ usr/binが/ binにソフトリンクされているため、notを使用する必要があります。シバンで。 #!/ bin/envは使用しないでください。

3
Mirko Steiner