menentukan jalur ke skrip shell bersumber

80

Apakah ada cara untuk skrip shell bersumber untuk menemukan jalan menuju dirinya sendiri? Saya terutama khawatir dengan bash, meskipun saya memiliki beberapa rekan kerja yang menggunakan tcsh.

Saya kira saya mungkin tidak memiliki banyak keberuntungan di sini, karena sumber menyebabkan perintah dieksekusi di shell saat ini, jadi $0masih permintaan shell saat ini, bukan skrip yang bersumber. Pemikiran terbaik saya saat ini adalah melakukan source $script $script, sehingga parameter posisi pertama berisi informasi yang diperlukan. Adakah yang punya cara yang lebih baik?

Untuk lebih jelasnya, saya sumber skrip, tidak menjalankannya:

source foo.bash
Cascabel
sumber
pertanyaan terkait yang memiliki 4200+ upvotes: stackoverflow.com/q/59895/52074
Trevor Boyd Smith

Jawaban:

65

Di tcsh, $_di awal skrip akan berisi lokasi jika file tersebut bersumber dan $0berisi jika dijalankan.

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

Di Bash:

#!/bin/bash
[[ $0 != $BASH_SOURCE ]] && echo "Script is being sourced" || echo "Script is being run"
Dennis Williamson
sumber
Saya hanya punya kesempatan untuk menggunakan ini di tcsh, dan menyadari bahwa itu tidak berfungsi tanpa shebang. Tampaknya sedikit aneh untuk perilaku berubah jika Anda hanya mengambilnya, tidak menjalankannya ...
Cascabel
Versi tcsh juga tampaknya tidak berfungsi jika skrip bersumber noninteraktif (misalnya dari cshrc). Sepertinya saya tidak dapat menemukan cara untuk mendapatkan informasi dalam kasus itu. Adakah pikiran?
Cascabel
Sumber itu berfungsi untuk saya tanpa 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. Sejauh sumber itu non-interaktif, file sumber dimasukkan ke dalam file induk seolah-olah itu benar-benar bagian dari itu (tidak bisa dibedakan) seperti yang Anda sebutkan dalam pertanyaan awal Anda. Saya pikir solusi parameter posisi Anda mungkin merupakan pendekatan terbaik. Namun, pertanyaan yang biasa adalah "mengapa Anda ingin melakukan itu" dan jawaban yang biasa untuk jawabannya adalah "jangan lakukan itu - lakukan ini sebagai gantinya" di mana "ini" sering disimpan ...
Dennis Williamson
2
@clacke: Saya menemukan bahwa di semua versi Bash yang saya uji dari 2.05b ke 4.2.37, termasuk 4.1.9, .dan sourcebekerja secara identik dalam hal ini. Catatan yang $_harus diakses dalam pernyataan pertama dalam file, jika tidak maka akan berisi argumen terakhir dari perintah sebelumnya. Saya suka memasukkan shebang untuk referensi saya sendiri jadi saya tahu shell apa yang seharusnya dan untuk editor sehingga menggunakan highlight sintaks.
Dennis Williamson
1
Ha ha. Jelas saya menguji dengan melakukan dulu source, lalu melakukan .. Saya minta maaf karena tidak kompeten. Mereka memang identik. Bagaimanapun, $BASH_SOURCEbekerja.
clacke
30

Saya pikir Anda bisa menggunakan $BASH_SOURCEvariabel. Ini mengembalikan jalur yang dieksekusi:

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

Jadi pada langkah selanjutnya kita harus memeriksa apakah path relatif atau tidak. Jika tidak relatif semuanya baik-baik saja. Jika itu kita bisa memeriksa path dengan pwd, menyatukan dengan /dan $BASH_SOURCE.

pbm
sumber
2
Dan perhatikan bahwa sourcemencari $PATHjika nama yang diberikan tidak mengandung a /. Urutan pencarian tergantung pada opsi shell, lihat manual untuk detailnya.
Gilles
1
Jadi, sesuatu seperti mydir="$(cd "$(dirname "$BASH_SOURCE")"; pwd)"akan berhasil?
Kevin Cantu
Terima kasih, jawaban yang cepat dan bermanfaat. Dennis memenangkan tanda centang hijau karena memberikan jawaban tcsh juga. @Gilles: Benar, saya menemukan itu di dokumentasi. Untungnya untuk kasus penggunaan saya, saya hampir pasti tidak perlu khawatir tentang hal itu.
Cascabel
18

Demi ketelitian dan demi pencarian, inilah yang dilakukan ... Ini adalah wiki komunitas, jadi jangan ragu untuk menambahkan persamaan shell lainnya (jelas, $ BASH_SOURCE akan berbeda).

test.sh:

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

test2.sh:

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

Pesta:

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

Berlari

$./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
sumber
1
Saya tidak mengerti: mengapa called=$_; echo $called; echo $_? Tidakkah ini dicetak $_dua kali?
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
5
@CiroSantilli: Tidak selalu, baca manual Bash pada $_parameter khusus: "Pada shell startup, setel ke pathname absolut yang digunakan untuk memanggil shell atau skrip shell yang dieksekusi sebagaimana diteruskan dalam lingkungan atau daftar argumen. Selanjutnya, perluas ke yang terakhir argumen ke perintah sebelumnya, setelah ekspansi. Juga setel ke nama path lengkap yang digunakan untuk memanggil setiap perintah yang dieksekusi dan ditempatkan di lingkungan yang diekspor ke perintah itu. Saat memeriksa email, parameter ini menyimpan nama file email. "
Adam Rosenfield
Masalah dengan ini adalah file yang bersumber memiliki header #! /bin/shyang membuatnya tidak berguna untuk sumber. Itu akan memulai instance baru /bin/sh, mengatur variabel, lalu keluar dari instance itu, membiarkan instance panggilan tidak berubah.
JamesThomasMoon1979
2
@ JamesThomasMoon1979: Apa yang kamu bicarakan? Apa pun yang dimulai dengan #skrip shell adalah komentar.  #!(Shebang) hanya memiliki arti khusus sebagai baris pertama dari skrip yang dieksekusi.   Sebagai baris pertama dari file yang bersumber, itu hanya komentar.
Scott
17

Solusi ini hanya berlaku untuk bash dan bukan tcsh. Perhatikan bahwa jawaban yang umum diberikan ${BASH_SOURCE[0]}tidak akan berfungsi jika Anda mencoba menemukan jalur dari dalam suatu fungsi.

Saya menemukan baris ini selalu berfungsi, terlepas dari apakah file tersebut bersumber atau dijalankan sebagai skrip.

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

Jika Anda ingin mengikuti symlink, gunakan readlinkjalur yang Anda dapatkan di atas, secara rekursif atau non-rekursif.

Berikut ini skrip untuk mencobanya dan membandingkannya dengan solusi yang diusulkan lainnya. Gunakan sebagai source test1/test2/test_script.shatau 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]}
}

Alasan one-liner berfungsi dijelaskan oleh penggunaan BASH_SOURCEvariabel lingkungan dan asosiasinya FUNCNAME.

BASH_SOURCE

Variabel array yang anggotanya adalah nama file sumber tempat nama fungsi shell yang sesuai dalam variabel array FUNCNAME didefinisikan. Fungsi shell $ {FUNCNAME [$ i]} didefinisikan dalam file $ {BASH_SOURCE [$ i]} dan dipanggil dari $ {BASH_SOURCE [$ i + 1]}.

FUNCNAME

Variabel array yang berisi nama-nama semua fungsi shell saat ini dalam tumpukan panggilan eksekusi. Elemen dengan indeks 0 adalah nama dari setiap fungsi shell yang sedang dijalankan. Elemen terbawah (yang memiliki indeks tertinggi) adalah "utama". Variabel ini hanya ada ketika fungsi shell mengeksekusi. Tugas ke FUNCNAME tidak berpengaruh dan mengembalikan status kesalahan. Jika FUNCNAME tidak disetel, FUNCNAME akan kehilangan properti khususnya, bahkan jika kemudian disetel ulang.

Variabel ini dapat digunakan dengan BASH_LINENO dan BASH_SOURCE. Setiap elemen FUNCNAME memiliki elemen yang sesuai di BASH_LINENO dan BASH_SOURCE untuk menggambarkan tumpukan panggilan. Misalnya, $ {FUNCNAME [$ i]} dipanggil dari file $ {BASH_SOURCE [$ i + 1]} di nomor baris $ {BASH_LINENO [$ i]}. Pemanggil builtin menampilkan tumpukan panggilan saat ini menggunakan informasi ini.

[Sumber: Bash manual]

gkb0986
sumber
Solusi ini bekerja untuk saya di bash sementara jawaban yang dipilih hanya bekerja sesekali. Saya tidak pernah mencari tahu mengapa itu bekerja kadang-kadang dan tidak yang lain (mungkin saya tidak memperhatikan cukup dekat dengan shell sumber).
Jim2B
13

Ini bekerja untuk saya di bash, dash, ksh, dan 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

Output untuk cangkang ini:

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

Saya mencoba membuatnya bekerja untuk csh / tcsh, tetapi terlalu sulit; Saya berpegang teguh pada POSIX.

Paul Brannan
sumber
1

Saya agak bingung dengan jawaban komunitas wiki (dari Shawn J. Goff), jadi saya menulis naskah untuk menyelesaikan masalah. Tentang $_, saya menemukan ini: Penggunaan _sebagai variabel lingkungan dilewatkan ke perintah . Ini adalah variabel lingkungan sehingga mudah untuk menguji nilainya secara salah.

Di bawah ini adalah skrip, lalu hasilnya. Mereka juga ada di inti ini .

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

Output dari ./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 : 

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

Apa yang kami pelajari?

$BASH_SOURCE

  • $BASH_SOURCE bekerja di bash dan hanya di bash.
  • Satu-satunya perbedaan $0adalah ketika file saat ini bersumber dari file lain. Dalam hal ini, $BASH_PROFILEberisi nama file sumber, bukan dari file sumber.

$0

  • Di zsh, $0memiliki nilai yang sama dengan $BASH_SOURCEdi bash.

$_

  • $_ dibiarkan tak tersentuh oleh tanda hubung dan ksh.
  • Dalam bash dan zsh, $_meluruh ke argumen terakhir dari panggilan terakhir.
  • bash menginisialisasi $_ke "bash".
  • zsh $_tidak tersentuh. (Saat sumber, itu hanya hasil dari aturan "argumen terakhir").

Symlinks

  • Ketika skrip dipanggil melalui symlink, tidak ada variabel yang berisi referensi ke tujuan tautan, hanya namanya.

ksh

  • Mengenai tes-tes itu, ksh berperilaku seperti tanda hubung.

SH

  • Ketika bash atau zsh dipanggil melalui symlink bernama sh, mengenai tes-tes itu, itu berperilaku seperti tanda hubung.
Mathieu CAROFF
sumber
0

Untuk bash shell, saya menemukan jawaban @Dennis Williamson sangat membantu, tetapi tidak berhasil dalam kasus sudo. Ini tidak:

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

Untuk membuat skrip Anda kompatibel dengan bash dan zsh alih-alih menggunakan pernyataan if, Anda cukup menulis ${BASH_SOURCE[0]:-${(%):-%x}}. Nilai yang dihasilkan akan diambil dari BASH_SOURCE[0]saat itu ditentukan, dan ${(%):-%x}}ketika BASH_SOURCE [0] tidak ditentukan.

dols3m
sumber
0

tl; dr script=$(readlink -e -- "${BASH_SOURCE}") (untuk bash jelas)


$BASH_SOURCE uji kasus

file yang diberikan /tmp/source1.sh

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

source file dengan cara yang berbeda

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

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

sourcedari jalur relatif berbeda /tmp/adan/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)

mengenai $0

dalam semua kasus, jika skrip memiliki perintah yang ditambahkan

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

maka sourceskrip selalu dicetak

$0 (bash)

namun , jika skrip dijalankan , mis

$> bash /tmp/source1.sh

maka $0akan menjadi nilai string /tmp/source1.sh.

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

jawaban ini menjelaskan bagaimana lsofdan sedikit grep magic adalah satu-satunya hal yang tampaknya memiliki peluang bekerja untuk file bersarang bersarang di bawah tcsh:

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

Mungkin ini tidak akan berfungsi dengan symlink atau file sumber tetapi bekerja untuk file normal. Diambil sebagai referensi mondar-mandir. @kenorb Tanpa dirname, tautan baca, BASH_SOURCE.

Hemanth Jabalpuri
sumber
1
Itu dijelaskan dalam pertanyaan yang $0memberi Anda informasi tentang skrip yang sedang berjalan , bukan yang bersumber.
Scott
-3

Sebenarnya, "dirname $ 0" akan memberi Anda jalan menuju skrip, tetapi Anda harus menafsirkannya sedikit:

$ 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

Anda harus bersiap untuk menangani "." sebagai nama direktori dalam beberapa keadaan umum. Saya akan bereksperimen sedikit, karena saya ingat dirname built-in untuk ksh melakukan sesuatu yang sedikit berbeda ketika "." muncul di PATH.

Bruce Ediger
sumber
4
Ini adalah skrip bersumber, bukan skrip yang dieksekusi. $0cukup berisi "bash" untuk shell interaktif, dan hanya itu yang dilihat skrip bersumber.
Cascabel