Ermitteln des Pfads zum Quell-Shell-Skript

80

Gibt es eine Möglichkeit für ein bezogenes Shell-Skript, den Pfad zu sich selbst herauszufinden? Ich beschäftige mich hauptsächlich mit Bash, obwohl ich einige Mitarbeiter habe, die tcsh verwenden.

Ich vermute, dass ich hier nicht viel Glück haben kann, da durch das Sourcing Befehle in der aktuellen Shell ausgeführt werden. Es handelt sich also $0immer noch um den Aufruf der aktuellen Shell, nicht um das Skript, von dem die Quelle stammt. Mein bester Gedanke ist derzeit zu tun source $script $script, damit der erste Positionsparameter die notwendigen Informationen enthält. Hat jemand einen besseren Weg?

Um es klar auszudrücken , ich verwende das Skript und führe es nicht aus:

source foo.bash
Cascabel
quelle
Verwandte Frage, die mehr als 4200 Upvotes hat: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Antworten:

65

In tcsh, $_am Anfang des Skripts wird die Position enthält , wenn die Datei bezogen wurde und $0enthält es , wenn es ausgeführt wurde.

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

In Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Dennis Williamson
quelle
Ich hatte gerade Gelegenheit, dies in tcsh zu verwenden, und bemerkte, dass es ohne den shebang nicht funktioniert. Scheint ein bisschen seltsam für das Verhalten zu ändern, wenn Sie es nur beschaffen, nicht ausführen ...
Cascabel
Die tcsh-Version scheint auch nicht zu funktionieren, wenn das Skript nicht interaktiv bezogen wird (z. B. von einem cshrc). In diesem Fall kann ich anscheinend keinen Weg finden, an die Informationen zu gelangen. Irgendwelche Gedanken?
Cascabel
Sourcing funktioniert bei mir ohne den Schebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. Soweit es nicht interaktiv beschafft wird, wird die Quelldatei in die übergeordnete Datei aufgenommen, als wäre sie tatsächlich ein Teil davon (nicht zu unterscheiden), wie Sie in Ihrer ursprünglichen Frage erwähnt haben. Ich denke, Ihre Problemumgehung für Positionsparameter ist wahrscheinlich der beste Ansatz. Die übliche Frage lautet jedoch "Warum möchten Sie das tun?" Und die übliche Antwort lautet "Tun Sie das nicht - tun Sie dies stattdessen", wo "dies" häufig aufbewahrt wird ...
Dennis Williamson
2
@clacke: Ich finde, dass in allen Versionen von Bash, die ich von 2.05b bis 4.2.37, einschließlich 4.1.9, getestet habe, .und sourcediesbezüglich identisch gearbeitet habe. Beachten Sie, dass $_in der ersten Anweisung in der Datei zugegriffen werden muss , da sonst das letzte Argument des vorherigen Befehls enthalten ist. Ich mag es, den Shebang zu meiner eigenen Referenz hinzuzufügen, damit ich weiß, für welche Shell er gedacht ist und für den Editor, damit er Syntax-Hervorhebungen verwendet.
Dennis Williamson
1
Haha. Offensichtlich habe ich getestet, indem ich erst getan habe source, dann getan habe .. Ich entschuldige mich für die Inkompetenz. Sie sind in der Tat identisch. Wie auch immer, $BASH_SOURCEfunktioniert.
Clacke
30

Ich denke, dass Sie $BASH_SOURCEVariable verwenden könnten . Es gibt den Pfad zurück, der ausgeführt wurde:

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

Im nächsten Schritt sollten wir also prüfen, ob der Pfad relativ ist oder nicht. Wenn es nicht relativ ist, ist alles in Ordnung. Wenn ja, können wir den Pfad pwdmit /und überprüfen , verketten $BASH_SOURCE.

pbm
quelle
2
Und beachten Sie, dass sourcegesucht wird, $PATHwenn der angegebene Name kein. Enthält /. Die Suchreihenfolge hängt von den Shell-Optionen ab. Weitere Informationen finden Sie im Handbuch.
Gilles
1
Also mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"würde so etwas funktionieren?
Kevin Cantu
Danke, eine schnelle und hilfreiche Antwort. Dennis gewinnt das grüne Häkchen, weil er auch eine tcsh-Antwort gegeben hat. @ Gilles: Richtig, das habe ich in der Dokumentation gefunden. Zum Glück für meinen Anwendungsfall muss ich mir mit ziemlicher Sicherheit keine Sorgen machen.
Cascabel
18

Aus Gründen der Gründlichkeit und zum Wohle der Suchenden tun diese Folgendes: Es handelt sich um ein Community-Wiki. Sie können also auch andere Shell-Entsprechungen hinzufügen ($ BASH_SOURCE ist natürlich anders).

test.sh:

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

test2.sh:

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

Bash:

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

Strich

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

$
Shawn J. Goff
quelle
1
Ich verstehe nicht: warum called=$_; echo $called; echo $_? Wird das nicht $_zweimal gedruckt ?
Ciro Santilli
5
@CiroSantilli: Lesen Sie nicht immer das Bash-Handbuch zu dem $_speziellen Parameter: "Setzen Sie beim Starten der Shell auf den absoluten Pfadnamen, der zum Aufrufen der Shell oder des Shell-Skripts verwendet wird, das in der Umgebungs- oder Argumentliste ausgeführt wird. Erweitert sich anschließend bis zum letzten Argument auf den vorherigen Befehl nach der Erweiterung. Wird auch auf den vollständigen Pfadnamen gesetzt, der zum Aufrufen jedes ausgeführten Befehls verwendet wird und in die für diesen Befehl exportierte Umgebung gestellt wird.
Adam Rosenfield
Das Problem dabei ist, dass die #! /bin/shQuelldatei einen Header hat, der die Quelle unbrauchbar macht. Das würde eine neue Instanz von starten /bin/sh, Variablen setzen und diese Instanz dann verlassen, wobei die aufrufende Instanz unverändert bleibt.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: Worüber sprichst du? Alles, was mit #in einem Shell-Skript beginnt, ist ein Kommentar.  #!(shebang) hat seine besondere Bedeutung nur als erste Zeile eines ausgeführten Skripts .   Als erste Zeile einer Datei, die als Quelle dient, handelt es sich nur um einen Kommentar.
Scott
17

Diese Lösung gilt nur für bash und nicht für tcsh. Beachten Sie, dass die häufig gelieferte Antwort ${BASH_SOURCE[0]}nicht funktioniert, wenn Sie versuchen, den Pfad innerhalb einer Funktion zu finden.

Ich habe festgestellt, dass diese Zeile immer funktioniert, unabhängig davon, ob die Datei als Quelle oder als Skript ausgeführt wird.

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

Wenn Sie Symlinks folgen möchten, verwenden Sie readlinkauf dem Pfad, den Sie oben erhalten, rekursiv oder nicht rekursiv.

Hier ist ein Skript, mit dem Sie es ausprobieren und mit anderen vorgeschlagenen Lösungen vergleichen können. Rufen Sie es als source test1/test2/test_script.shoder auf 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]}
}

Der Grund, warum der Einzeiler funktioniert, wird durch die Verwendung der BASH_SOURCEUmgebungsvariablen und ihrer assoziierten Elemente erklärt FUNCNAME.

BASH_SOURCE

Eine Array-Variable, deren Mitglieder die Quelldateinamen sind, in denen die entsprechenden Shell-Funktionsnamen in der Array-Variablen FUNCNAME definiert sind. Die Shell-Funktion $ {FUNCNAME [$ i]} ist in der Datei $ {BASH_SOURCE [$ i]} definiert und wird von $ {BASH_SOURCE [$ i + 1]} aufgerufen.

FUNCNAME

Eine Array-Variable, die die Namen aller Shell-Funktionen enthält, die sich derzeit im Ausführungsaufrufstapel befinden. Das Element mit dem Index 0 ist der Name einer aktuell ausgeführten Shell-Funktion. Das unterste Element (das mit dem höchsten Index) ist "main". Diese Variable existiert nur, wenn eine Shell-Funktion ausgeführt wird. Zuweisungen zu FUNCNAME haben keine Auswirkung und geben einen Fehlerstatus zurück. Wenn FUNCNAME nicht gesetzt ist, verliert es seine besonderen Eigenschaften, auch wenn es später zurückgesetzt wird.

Diese Variable kann mit BASH_LINENO und BASH_SOURCE verwendet werden. Jedes Element von FUNCNAME verfügt über entsprechende Elemente in BASH_LINENO und BASH_SOURCE, um den Aufrufstapel zu beschreiben. Beispielsweise wurde $ {FUNCNAME [$ i]} aus der Datei $ {BASH_SOURCE [$ i + 1]} mit der Zeilennummer $ {BASH_LINENO [$ i]} aufgerufen. Der eingebaute Anrufer zeigt anhand dieser Informationen die aktuelle Anrufliste an.

[Quelle: Bash-Handbuch]

gkb0986
quelle
Diese Lösung hat bei mir in Bash funktioniert, während die ausgewählte Antwort nur zeitweise funktioniert hat. Ich habe nie herausgefunden, warum es manchmal funktioniert hat und nicht andere (vielleicht habe ich der Sourcing-Shell nicht genügend Aufmerksamkeit geschenkt).
Jim2B,
13

Dies funktionierte für mich in bash, dash, ksh und 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

Ausgabe für diese Shells:

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

Ich habe versucht, es für csh / tcsh zum Laufen zu bringen, aber es ist zu schwer. Ich halte mich an POSIX.

Paul Brannan
quelle
1

Die Community-Wiki-Antwort (von Shawn J. Goff) hat mich ein wenig verwirrt, deshalb habe ich ein Skript geschrieben, um die Dinge zu regeln. Über $_, fand ich dies: Verwendung _als Umgebungsvariable an einen Befehl übergeben . Da es sich um eine Umgebungsvariable handelt, ist es einfach, ihren Wert falsch zu testen.

Unten ist das Skript, dann wird es ausgegeben. Sie sind auch in diesem Kern .

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

Ausgabe von ./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 : 

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

Was haben wir gelernt?

$BASH_SOURCE

  • $BASH_SOURCE funktioniert in bash und nur in bash.
  • Der einzige Unterschied $0besteht darin, wann die aktuelle Datei von einer anderen Datei bezogen wurde. $BASH_PROFILEEnthält in diesem Fall den Namen der Quelldatei und nicht den Namen der Quelldatei.

$0

  • Hat in zsh $0den gleichen Wert wie $BASH_SOURCEin bash.

$_

  • $_ wird von dash und ksh unberührt gelassen.
  • Zerfällt in bash und zsh $_zum letzten Argument des letzten Aufrufs.
  • Bash wird $_auf "Bash" initialisiert .
  • zsh bleibt $_unberührt. (bei der Beschaffung ist es nur das Ergebnis der Regel "letztes Argument").

Symlinks

  • Wenn ein Skript über einen Symlink aufgerufen wird, enthält keine Variable einen Verweis auf das Ziel des Links, sondern nur seinen Namen.

ksh

  • In Bezug auf diese Tests verhält sich ksh wie ein Bindestrich.

Sch

  • Wenn bash oder zsh über einen Symlink namens aufgerufen shwird, verhält es sich in Bezug auf diese Tests wie ein Bindestrich.
Mathieu CAROFF
quelle
0

Für die Bash-Shell fand ich die Antwort von @Dennis Williamson am hilfreichsten, aber im Fall von funktionierte sie nicht sudo. Das macht:

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

Um Ihr Skript sowohl bash- als auch zsh-kompatibel zu machen, anstatt if-Anweisungen zu verwenden, können Sie einfach schreiben ${BASH_SOURCE[0]:-${(%):-%x}}. Der resultierende Wert wird BASH_SOURCE[0]bei der Definition entnommen und ${(%):-%x}}wenn BASH_SOURCE [0] nicht definiert ist.

dols3m
quelle
0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (für Bash offensichtlich)


$BASH_SOURCE Testfälle

gegebene Datei /tmp/source1.sh

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

source die Datei auf verschiedene Arten

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

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

sourceaus verschiedenen relativen Pfaden /tmp/aund/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)

hinsichtlich $0

in allen Fällen, wenn das Skript den hinzugefügten Befehl hatte

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

dann wird sourcedas Skript immer gedruckt

$0 (bash)

jedoch , wenn das Skript wurde ausgeführt , zB

$> bash /tmp/source1.sh

dann $0wäre string value /tmp/source1.sh.

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

Diese Antwort beschreibt, wie lsofund ein bisschen Grep-Magie die einzige Möglichkeit zu sein scheint, mit verschachtelten Quelldateien unter tcsh zu arbeiten:

/usr/sbin/lsof +p $$ | grep -oE /.\*source_me.tcsh
Patrick Maupin
quelle
-2
wdir="$PWD"; [ "$PWD" = "/" ] && wdir=""
case "$0" in
  /*) scriptdir="${0%/*}";;
  *) scriptdir="$wdir/${0#./}"; scriptdir="${scriptdir%/*}";;
esac
echo "$scriptdir"

Möglicherweise funktioniert dies nicht mit Symlinks oder Quelldateien, sondern für normale Dateien. Als Referenz hergenommen. @kenorb Kein Dirname, Readlink, BASH_SOURCE.

HemanthJabalpuri
quelle
1
Es wurde in der Frage erläutert, mit der $0Sie Informationen zum aktuell ausgeführten Skript erhalten, nicht zu einem Skript mit Quellenangabe.
Scott,
-3

"Dirname $ 0" gibt Ihnen den Pfad zum Skript, aber Sie müssen ihn ein wenig interpretieren:

$ 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

Sie müssen sich darauf vorbereiten, mit "." als Verzeichnisname unter bestimmten Umständen. Ich würde ein bisschen experimentieren, da ich mich daran erinnere, dass der in ksh integrierte Dirname die Dinge ein bisschen anders macht, wenn "." erscheint in PATH.

Bruce Ediger
quelle
4
Dies ist ein Quellenskript, kein ausgeführtes Skript. $0Enthält einfach "bash" für eine interaktive Shell und das ist alles, was das Skript sieht.
Cascabel