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


11

上部のコメントセクションからヘルプとバージョン情報を印刷できるように、現在のスクリプトを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は常にスクリプトが呼び出された方法とまったく同じです。その場合、の値は絶対パスです。したがって、別のコマンドに渡しても安全なようです。$0

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

ご質問

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

人々は$0、質問のタイトルに答える台本以外の何かがある状況について答えてきました。ただし、$0スクリプト自体は含まれているがディレクトリは含まれていない状況にも興味があります。特に、SOの回答に対するコメントを理解しようとしています。
toxalot 2014年

回答:


17

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

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

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

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

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

the-shell -shell-options the/script its args

$0取得しthe/scriptます。

実行すると:

the/script its args

あなたのシェルは次のことをします:

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

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

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

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

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

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

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

さて、あなたはそれが主張すること/dev/fd/x そのスクリプトへのパス。ただし、から読み取る場合$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実際に現在のディレクトリで検索が行われない限り、スクリプトへのパス含まれていませんでした$PATHthe-script

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

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

zshshエミュレーションのように行うだろうbash場合ことを除いてthe-script、現在のディレクトリに壊れたシンボリックリンクで、それがために検索しないだろうthe-script$PATH、代わりにエラーを報告します。

他のすべてのBourneのようなシェルはで検索the-scriptされません$PATH

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

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

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は、セパレーターの代わりに区切り文字$IFSとして機能するシェルを持つ末尾の空の要素を保持することです)。

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

the-shell < the-script

または:

cat the-script | the-shell

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

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

次のこともできます:

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

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

または:

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

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


ステファン、"-"上の例でのあなたの使い方がわかりません。私の経験でexec("the-script", ["the-script", "its", "args"])exec("/the/interpreter", ["/the/interpreter", "the-script", "its", "args"])、になります。もちろん、通訳オプションの可能性もあります。
jrw32982は、

@ jrw32982、#! /bin/sh -ある「常に使用しcmd -- somethingますが、それが保証できない場合somethingに起動しません-ここに適用されることをお勧めの格言/bin/sh(ここで、-エンド・オブ・オプションマーカーは、よりポータブルであるとして--)とsomethingのパス/名前であること脚本。あなたはsetuidさスクリプトのためにそれを使用しない場合(ただし、それらをサポートするシステムでは/ dev / fdの答えで述べた/ X法)、その後、1と呼ばれるスクリプトへのシンボリックリンクを作成することにより、ルートシェルを得ることができる-i-sのためにインスタンス。
ステファンChazelas

ありがとう、ステファン。私はあなたの例のシバン行の末尾の単一のハイフンを逃しました。1つのハイフンが2つのハイフンpubs.opengroup.org/onlinepubs/9699919799/utilities/sh.htmlと同等であると記載されている場所を探す必要がありました
jrw32982は

setuidスクリプトのシバン行の末尾のシングル/ダブルハイフンを忘れるのは簡単すぎます。どういうわけか、システムはあなたのためにそれを処理する必要があります。setuidスクリプトの実行を完全に禁止するか、setuidスクリプト/bin/shの実行を検出できる場合は、何らかの方法で独自のオプション処理を無効にする必要があります。/ dev / fd / xを使用するだけでそれがどのように修正されるかはわかりません。まだシングル/ダブルハイフンが必要だと思います。
jrw32982は

@ jrw32982、ではなく、で/dev/fd/x始まります。第一の目標は2つの間で競合状態取り除くためにある間に(にもかかわらずSを特権とそれに続く上昇いる)非常によく、その間に何か他のものへのシンボリックリンクに置き換えていた可能性がある(後にザ・スクリプトが開きます。実装システムSUIDスクリプト適切に行う代わりに、n)は最初はexecve(の一環として開かれているが。/-execve()execve("the-script")execve("interpreter", "thescript")interpreterexecve("interpreter", "/dev/fd/n")
ステファンChazelas

6

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

> bash scriptname
scriptname

> bash <scriptname
bash

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

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

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

注意事項

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

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


3

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

sh -c 'echo $0' argv0

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

-c

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


-c 'command'はオペランドでありargv0、最初の非オペランドコマンドライン引数であるため、これは機能すると思います。
mikeserv 2014年

@mike正解、私はmanスニペットで更新しました。
Graeme

3

注:他の人はすでにのメカニズムを説明している$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

追加の策略

私たちが調べるとき今、一貫した結果で返される$0経てreadlink、我々は単純に使用できるdirname $(readlink -f $0)スクリプト-または-への絶対パスを取得するにはbasename $(readlink -f $0)、スクリプトの実際の名前を取得します。


0

私のmanページは言う:

$0:に展開nameしますshellshell script

これargv[0]は現在のシェルに変換されるようです-または、現在解釈中のシェルが呼び出されたときに供給される最初の非オペランドコマンドライン引数。私は以前にsh ./somescriptその変数をにパスすることを述べましたが、これは独自の新しいプロセスであり、新しいで呼び出されるため、これは正しくありませんでした。$0 $ENV shshell$ENV

このsh ./somescript.sh. ./somescript.shで、は現在の環境で実行され、$0すでに設定されているものとは異なります。

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

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

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


私のシステムでは、それがソース(. ./myscript.shまたはsource ./myscript.sh)の場合$0、シェルです。ただし、シェル(sh ./myscript.sh)に引数として渡された場合$0は、スクリプトへのパスです。もちろん、sh私のシステムではBashです。したがって、それが違いを生むかどうかはわかりません。
toxalot 2014年

ハッシュバングはありますか?私は違いが重要だと思います-そして私はそれを追加することができます-それなしではshはexecそれをすべきではなく代わりにそれを調達するべきですが、それとともにそれはそうするべきexecです。
mikeserv 2014年

ハッシュバンの有無にかかわらず、同じ結果が得られます。実行可能であってもなくても、同じ結果が得られます。
toxalot 2014年

私も!それは組み込みのシェルだからだと思います。確認しています...
mikeserv 2014年

強打線はカーネルによって解釈されます。あなたがexecした場合./somescriptbanglineと#!/bin/sh、それが実行することと同じになります/bin/sh ./somescript。それ以外の場合は、シェルに違いはありません。
Graeme

0

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

一方で$0、それはスクリプトが呼び出された方法に基づいて接頭辞パスを含むことができ、スクリプト名が含まれている、私はいつも使用している${0##*/}から、すべての主要なパスを削除するヘルプの出力にスクリプト名を印刷します$0

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

${var#Pattern}

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

${var##Pattern}

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

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


はい、ヘルプメッセージで使用さているスクリプトの名前についても同様です。しかし、私はスクリプトのgreppingについて話しているので、少なくとも相対パスが必要です。ヘルプメッセージをコメントの一番上に配置し、後で印刷するときにヘルプメッセージを繰り返す必要がないようにしたい。
toxalot 2014年

0

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

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

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

rPath 常に同じ値になります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.