web-dev-qa-db-ja.com

$ 0には常にスクリプトへのパスが含まれますか?

上部のコメントセクションからヘルプとバージョン情報を印刷できるように、現在のスクリプトをgrepしたいと思います。

私はこのようなことを考えていました:

_grep '^#h ' -- "$0" | sed -e 's/#h //'
_

しかし、スクリプトがPATHにあるディレクトリにあり、ディレクトリを明示的に指定せずに呼び出された場合はどうなるのだろうと思いました。

特殊変数の説明を検索したところ、_$0_の次の説明が見つかりました。

  • 現在のシェルまたはプログラムの名前

  • 現在のスクリプトのファイル名

  • スクリプト自体の名前

  • 実行されたときのコマンド

これらのいずれも、スクリプトがディレクトリなしで呼び出された場合に_$0_の値にディレクトリが含まれるかどうかを明確にしません。最後の1つは、実際にはそうではないことを意味します。

マイシステムでのテスト(Bash 4.1)

/ usr/local/binにscriptnameという名前の実行可能ファイルを1行_echo $0_で作成し、別の場所から呼び出しました。

これらは私の結果です:

_> cd /usr/local/bin/test
> ../scriptname
../scriptname

> cd /usr/local/bin
> ./scriptname
./scriptname

> cd /usr/local
> bin/scriptname
bin/scriptname

> cd /tmp
> /usr/local/bin/scriptname
/usr/local/bin/scriptname

> scriptname
/usr/local/bin/scriptname
_

これらのテストでは、_$0_の値は常に、スクリプトが呼び出された方法とまったく同じですexcept(パスコンポーネントなしで呼び出された場合)。その場合、_$0_の値は絶対パスです。したがって、別のコマンドに渡しても安全なようです。

しかしそれから私は Stack Overflow についてのコメントに出くわしました、それは私を混乱させました。答えは、現在のスクリプトのディレクトリを取得するために$(dirname $0)を使用することを提案しています。コメント(7回賛成)は、「スクリプトがパスにある場合は機能しません」と述べています。

質問

  • そのコメントは正しいですか?
  • 他のシステムでは動作が異なりますか?
  • _$0_がディレクトリを含まない状況はありますか?
11
toxalot

最も一般的なケースでは、$0には、絶対パスまたはスクリプトへの相対パスが含まれるため、

script_path=$(readlink -e -- "$0")

readlinkコマンドがあり、それが-eをサポートしていると仮定して)は、通常、スクリプトへの正規の絶対パスを取得するのに十分な方法です。

$0は、インタープリターに渡されるスクリプトを指定する引数から割り当てられます。

たとえば、次の場所にあります。

the-Shell -Shell-options the/script its args

$0the/scriptを取得します。

実行すると:

the/script its args

シェルは次のことを行います:

exec("the/script", ["the/script", "its", "args"])

たとえば、スクリプトに#! /bin/sh - she-bangが含まれている場合、システムはそれを次のように変換します。

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "the/script", "its", "args"])

(シバンが含まれていない場合、またはより一般的にはシステムがENOEXECエラーを返した場合、同じことを行うシェルです)

一部のシステムのsetuid/setgidスクリプトには例外があり、システムは一部のfdxでスクリプトを開いて代わりに実行します。

exec("/bin/sh", ["/bin/sh" or "the/script", "-", "/dev/fd/x", "its", "args"])

競合状態を回避するため(この場合、$0には/dev/fd/xが含まれます)。

ここで、/dev/fd/xisがそのスクリプトへのパスであると主張するかもしれません。ただし、$0から読み取る場合は、入力を使用するときにスクリプトが壊れます。

ここで、呼び出されたスクリプトコマンド名にスラッシュが含まれていない場合は、違いがあります。に:

the-script its args

シェルはthe-script$PATHを検索します。 $PATHには、一部のディレクトリへの絶対パスまたは相対パス(空の文字列を含む)を含めることができます。たとえば、$PATH/bin:/usr/bin:が含まれ、the-scriptが現在のディレクトリで見つかった場合、シェルは次のことを行います。

exec("the-script", ["the-script", "its", "args"])

これは次のようになります:

exec("/bin/sh", ["/bin/sh" or "the-script", "-", "the-script", "its", "args"]

または、/usr/binにある場合:

exec("/usr/bin/the-script", ["the-script", "its", "args"])
exec("/bin/sh", ["/bin/sh" or "the-script" or "/usr/bin/the-script",
     "-", "/usr/bin/the-script", "its", "args")

上記のsetuidコーナーケースを除くすべてのケースで、$0にはスクリプトへのパス(絶対パスまたは相対パス)が含まれます。

これで、スクリプトは次のように呼び出すこともできます。

the-interpreter the-script its args

上記のthe-scriptにスラッシュ文字が含まれていない場合、動作はシェルによってわずかに異なります。

古いAT&T ksh実装は実際に$PATHで無条件にスクリプトを検索していました(これは実際にはバグであり、setuidスクリプトのセキュリティホールでした)。したがって、$0は実際にnotへのパスを含みます$PATHルックアップが実際に現在のディレクトリでthe-scriptを見つけた場合を除いて、スクリプト。

新しいAT&T kshは、現在のディレクトリでthe-scriptが読み取り可能であれば、それを解釈しようとします。そうでない場合は、読み取り可能なおよび実行可能ファイルthe-script内の$PATHを検索します。

bashの場合、the-scriptが現在のディレクトリにあるかどうか(壊れたシンボリックリンクではないかどうか)をチェックし、ない場合は、the-scriptで読み取り可能な(必ずしも実行可能ではない)$PATHを検索します。

zsh in shエミュレーションはbashと同様ですが、the-scriptが現在のディレクトリで壊れたシンボリックリンクである場合、the-script$PATHで検索せず、代わりにエラー。

他のすべてのBourneのようなシェルは、the-script$PATHを検索しません。

とにかく、これらすべてのシェルについて、$0/が含まれておらず、読み取りもできないことがわかった場合は、おそらく$PATHで検索されています。次に、$PATH内のファイルは実行可能である可能性が高いので、command -v -- "$0"を使用してそのパスを見つけることはおそらく安全な概算です(ただし、$0がShellビルトインまたはキーワード(ほとんどのシェル)の名前でもある場合、これは機能しません) )。

したがって、そのケースを本当にカバーしたい場合は、次のように書くことができます。

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}""; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

""に追加された$PATHは、$IFSseparatorではなくdelimiterとして機能するシェルで末尾の空の要素を保持するためです)。

現在、スクリプトを呼び出すためのより難解な方法があります。次のことができます:

the-Shell < the-script

または:

cat the-script | the-Shell

その場合、$0インタープリターが受け取った最初の引数(argv[0])になります(the-Shellより上ですが、通常はベース名またはそのインタープリターへの1つのパスのいずれでもかまいません)。

$0の値に基づいてその状況にあることを検出することは信頼できません。 ps -o args= -p "$$"の出力を見て、手掛かりを得ることができます。パイプの場合、スクリプトへのパスに戻るための実際の方法はありません。

次のこともできます:

the-Shell -c '. the-script' blah blih

次に、zsh(およびBourne Shellの古い実装)を除いて、$0blahになります。繰り返しになりますが、これらのシェルでスクリプトのパスを取得するのは困難です。

または:

the-Shell -c "$(cat the-script)" blah blih

等.

正しい$prognameがあることを確認するには、次のようにして特定の文字列を検索できます。

progname=$0
[ -r "$progname" ] || progname=$(
    IFS=:; set -f
    for i in ${PATH-$(getconf PATH)}:; do
      case $i in
        "") p=$progname;;
        */) p=$i$progname;;
        *) p=$i/$progname
      esac
      [ -r "$p" ] && exec printf '%s\n' "$p"
    done
    exit 1
  ) && progname=$(readlink -e -- "$progname") ||
  progname=unknown

[ -f "$progname" ] && grep -q 7YQLVVD3UIUDTA32LSE8U9UOHH < "$progname" ||
  progname=unknown

しかし、繰り返しますが、努力する価値はないと思います。

17

ディレクトリがnotに含まれる2つの状況を次に示します。

> bash scriptname
scriptname

> bash <scriptname
bash

どちらの場合も、現在のディレクトリはscriptnameが配置されたディレクトリでなければなりません。

最初のケースでは、$0の値をgrepに渡すことができます。これは、FILE引数が現在のディレクトリに関連していると想定しているためです。

2番目のケースでは、ヘルプとバージョン情報が特定のコマンドラインオプションへの応答としてのみ出力される場合は、問題にはなりません。ヘルプやバージョン情報を出力するために、なぜそのようにスクリプトを呼び出すのかわかりません。

警告

  • スクリプトが現在のディレクトリを変更する場合、相対パスを使用する必要はありません。

  • スクリプトがソースである場合、$0の値は通常、ソーススクリプトではなく呼び出し元スクリプトになります。

6
toxalot

ほとんどの(すべての)シェルで-cオプションを使用する場合、任意の引数ゼロを指定できます。例えば:

sh -c 'echo $0' argv0

man bashから(これは私のman shよりも説明が優れているために選択されました-使用方法は同じです):

-c

-cオプションが存在する場合、コマンドは最初の非オプション引数command_stringから読み取られます。 command_stringの後に引数がある場合、それらは$ 0から始まる定位置パラメーターに割り当てられます。

3
Graeme

注:他の人は_$0_の仕組みをすでに説明しているので、すべてスキップします

私は通常、この問題全体を回避して、単純にコマンド_readlink -f $0_を使用します。これにより、引数として与えたすべてのパスが常に返されます。

私が最初にここにいるとしましょう:

_$ pwd
/home/saml/tst/119929/adir
_

ディレクトリとファイルを作成します。

_$ mkdir adir
$ touch afile
$ cd adir/
_

readlinkを自慢し始めましょう:

_$ readlink -f ../adir
/home/saml/tst/119929/adir

$ readlink -f ../
/home/saml/tst/119929

$ readlink -f ../afile 
/home/saml/tst/119929/afile

$ readlink -f .
/home/saml/tst/119929/adir
_

追加の策略

これで、readlinkを介して_$0_に問い合わせたときに一貫した結果が返されるので、単純にdirname $(readlink -f $0)を使用してスクリプトへの絶対パスを取得できます-または-basename $(readlink -f $0)スクリプトの実際の名前を取得します。

3
slm

私が使用する同様の何かのために:

rPath="$(dirname $(realpath $0))"
echo $rPath 

rPath=$(dirname $(readlink -e -- "$0"))
echo $rPath 

rPathは常に同じ値です。

0
Anonimous

私のmanページは言う:

$0nameShellまたはShell script

これは、現在のシェルのargv[0]または、現在解釈しているシェルの最初のnonoperandコマンドライン引数に変換されるようです呼び出されたときに供給されます。 sh ./somescript$0 $ENVvariableshしかし、Shellは独自の新しいプロセスであり、新しい$ENVで呼び出されます

このように、sh ./somescript.sh. ./somescript.shとは異なります current環境および$0はすでに設定されています。

$0/proc/$$/statusと比較することでこれを確認できます。

echo 'script="/proc/$$/status"
    echo $0
    cat "$script"' \
    > ./script.sh
sh ./script.sh ; . ./script.sh

@toxalot、訂正ありがとうございます。私は何かを学びました。

0
mikeserv

上部のコメントセクションからヘルプとバージョン情報を印刷できるように、現在のスクリプトをgrepしたいと思います。

$0にはスクリプト名が含まれますが、スクリプトの呼び出し方法に基づいてプレフィックス付きのパスが含まれる場合がありますが、私は常に${0##*/}を使用して、$0から先頭のパスを削除するヘルプ出力にスクリプト名を出力しました。

抜粋 高度なBashスクリプトガイド-セクション10.2パラメータの置換

${var#Pattern}

$varのフロントエンドに一致する$Patternの最短部分を$varから削除します。

${var##Pattern}

$varのフロントエンドと一致する$Patternの最も長い部分を$varから削除します。

したがって、$0に一致する*/の最も長い部分は、パスのプレフィックス全体であり、スクリプト名のみを返します。

0
sambler