web-dev-qa-db-ja.com

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

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

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

明確にするために、私はsourcingスクリプトであり、実行していません。

source foo.bash
86
Cascabel

tcshでは、スクリプトの先頭の$_には、ファイルが読み込まれた場合はその場所が含まれ、実行された場合は$0が含まれます。

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

バッシュで:

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

$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

したがって、次のステップでは、パスが相対パスかどうかを確認する必要があります。相対的でない場合は、すべてがOKです。パスがpwdで確認できる場合は、/および$BASH_SOURCEと連結します。

32
pbm

このソリューションは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の要素は、現在実行中のシェル関数の名前です。一番下の要素(最高のインデックスを持つ要素)は「メイン」です。この変数は、シェル関数の実行中にのみ存在します。 FUNCNAMEへの割り当ては効果がなく、エラーステータスを返します。 FUNCNAMEが設定されていない場合、その後リセットされても、特殊なプロパティは失われます。

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

[出典:バッシュマニュアル]

21
gkb0986

完全性とサーチャーのために、ここでこれらの機能を説明します...これはコミュニティー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

$
18
Shawn J. Goff

これは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に固執しています。

16
Paul Brannan

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

以下はスクリプトで、出力されます。それらは this Gist にもあります。

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では、$0の値はbashの$BASH_SOURCEと同じです。

$_

  • $_は、ダッシュとkshの影響を受けません。
  • Bashとzshでは、$_は最後の呼び出しの最後の引数まで減衰します。
  • bashは$_を「bash」に初期化します。
  • zshは$_をそのままにします。 (ソーシング時は、「最後の引数」ルールの結果のみです)。

シンボリックリンク

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

ksh

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

sh

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

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

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

tl; drscript=$(readlink -e -- "${BASH_SOURCE}")(forbash明らかに)


$BASH_SOURCEテストケース

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

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

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

source from /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 from /

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)

ただし、スクリプトがrunの場合、たとえば.

$> 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

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

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

Ifステートメントを使用する代わりに、スクリプトをbash互換とzsh互換の両方にするには、単に${BASH_SOURCE[0]:-${(%):-%x}}と記述します。結果の値は、定義されている場合は_BASH_SOURCE[0]_から取得され、BASH_SOURCE [0]が定義されていない場合は${(%):-%x}}から取得されます。

0
dols3m

最もトリッキーな部分は、Ubuntuでsh置換として使用されているダッシュシェル用の現在のソースファイルを見つけることです。次のコードスニペットをソースとして使用するスクリプトで使用して、その絶対パスを決定できます。 bash、zsh、およびdashでテストされ、dashおよびshの両方として呼び出されます。

注意:現代に依存 realpath(1) ユーティリティGNU coreutilsパッケージ

注意:lsof(1)オプションも確認する必要があります。これは、Ubuntu 18および19では、このページと他のページの両方から同様のアドバイスが機能しなかったため、これを再発明する必要があったためです。

getShellName() {
    [ -n "$BASH" ] && echo ${BASH##/*/} && return
    [ -n "$ZSH_NAME" ] && echo $ZSH_NAME && return
    echo ${0##/*/}
}

getCurrentScript() {
    local result
    case "$(getShellName)" in
        bash )  result=${BASH_SOURCE[0]}
                ;;
        zsh )   emulate -L zsh
                result=${funcfiletrace[1]%:*}
                ;;
        dash | sh )
                result=$(
                    lsof -p $$ -Fn  \
                    | tail --lines=1  \
                    | xargs --max-args=2  \
                    | cut --delimiter=' ' --fields=2
                )
                result=${result#n}
                ;;
        * )     result=$0
                ;;
    esac
    echo $(realpath $result)
}
0
maoizm