określanie ścieżki do skryptu powłoki

80

Czy istnieje sposób, aby skrypt powłoki pozyskał ścieżkę do siebie? Zajmuje mnie głównie bash, chociaż mam kilku współpracowników, którzy używają tcsh.

Zgaduję, że nie mam tu dużo szczęścia, ponieważ zaopatrzenie powoduje, że polecenia są wykonywane w bieżącej powłoce, podobnie $0jak wywoływanie bieżącej powłoki, a nie skrypt źródłowy. Moja najlepsza myśl obecnie to zrobić source $script $script, aby pierwszy parametr pozycyjny zawierał niezbędne informacje. Czy ktoś ma lepszy sposób?

Dla jasności pozyskuję skrypt, a nie uruchamiam go:

source foo.bash
Cascabel
źródło
powiązane pytanie, które ma ponad 4200 głosów: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Odpowiedzi:

65

W tcsh, $_na początku skryptu będzie zawierać lokalizację, jeśli plik został pobrany, i $0zawiera, jeśli został uruchomiony.

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

W Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Dennis Williamson
źródło
Właśnie miałem okazję użyć tego w tcsh i zauważyłem, że nie działa bez shebang. Wydaje się nieco dziwne zachowanie się zmienić, jeśli jesteś po prostu pozyskiwanie go, nie otwierając go ...
Kaskabel
Wersja tcsh również nie działa, jeśli skrypt jest pozyskiwany nieinteraktywnie (np. Z cshrc). Nie mogę znaleźć sposobu na uzyskanie informacji w tej sprawie. jakieś pomysły?
Cascabel
Pozyskiwanie działa dla mnie bez shebang. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. Jeśli chodzi o pozyskiwanie go w sposób nieinteraktywny, plik źródłowy jest dołączany do pliku nadrzędnego, tak jakby rzeczywiście był jego częścią (tak, jak można to odróżnić), jak wspomniałeś w swoim pierwotnym pytaniu. Myślę, że obejście parametru pozycyjnego jest prawdopodobnie najlepszym podejściem. Jednak typowe pytanie brzmi: „dlaczego chcesz to zrobić”, a odpowiedź zazwyczaj brzmi: „nie rób tego - zrób to zamiast tego ” tam, gdzie „to” jest często przechowywane ...
Dennis Williamson
2
@clacke: Uważam, że we wszystkich wersjach Bash że testowane z 2.05b do 4.2.37, 4.1.9 w tym, że .i sourcepracowały identycznie w tym zakresie. Zauważ, że $_należy uzyskać do niego dostęp w pierwszej instrukcji w pliku, w przeciwnym razie będzie on zawierał ostatni argument poprzedniej komendy. Lubię dołączać shebang dla własnego odniesienia, więc wiem, dla jakiej powłoki powinien być i dla edytora, więc używa podświetlania składni.
Dennis Williamson,
1
Ha ha. Oczywiście najpierw testowałem source, a potem robiłem .. Przepraszam za niekompetencję. Są rzeczywiście identyczne. W każdym razie $BASH_SOURCEdziała.
clacke
30

Myślę, że możesz użyć $BASH_SOURCEzmiennej. Zwraca ścieżkę, która została wykonana:

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

W następnym kroku powinniśmy sprawdzić, czy ścieżka jest względna, czy nie. Jeśli nie jest względny, wszystko jest w porządku. Jeśli tak, możemy sprawdzić ścieżkę za pomocą pwd, połączyć z /i $BASH_SOURCE.

pbm
źródło
2
I zauważ, że sourceszuka, $PATHjeśli podana nazwa nie zawiera /. Kolejność wyszukiwania zależy od opcji powłoki, szczegółowe informacje można znaleźć w instrukcji obsługi.
Gilles
1
Więc coś mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"by działało?
Kevin Cantu,
Dzięki, szybka i pomocna odpowiedź. Dennis wygrywa również zielony znacznik wyboru za udzielenie odpowiedzi tcsh. @Gilles: Tak, znalazłem to w dokumentacji. Na szczęście dla mojego przypadku użycia prawie na pewno nie muszę się o to martwić.
Cascabel,
17

To rozwiązanie dotyczy tylko bash, a nie tcsh. Zauważ, że najczęściej podawana odpowiedź ${BASH_SOURCE[0]}nie zadziała, jeśli spróbujesz znaleźć ścieżkę z funkcji.

Przekonałem się, że ta linia zawsze działa, niezależnie od tego, czy plik jest pobierany czy uruchamiany jako skrypt.

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

Jeśli chcesz podążać za dowiązaniami symbolicznymi, użyj readlinkścieżki powyżej, rekursywnie lub nierekurencyjnie.

Oto skrypt do wypróbowania i porównania z innymi proponowanymi rozwiązaniami. Wywołaj jako source test1/test2/test_script.shlub 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]}
}

Powód, dla którego działa jednolinijka, jest wyjaśniony przez użycie BASH_SOURCEzmiennej środowiskowej i jej powiązania FUNCNAME.

BASH_SOURCE

Zmienna tablicowa, której członkowie są źródłowymi nazwami plików, w których zdefiniowane są odpowiednie nazwy funkcji powłoki w zmiennej tablicowej FUNCNAME. Funkcja powłoki $ {FUNCNAME [$ i]} jest zdefiniowana w pliku $ {BASH_SOURCE [$ i]} i wywołana z $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Zmienna tablicowa zawierająca nazwy wszystkich funkcji powłoki znajdujących się obecnie na stosie wywołań wykonawczych. Element o indeksie 0 jest nazwą każdej aktualnie wykonywanej funkcji powłoki. Najniższy element (ten o najwyższym indeksie) to „main”. Ta zmienna istnieje tylko podczas wykonywania funkcji powłoki. Przypisania do FUNCNAME nie działają i zwracają status błędu. Jeśli FUNCNAME jest rozbrojony, traci swoje specjalne właściwości, nawet jeśli zostanie następnie zresetowany.

Tej zmiennej można używać z BASH_LINENO i BASH_SOURCE. Każdy element FUNCNAME ma odpowiadające elementy w BASH_LINENO i BASH_SOURCE do opisania stosu wywołań. Na przykład $ {FUNCNAME [$ i]} został wywołany z pliku $ {BASH_SOURCE [$ i + 1]} w linii $ {BASH_LINENO [$ i]}. Wbudowane narzędzie wywołujące wyświetla bieżący stos połączeń, korzystając z tych informacji.

[Źródło: instrukcja Bash]

gkb0986
źródło
To rozwiązanie działało dla mnie bash, a wybrana odpowiedź działała tylko sporadycznie. Nigdy nie zrozumiałem, dlaczego czasami działa, a nie inne (może nie zwracałem wystarczającej uwagi na powłokę pozyskiwania).
Jim2B
17

Dla dokładności i ze względu na wyszukiwarki, oto co oni robią ... Jest to wiki społeczności, więc możesz dodać inne odpowiedniki powłoki (oczywiście $ BASH_SOURCE będzie inny).

test.sh:

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

test2.sh:

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

Grzmotnąć:

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

Dziarskość

$./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
źródło
1
Nie rozumiem: dlaczego called=$_; echo $called; echo $_? Czy nie jest to druk $_dwa razy?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
5
@CiroSantilli: Nie zawsze przeczytaj instrukcję Bash na temat $_parametru specjalnego: „Podczas uruchamiania powłoki ustaw bezwzględną nazwę ścieżki używaną do wywoływania powłoki lub skryptu powłoki wykonywanej jako przekazanej w środowisku lub liście argumentów. Następnie rozwija się do ostatniego argument do poprzedniego polecenia, po rozwinięciu. Ustaw także na pełną ścieżkę używaną do wywoływania każdego polecenia wykonanego i umieszczonego w środowisku wyeksportowanym do tego polecenia. Podczas sprawdzania poczty parametr ten przechowuje nazwę pliku poczty. ”
Adam Rosenfield
Problem z tym, że plik #! /bin/shźródłowy ma nagłówek, co czyni go bezużytecznym do źródła. Rozpocznie to nową instancję /bin/sh, ustawi zmienne, a następnie opuści tę instancję, pozostawiając instancję wywołującą niezmienioną.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: O czym ty mówisz? Wszystko, co zaczyna się od #skryptu powłoki, jest komentarzem.  #!(shebang) ma swoje specjalne znaczenie tylko jako pierwszy wiersz wykonywanego skryptu .   Jako pierwszy wiersz pozyskiwanego pliku jest to tylko komentarz.
Scott,
13

Działa to dla mnie w bash, dash, ksh i 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

Dane wyjściowe dla tych powłok:

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

Próbowałem sprawić, by działał dla csh / tcsh, ale jest to zbyt trudne; Pozostaję przy POSIX.

Paul Brannan
źródło
1

Byłem trochę zdezorientowany odpowiedzią wiki społeczności (od Shawna J. Goffa), więc napisałem skrypt, aby to rozwiązać. O $_znalazłem to: Wykorzystanie _jako zmiennej środowiskowej przekazanej do polecenia . Jest to zmienna środowiskowa, więc łatwo jest niepoprawnie przetestować jej wartość.

Poniżej znajduje się skrypt, a następnie jego wynik. Oni również są w tej istocie .

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

Wyjście z ./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 : 

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

Czego się nauczyliśmy?

$BASH_SOURCE

  • $BASH_SOURCE działa w trybie bash i tylko w bash.
  • Jedyna różnica $0polega na tym, że bieżący plik pochodzi z innego pliku. W takim przypadku $BASH_PROFILEzawiera nazwę pliku źródłowego, a nie pliku źródłowego.

$0

  • W zsh $0ma taką samą wartość jak $BASH_SOURCEw bash.

$_

  • $_ pozostaje nietknięty przez myślnik i ksh.
  • W bash i zsh $_rozpada się do ostatniego argumentu ostatniego wywołania.
  • bash inicjuje się $_na „bash”.
  • Zsh pozostawia $_nietknięte. (podczas pozyskiwania jest to wynik reguły „ostatniego argumentu”).

Symlinks

  • Kiedy skrypt jest wywoływany przez dowiązanie symboliczne, żadna zmienna nie zawiera żadnego odniesienia do miejsca docelowego łącza, tylko jego nazwa.

ksh

  • Jeśli chodzi o te testy, ksh zachowuje się jak myślnik.

sh

  • Kiedy bash lub zsh jest wywoływany przez nazwany dowiązanie symboliczne sh, w odniesieniu do tych testów zachowuje się jak myślnik.
Mathieu CAROFF
źródło
0

W przypadku powłoki bash uważam, że odpowiedź @Dennis Williamson jest najbardziej pomocna, ale nie zadziałała w przypadku sudo. To robi:

if ( [[ $_ != $0 ]] && [[ $_ != $SHELL ]] ); then
    echo "I'm being sourced!"
    exit 1
fi
Matt
źródło
0

Aby skrypt był zgodny zarówno z bash, jak i zsh, zamiast używać instrukcji if, wystarczy napisać ${BASH_SOURCE[0]:-${(%):-%x}}. Wynikowa wartość zostanie pobrana, BASH_SOURCE[0]gdy zostanie zdefiniowana, a ${(%):-%x}}gdy BASH_SOURCE [0] nie zostanie zdefiniowany.

dols3m
źródło
0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (oczywiściedla bash )


$BASH_SOURCE przypadki testowe

podany plik /tmp/source1.sh

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

source plik na różne sposoby

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

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

sourcez różnych ścieżek względnych /tmp/ai/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)

Jeżeli chodzi o $0

we wszystkich przypadkach, jeśli skrypt ma dodane polecenie

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

wtedy sourceskrypt jest zawsze drukowany

$0 (bash)

jednak jeśli skrypt został uruchomiony , np

$> bash /tmp/source1.sh

to $0byłaby wartość ciągu /tmp/source1.sh.

$0 (/tmp/source1.sh)
$BASH_SOURCE (/tmp/source1.sh)
readlink -e $BASH_SOURCE (/tmp/source1.sh)
JamesThomasMoon1979
źródło
0

ta odpowiedź opisuje, jak lsofi odrobina magii grep jest jedyną rzeczą, która wydaje się mieć szansę pracy dla zagnieżdżonych plików źródłowych w tcsh:

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

Być może nie będzie to działać z dowiązaniami symbolicznymi lub źródłowymi, ale będzie działać z normalnymi plikami. Odniesiono jako odniesienie. @kenorb Brak nazwy katalogu, readlink, BASH_SOURCE.

Hemanth Dżabalpuri
źródło
1
Zostało to wyjaśnione w pytaniu, $0które zawiera informacje o aktualnie uruchomionym skrypcie, a nie źródłowym.
Scott,
-3

W rzeczywistości „dirname $ 0” poprowadzi cię do skryptu, ale musisz go trochę zinterpretować:

$ 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

Musisz przygotować się do obsługi „”. jako nazwa katalogu w niektórych typowych okolicznościach. Poeksperymentowałem trochę, ponieważ pamiętam nazwę dirname wbudowaną w ksh, która robi coś nieco inaczej, gdy „.” pojawia się w ścieżce.

Bruce Ediger
źródło
4
To jest skrypt źródłowy, a nie skrypt wykonany. $0po prostu zawiera „bash” dla interaktywnej powłoki, i to wszystko, co widzi skrypt.
Cascabel,