kaynaklı kabuk betiğine giden yolu belirleme


80

Bir yönelik bir yolu var mı kaynaklı kabuk komut kendisine yol bulmak için? Genelde bash ile ilgileniyorum, ancak tcsh kullanan iş arkadaşlarım var.

Sanırım kaynak $0mevcut komut dosyasında yürütülecek komutlara neden olduğu için burada bir ton şansım olmayabilir. En iyi düşüncem şu anda yapmaktır source $script $script, böylece ilk konumsal parametre gerekli bilgileri içerir. Herkes daha iyi bir yol var mı?

Açık olmak gerekirse, ben am kaynak çalışmıyorken, senaryoyu:

source foo.bash

4200+ olumlu oylama ile ilgili bir soru: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Yanıtlar:


65

Içinde tcsh, $_betiğin başında dosya kaynaklanmışsa konumu içerecek ve çalıştırıldıysa da içerecektir $0.

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

Bash'te:

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

Bunu sadece tcsh içinde kullanma fırsatım oldu ve shebang olmadan işe yaramadığını fark ettim. Sadece kaynak kullanıyorsanız davranışını değiştirmesi biraz garip görünüyor, çalıştırmıyorsunuz ...
Cascabel

Komut dosyası etkileşimli olmayan bir şekilde kaynaklanırsa (ör. Bir cshrc'den) tcsh sürümü de çalışmaz. Bu durumda bilgiyi edinmenin bir yolunu bulamıyorum. Düşüncesi olan var mı?
Cascabel

Kaynak bulma, benim için shebang olmadan işe yarıyor. > tcsh --version\n tcsh 6.14.00 (Astron) 2005-03-25 (i486-intel-linux) options wide,nls,dl,al,kan,rh,nd,color,filec. Etkileşimli olarak etkileşime girmeden kaynak dosya, asıl sorunuzda bahsettiğiniz gibi aslında bir parçasıymış gibi ana dosyaya eklenir (ayırt edilemez şekilde). Konumsal parametre çözümünüzün muhtemelen en iyi yaklaşım olduğunu düşünüyorum. Ancak, her zamanki soru ve yanıta her zamanki cevabı "Neden bunu yapmak istiyorsun" "olduğunu yapmayın - do Bu yerine" nerede "bu" ... mağazaya genellikle
Dennis Williamson

2
@clacke: Bunu bulmak ben, 4.1.9 dahil 4.2.37 için 2.05b o test Bash tüm sürümlerinde .ve sourcebu konuda aynı çalıştı. Not $_erişilebilir olmalıdır ilk dosyada açıklamada, aksi takdirde bir önceki komuta son argüman içerecektir. Shebang'ı kendi referansım için eklemeyi seviyorum, bu yüzden hangi kabuk için olması gerektiğini ve editör için sözdizimi vurgulamasını kullandığını biliyorum.
Dennis Williamson,

1
Haha. Belli ki önce yaparak source, sonra yaparak test ediyordum .. Beceriksiz olduğum için özür dilerim. Gerçekten de özdeştirler. Neyse, $BASH_SOURCEçalışıyor.
clacke

30

$BASH_SOURCEDeğişken kullanabileceğini düşünüyorum . Yürütülen yolu döndürür:

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

Bu yüzden bir sonraki adımda yolun göreli olup olmadığını kontrol etmeliyiz. Göreceli değilse her şey yolunda. Eğer öyleyse yolu kontrol edebiliriz pwd, /ve ile birleştiririz $BASH_SOURCE.


2
Ve verilen adın içermediğini sourcearar . Arama sırası kabuk seçeneklerine bağlıdır, ayrıntılar için kılavuza bakın. $PATH/
Gilles

1
Yani, bir şey mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"işe yarayacak?
Kevin Cantu,

Teşekkürler, hızlı ve faydalı bir cevap. Dennis de tcsh cevabı verdiği için yeşil onay işaretini kazandı. @Gilles: Doğru, bunu belgelerde buldum. Neyse ki benim kullanım durumum için kesinlikle endişelenmenize gerek yok.
Cascabel

17

Bu çözüm sadece bash için geçerlidir ve tcsh için geçerli değildir. ${BASH_SOURCE[0]}Bir fonksiyonun içinden yolu bulmaya çalışırsanız, verilen cevapların işe yaramayacağını unutmayın.

Dosyanın kaynaklanıp kaynaklanmadığından veya bir komut dosyası olarak çalıştırılmasından bağımsız olarak bu satırı her zaman çalışacak şekilde buldum.

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

İzleri takip etmek istiyorsanız, readlinkyukarıda aldığınız yolda, özyinelemeli veya özyinelemesiz kullanın.

İşte denemek ve önerilen diğer çözümlerle karşılaştırmak için bir komut dosyası. source test1/test2/test_script.shVeya olarak çağır 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]}
}

Tek astarın çalışmasının nedeni, BASH_SOURCEortam değişkeni ve ortak kullanımıyla açıklanmaktadır FUNCNAME.

BASH_SOURCE

Üyeleri FUNCNAME dizi değişkenindeki karşılık gelen kabuk işlevi adlarının tanımlandığı kaynak dosya adları olan bir dizi değişkeni. $ {FUNCNAME [$ i]} kabuk işlevi $ {BASH_SOURCE [$ i]} dosyasında tanımlandı ve $ {BASH_SOURCE [$ i + 1]} 'den çağrıldı.

FUNCNAME

Halen yürütme çağrısı yığındaki tüm kabuk işlevlerinin adlarını içeren bir dizi değişkeni. 0 dizinli öğe, şu anda yürütülen kabuk işlevinin adıdır. En alttaki eleman (en yüksek dizine sahip olan) "ana" dır. Bu değişken yalnızca bir kabuk işlevi yürütülürken mevcuttur. FUNCNAME uygulamasına yapılan atamaların hiçbir etkisi olmaz ve bir hata durumu döndürür. FUNCNAME ayarlanmamışsa, daha sonra sıfırlansa bile özel özelliklerini kaybeder.

Bu değişken BASH_LINENO ve BASH_SOURCE ile kullanılabilir. FUNCNAME öğelerinin her elemanı, çağrı yığınını tanımlamak için BASH_LINENO ve BASH_SOURCE içindeki karşılık gelen öğelere sahiptir. Örneğin, $ {FUNCNAME [$ i]}, $ {BASH_SOURCE [$ i + 1]} dosyasından $ {BASH_LINENO [$ i]} satır numarasından çağrıldı. Arayan yerleşik bu bilgiyi kullanarak mevcut çağrı yığınını görüntüler.

[Kaynak: bash kılavuzu]


Seçilen çözüm sadece aralıklı olarak çalışırken bu çözüm benim için bash'te işe yaradı. Neden bazen işe yaradığını ve başkalarının neden işe yaramadığını asla anlamadım (belki de kaynak kabuğuna yeterince dikkat etmiyordum).
Jim2B

17

Tamlık ve arayıcı uğruna, işte bunlar ne yapar ... Bu bir topluluk wiki, bu nedenle diğer kabuğun eşdeğerlerini eklemekten çekinmeyin (açıkçası, $ BASH_SOURCE farklı olacaktır).

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

tire

$./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
Anlamıyorum: neden called=$_; echo $called; echo $_? Bu $_iki kez basılmaz mı?
Ciro Santilli,

5
@CiroSantilli: Her zaman değil , özel parametrenin Bash el kitabını okuyun $_: "Kabuk başlangıcında, ortamda veya bağımsız değişken listesinde geçirildiği şekilde yürütülen kabuk veya kabuk komut dosyasını çağırmak için kullanılan mutlak yol adını ayarlayın. Sonradan sonuncuya genişler. Bir önceki komutun argümanı, genişlemeden sonra. Ayrıca, komutun dışına yerleştirilen ve çalıştırılan her komutu çağırmak için kullanılan tam yol adına ayarlayın. Posta kontrol edilirken, bu parametre posta dosyasının adını tutar. "
Adam Rosenfield

#! /bin/shBununla ilgili sorun, kaynak kodlu dosyanın, kaynak kullanılmasını gereksiz kılan başlığa sahip olmasıdır. Bu, yeni bir /bin/shdeğişken örneği başlatır , değişkenleri ayarlar, sonra bu örnekten çıkar ve çağıran örneği değiştirmez.
JamesThomasMoon1979

2
@ JamesThomasMoon1979: Ne hakkında konuşuyorsun? #Bir kabuk betiğinde başlayan herhangi bir şey yorumdur.  #!(shebang) özel bir anlamı ancak çalıştırılan bir komut dosyasının ilk satırı olarak . Kaynaklanan   dosyanın ilk satırı olarak , sadece bir yorum.
Scott

13

Bu benim için bash, dash, ksh ve zsh'da çalıştı:

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

Bu kabukları için çıktı:

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 için çalışmasını sağlamaya çalıştım, ama çok zor; POSIX'e yapıyorum.


1

Topluluk wiki yanıtıyla (Shawn J. Goff'tan) biraz kafam karıştı, bu yüzden işleri düzeltmek için bir senaryo yazdım. Hakkında $_: bunu buldum Kullanımları _bir ortam değişkeni bir komuta geçerken . Bu bir ortam değişkenidir ve değerini yanlış test etmek kolaydır.

Aşağıda script var, sonra çıktı. Onlar da bu oyundalar .

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

Çıktısı ./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 : 

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

Ne öğrendik?

$BASH_SOURCE

  • $BASH_SOURCE bash ve sadece bash olarak çalışır.
  • Aradaki fark $0, geçerli dosyanın başka bir dosya tarafından alındığı durumdur. Bu durumda, $BASH_PROFILEkaynak dosyanın adını değil kaynak dosya adını içerir.

$0

  • Zsh'de bash ile $0aynı değere sahiptir $BASH_SOURCE.

$_

  • $_ çizgi ve ksh ile dokunulmaz.
  • Bash ve zsh'da $_son çağrının son argümanına çürür .
  • bash $_"bash" olarak başlatılır .
  • Zsh $_el değmemiş bırakır . (kaynak yaparken, sadece "son tartışma" kuralının sonucudur).

symlinks

  • Bir script bir link link üzerinden çağrıldığında, hiçbir değişken linkin varış yerine herhangi bir referans içermez, sadece ismi içerir.

ksh

  • Bu testler ile ilgili olarak, ksh çizgi gibi davranır.

sh

  • Bash veya zsh, shbu testlerle ilgili olarak adlandırılan bir sembolik bağlantı aracılığıyla çağrıldığında , çizgi gibi davranır.


0

Komut dosyanızı hem bash hem de zsh ile uyumlu hale getirmek için if ifadelerini kullanmak yerine sadece yazabilirsiniz ${BASH_SOURCE[0]:-${(%):-%x}}. Sonuç değeri BASH_SOURCE[0]tanımlandığında ve ${(%):-%x}}BASH_SOURCE [0] tanımlanmadığında alınacaktır.


0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (açıkça bash için)


$BASH_SOURCE test durumları

verilen dosya /tmp/source1.sh

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

source farklı şekillerde dosya

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

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

sourcefarklı akraba yollarından /tmp/ave/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)

ilişkin $0

her durumda, komut dosyası eklenen komutu içeriyorsa

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

sonra sourcekomut her zaman yazdırılır

$0 (bash)

ancak , komut dosyası çalıştırıldıysa , örneğin

$> bash /tmp/source1.sh

o $0zaman dize değeri olurdu /tmp/source1.sh.

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

0

Bu cevaplsof tcsh altında iç içe kaynaklı dosyalar için çalışma şansına sahip gibi görünen tek şeyin nasıl ve bir miktar iz bırakma sihirini açıklar :

/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"

Belki bu, sembolik bağlantılar veya kaynak kodlu dosyalar ile çalışmaz fakat normal dosyalar için çalışır. Sağa sola referans olarak alınmıştır. @ kenorb Dizin yok, readlink, BASH_SOURCE.


1
Söz konusu açıklanmıştır $0şu anda hakkında bilgi alır çalışan komut dosyası değil, bir kaynaklı bir.
Scott

-3

Aslında, "dirname $ 0", betiğin yolunu bulacaktır, ancak biraz yorumlamanız gerekir:

$ 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

"" İle başa çıkmak için hazırlanmalısınız. Bazı yaygın koşullar altında dizin adı olarak Biraz denemeliyim, çünkü "." PATH'te görünür.


4
Bu kaynak kodlu bir komut dosyasıdır, yürütülen bir komut dosyası değildir. $0sadece etkileşimli bir kabuk için "bash" içerir ve kaynak kodun tamamı budur.
Cascabel
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.