ソースシェルスクリプトへのパスを決定する


80

ソースシェルスクリプトがそれ自体へのパスを見つける方法はありますか?私は主にbashに関心がありますが、tcshを使用する同僚もいます。

ソーシングによりコマンドが現在のシェルで実行されるため$0、ソーススクリプトではなく、現在のシェルの呼び出しも同じであるため、ここではあまり運がないかもしれません。私の現在の最善の考えはsource $script $script、最初の位置パラメータに必要な情報が含まれるようにすることです。誰より良い方法がありますか?

明確にするために、スクリプトを実行しているのではなく、ソースを提供しています。

source foo.bash

4200以上の賛成票がある関連質問:stackoverflow.com/q/59895/52074
Trevor Boyd Smith

回答:


65

スクリプトの先頭にファイルが供給された場合は場所が含まれております、それが実行された場合は、それが含まれています。tcsh$_$0

#!/bin/tcsh
set sourced=($_)
if ("$sourced" != "") then
    echo "sourced $sourced[2]"
endif
if ("$0" != "tcsh") then
    echo "run $0"
endif

Bashの場合:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"

私はこれをtcshで使用する機会がありましたが、シバンなしでは機能しないことに気付きました。...あなたはそれを実行していない、それを調達している場合は変更する動作のために少し奇妙に思える
カスカベル

スクリプトが非対話形式で(cshrcなどから)ソースされている場合、tcshバージョンも機能していないようです。その場合、情報を取得する方法を見つけることができないようです。何かご意見は?
カスカベル

調達は、シバンなしで私のために機能します。> tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec。非インタラクティブにソースを取得する限り、ソースファイルは、元の質問で言及したように、実際にはその一部であるかのように(区別できないほど)親ファイルに含まれます。あなたの位置パラメータの回避策はおそらく最良のアプローチだと思います。しかし、通常の質問は、応答への通常の答えがある「という理由をあなたがしたいん」である「ことをしない-ん、これを代わりに」どこ「これは」...店に頻繁にある
デニスウィリアムソン

2
@clacke:私は見つけること私は、4.1.9を含む、4.2.37に2.05bからそれをテストしたバッシュのすべてのバージョンで.source、この点では同じように働いていました。ファイルの最初のステートメントで$_アクセスする必要があることに注意してください。そうしないと、前のコマンドの最後の引数が含まれます。私は自分のリファレンスにシバンを含めるのが好きなので、構文の強調表示を使用するためにエディター用とシェル用のシェルを知っています。
デニスウィリアムソン

1
ハハ。明らかに最初にしてsourceからしてからテストしていた.。無能であることをおpoびします。それらは確かに同一です。とにかく、$BASH_SOURCE動作します。
クラック

30

$BASH_SOURCE変数を使用できると思います。実行されたパスを返します。

pbm@tauri ~ $ /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ ./a.sh
./a.sh
pbm@tauri ~ $ source /home/pbm/a.sh 
/home/pbm/a.sh
pbm@tauri ~ $ source ./a.sh
./a.sh

そのため、次のステップでは、パスが相対であるかどうかを確認する必要があります。相対的でない場合はすべて大丈夫です。それは我々がパスを確認できている場合はpwd、で連結/して$BASH_SOURCE


2
また、指定された名前にが含まれていない場合にsource検索することに注意してください。検索順序はシェルオプションによって異なります。詳細については、マニュアルを参照してください。$PATH/
ジル

1
だから、のようなものmydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"はうまくいくでしょうか?
ケビンCantu

ありがとう、迅速で役立つ回答。デニスは、tcshの回答も緑色のチェックマークを獲得しています。@Gilles:そうですね、ドキュメントでそれを見つけました。幸いなことに、私のユースケースでは、ほとんど心配する必要はありません。
カスカベル

18

徹底的かつ検索者のために、ここにこれらの機能があります...これはコミュニティwikiなので、他のシェルの同等物を自由に追加してください(明らかに、$ BASH_SOURCEは異なります)。

test.sh:

#! /bin/sh
called=$_
echo $called
echo $_
echo $0
echo $BASH_SOURCE

test2.sh:

#! /bin/sh
source ./test.sh

バッシュ:

$./test2.sh
./test2.sh
./test2.sh
./test2.sh
./test.sh
$ sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh
./test.sh

ダッシュ

$./test2.sh
./test2.sh
./test2.sh
./test2.sh

$/bin/sh ./test2.sh
/bin/sh
/bin/sh
./test2.sh

$

Zsh

$ ./test2.sh
./test.sh
./test.sh
./test.sh

$ zsh test.sh

echo
test.sh

$

1
わかりません:なぜcalled=$_; echo $called; echo $_?これは$_2回印刷されませんか?
Ciro Santilli新疆改造中心法轮功六四事件

5
@CiroSantilli:常にではありませんが、Bash$_特別なパラメーターに関するマニュアルをお読みください:「シェルの起動時に、環境または引数リストで渡されたシェルまたはシェルスクリプトの呼び出しに使用される絶対パス名を設定します。その後、最後に展開します展開後の前のコマンドの引数。また、そのコマンドにエクスポートされた環境で実行および配置される各コマンドの呼び出しに使用されるフルパス名に設定します。メールをチェックするとき、このパラメータはメールファイルの名前を保持します。
アダムローゼンフィールド

これに伴う問題は、ソースファイルにヘッダーがあり、ソース#! /bin/shに役に立たないことです。の新しいインスタンスを開始して/bin/sh変数を設定し、そのインスタンスを終了して、呼び出し元のインスタンスを変更しません。
JamesThomasMoon1979

2
@ JamesThomasMoon1979:あなたは何について話しているのですか?#シェルスクリプトで始まるものはすべてコメントです。  #!(shebang)は、実行されるスクリプトの最初の行としてのみ特別な意味を持ちます。ソースとなる  ファイルの最初の行は、単なるコメントです。
スコット

17

このソリューションは、bashにのみ適用され、tcshには適用されません。${BASH_SOURCE[0]}関数内からパスを見つけようとすると、一般的に提供される答えは機能しないことに注意してください。

ファイルがソースされているか、スクリプトとして実行されているかに関係なく、この行は常に機能することがわかりました。

echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

readlink再帰的または非再帰的に、上記のパスで使用するシンボリックリンクを追跡する場合。

これを試して、他の提案されたソリューションと比較するスクリプトを次に示します。source test1/test2/test_script.shまたはとして呼び出しbash test1/test2/test_script.shます。

#
# Location: test1/test2/test_script.sh
#
echo $0
echo $_
echo ${BASH_SOURCE}
echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}

cur_file="${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}"
cur_dir="$(dirname "${cur_file}")"
source "${cur_dir}/func_def.sh"

function test_within_func_inside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

echo "Testing within function inside"
test_within_func_inside

echo "Testing within function outside"
test_within_func_outside

#
# Location: test1/test2/func_def.sh
#
function test_within_func_outside {
    echo ${BASH_SOURCE}
    echo ${BASH_SOURCE[${#BASH_SOURCE[@]} - 1]}
}

ワンライナーが機能する理由は、BASH_SOURCE環境変数とその関連の使用によって説明されますFUNCNAME

BASH_SOURCE

FUNCNAME配列変数内の対応するシェル関数名が定義されているソースファイル名をメンバーとする配列変数。シェル関数$ {FUNCNAME [$ i]}はファイル$ {BASH_SOURCE [$ i]}で定義され、$ {BASH_SOURCE [$ i + 1]}から呼び出されます。

FUNCNAME

現在実行呼び出しスタックにあるすべてのシェル関数の名前を含む配列変数。インデックス0の要素は、現在実行中のシェル関数の名前です。一番下の要素(最高のインデックスを持つ要素)は "main"です。この変数は、シェル関数が実行されている場合にのみ存在します。FUNCNAMEへの割り当ては効果がなく、エラーステータスを返します。FUNCNAMEが設定されていない場合、後でリセットされても、その特別なプロパティは失われます。

この変数は、BASH_LINENOおよびBASH_SOURCEで使用できます。FUNCNAMEの各要素には、BASH_LINENOおよびBASH_SOURCEに対応する要素があり、コールスタックを記述します。たとえば、$ {FUNCNAME [$ i]}は、行番号$ {BASH_LINENO [$ i]}のファイル$ {BASH_SOURCE [$ i + 1]}から呼び出されました。呼び出し元のビルトインは、この情報を使用して現在の呼び出しスタックを表示します。

[出典:Bashマニュアル]


選択した答えが断続的にしか機能しない一方で、この解決策はbashでうまくいきました。なぜ機能するのか、他の機能では機能しないのか、私は理解できませんでした(おそらく、調達シェルに十分な注意を払っていませんでした)。
Jim2B

13

これは、bash、dash、ksh、およびzshで機能しました。

if test -n "$BASH" ; then script=$BASH_SOURCE
elif test -n "$TMOUT"; then script=${.sh.file}
elif test -n "$ZSH_NAME" ; then script=${(%):-%x}
elif test ${0##*/} = dash; then x=$(lsof -p $$ -Fn0 | tail -1); script=${x#n}
else script=$0
fi

echo $script

これらのシェルの出力:

BASH source: ./myscript
ZSH source: ./myscript
KSH source: /home/pbrannan/git/theme/src/theme/web/myscript
DASH source: /home/pbrannan/git/theme/src/theme/web/myscript
BASH: ./myscript
ZSH: ./myscript
KSH: /home/pbrannan/git/theme/src/theme/web/myscript
DASH: ./myscript

私はそれをcsh / tcshで動作させようとしましたが、難しすぎます。私はPOSIXにこだわっています。


1

私はコミュニティのwikiの回答(Shawn J. Goffから)に少し混乱していたので、物事を整理するスクリプトを書きました。について$_、私はこれを見つけました:コマンドに渡される環境変数としての使用_。これは環境変数なので、その値を誤ってテストするのは簡単です。

以下がスクリプトで、出力です。彼らもこの趣旨です。

test-shell-default-variables.sh

#!/bin/bash

# test-shell-default-variables.sh

# Usage examples (you might want to `sudo apt install zsh ksh`):
#
#  ./test-shell-default-variables.sh dash bash
#  ./test-shell-default-variables.sh dash bash zsh ksh
#  ./test-shell-default-variables.sh dash bash zsh ksh | less -R

# `-R` in `less -R` to have less pass escape sequences directly to the terminal
# so we have colors.


# The "invoking with name `sh`" tests are commented because for every shell I
# tested (dash, bash, zsh and ksh), the output was the same as that of dash.

# The `test_expression` function also work with expansion changes. You can try
# lines like `test_expression '{BASH_SOURCE:-$0}'`.

echolor() {
    echo -e "\e[1;36m$@\e[0m"
}

tell_file() {
    echo File \`"$1"\` is:
    echo \`\`\`
    cat "$1"
    echo \`\`\`
    echo
}

SHELL_ARRAY=("$@")

test_command() {
    for shell in "${SHELL_ARRAY[@]}"
    do
        prepare "$shell"
        cmd="$(eval echo $1)"
        # echo "cmd: $cmd"
        printf '%-4s: ' "$shell"
        { env -i $cmd 2>&1 1>&3 | sed 's/^/[err]/'; } 3>&1
        teardown
    done
    echo
}

prepare () {
    shell="$1"
    PATH="$PWD/$shell/sh:$PATH"
}

teardown() {
    PATH="${PATH#*:}"
}


###
### prepare
###
for shell in "${SHELL_ARRAY[@]}"
do
    mkdir "$shell"
    ln -sT "/bin/$shell" "$shell/sh"
done

echo > printer.sh
echo '. ./printer.sh' > sourcer.sh
rm linked.sh &>/dev/null; ln -sT "printer.sh" "linked.sh"

tell_file sourcer.sh

###
### run
###
test_expression() {
    local expr="$1"

    # prepare
    echo "echo $expr" > printer.sh
    tell_file printer.sh

    # run
    cmd='$shell ./printer.sh'
    echolor "\`$cmd\` (simple invocation) ($expr):"
    test_command "$cmd"

    # cmd='sh ./printer.sh'
    # echolor "\`$cmd\` (when executable name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./sourcer.sh'
    echolor "\`$cmd\` (via sourcing) ($expr):"
    test_command "$cmd"

    # cmd='sh ./sourcer.sh'
    # echolor "\`$cmd\` (via sourcing, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    cmd='$shell ./linked.sh'
    echolor "\`$cmd\` (via symlink) ($expr):"
    test_command "$cmd"

    # cmd='sh ./linked.sh'
    # echolor "\`$cmd\` (via symlink, when name is \`sh\`) ($expr):"
    # test_command "$cmd"

    echolor "------------------------------------------"
    echo
}

test_expression '$BASH_SOURCE'
test_expression '$0'
test_expression '$(/bin/true x y; true a b c; echo $_)' # Rq: true is a builtin
test_expression '$_'

###
### teardown
###
for shell in "${SHELL_ARRAY[@]}"
do
    rm "$shell/sh"
    rm -d "$shell"
done

rm sourcer.sh
rm linked.sh
rm printer.sh

の出力 ./test-shell-default-variables.sh {da,ba,z,k}sh

File `sourcer.sh` is:
```
. ./printer.sh
```

File `printer.sh` is:
```
echo $BASH_SOURCE
```

`$shell ./printer.sh` (simple invocation) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($BASH_SOURCE):
dash: 
bash: ./printer.sh
zsh : 
ksh : 

`$shell ./linked.sh` (via symlink) ($BASH_SOURCE):
dash: 
bash: ./linked.sh
zsh : 
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $0
```

`$shell ./printer.sh` (simple invocation) ($0):
dash: ./printer.sh
bash: ./printer.sh
zsh : ./printer.sh
ksh : ./printer.sh

`$shell ./sourcer.sh` (via sourcing) ($0):
dash: ./sourcer.sh
bash: ./sourcer.sh
zsh : ./printer.sh
ksh : ./sourcer.sh

`$shell ./linked.sh` (via symlink) ($0):
dash: ./linked.sh
bash: ./linked.sh
zsh : ./linked.sh
ksh : ./linked.sh

------------------------------------------

File `printer.sh` is:
```
echo $(/bin/true x y; true a b c; echo $_)
```

`$shell ./printer.sh` (simple invocation) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

`$shell ./linked.sh` (via symlink) ($(/bin/true x y; true a b c; echo $_)):
dash: 
bash: c
zsh : c
ksh : 

------------------------------------------

File `printer.sh` is:
```
echo $_
```

`$shell ./printer.sh` (simple invocation) ($_):
dash: 
bash: bash
zsh : 
ksh : 

`$shell ./sourcer.sh` (via sourcing) ($_):
dash: 
bash: bash
zsh : ./printer.sh
ksh : 

`$shell ./linked.sh` (via symlink) ($_):
dash: 
bash: bash
zsh : 
ksh : 

------------------------------------------

何を学びましたか?

$BASH_SOURCE

  • $BASH_SOURCE bashでのみ動作します。
  • との唯一の違い$0は、現在のファイルが別のファイルから取得されたときです。その場合、$BASH_PROFILEソースファイルの名前ではなく、ソースファイルの名前が含まれます。

$0

  • zshでは、bash $0と同じ値を持ち$BASH_SOURCEます。

$_

  • $_ ダッシュとkshには触れられません。
  • bashおよびzshでは$_、最後の呼び出しの最後の引数に減衰します。
  • bash $_は「bash」に初期化されます。
  • zshは$_そのままです。(ソーシングするときは、「最後の引数」ルールの結果です)。

シンボリックリンク

  • スクリプトがシンボリックリンクを介して呼び出された場合、変数にはリンクのリンク先への参照は含まれず、名前のみが含まれます。

ksh

  • これらのテストに関して、kshはダッシュのように動作します。

sh

  • shこれらのテストに関して、bashまたはzshがという名前のシンボリックリンクを介して呼び出されると、ダッシュのように動作します。

0

bashシェルについては、@ Dennis Williamsonの回答が最も役立ちましたが、の場合は機能しませんでしたsudo。これは:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi

0

スクリプトをifステートメントを使用する代わりにbashとzshの両方に対応させるには、単にを書くことができます${BASH_SOURCE[0]:-${(%):-%x}}。結果の値はBASH_SOURCE[0]、定義されているとき、および${(%):-%x}}BASH_SOURCE [0]が定義されていないときから取得されます。


0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}")(明らかに bashの場合


$BASH_SOURCE テストケース

与えられたファイル /tmp/source1.sh

echo '$BASH_SOURCE '"(${BASH_SOURCE})"
echo 'readlink -e $BASH_SOURCE'\
     "($(readlink -e -- "${BASH_SOURCE}"))"

source さまざまな方法でファイル

source から /tmp

$> cd /tmp

$> source source1.sh
$BASH_SOURCE (source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source ./source1.sh
$BASH_SOURCE (./source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> source /tmp/source1.sh
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source から /

cd /
$> source /tmp/source1.sh
$0 (bash)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

source異なる相対パス/tmp/aから/var

$> cd /tmp/a

$> source ../source1.sh
$BASH_SOURCE (../source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

$> cd /var

$> source ../tmp/source1.sh
$BASH_SOURCE (../tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

に関して $0

すべての場合において、スクリプトにコマンドが追加されている場合

echo '$0 '"(${0})"

その後source、スクリプトは常に印刷されます

$0 (bash)

ただし、スクリプトが実行された場合、たとえば

$> bash /tmp/source1.sh

その後、$0文字列値になります/tmp/source1.sh

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)

0

この回答でlsofは、tcshでネストされたソースファイルを操作できる可能性があると思われる唯一の方法は、grepマジックの一部であると説明しています。

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh

-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

たぶん、これはシンボリックリンクやソースファイルでは動作しませんが、通常のファイルでは動作します。あちこち参照として取られます。@kenorb dirname、readlink、BASH_SOURCEはありません。


1
それは、ソーススクリプトではなく$0、現在実行中のスクリプトに関する情報を取得する質問で説明されました。
スコット

-3

実際、「dirname $ 0」はスクリプトへのパスを取得しますが、少し解釈する必要があります。

$ cat bash0
#!/bin/bash
echo \$0=$0
dirname $0
$ bash0    # "." appears in PATH right now.
$0=./bash0
.
$ ./bash0
$0=./bash0
.
$ $PWD/bash0
$0=/home/00/bediger/src/ksh/bash0
/home/00/bediger/src/ksh
$ $PWD/../ksh/bash0
$0=/home/00/bediger/src/ksh/../ksh/bash0
/home/00/bediger/src/ksh/../ksh
$ ../ksh/bash0
$0=../ksh/bash0
../ksh

「。」を処理する準備をする必要があります。一般的な状況でのディレクトリ名として。"。"の場合にkshの動作を少し変えて組み込みのdirnameを覚えているので、少し実験します。PATHに表示されます。


4
これは、実行されたスクリプトではなく、ソーススクリプトです。$0対話型シェルの「bash」が含まれているだけで、ソーススクリプトからはそれだけが見えます。
カスカベル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.