Membedakan antara menjalankan dan bersumber dalam skrip bash shell?

22

Entah apa yang saya tanyakan di sini sangat tidak lazim / tidak konvensional / berisiko, atau keterampilan Google-fu saya tidak cukup ...

Dalam bashskrip shell, apakah ada cara mudah untuk mengetahui apakah skrip tersebut diperoleh dari skrip shell lain, atau apakah skrip dijalankan dengan sendirinya? Dengan kata lain, apakah mungkin untuk membedakan antara dua perilaku berikut?

# from another shell script
source myScript.sh

# from command prompt, or another shell script
./myScript.sh

Apa yang saya pikirkan lakukan adalah membuat skrip shell yang berisi bashfungsi yang dapat dibuat tersedia saat bersumber. Ketika skrip ini dijalankan dengan sendirinya, saya ingin skrip ini melakukan operasi tertentu, berdasarkan pada fungsi yang ditentukan juga. Apakah ada semacam variabel lingkungan yang dapat diambil skrip shell ini, misalnya

some_function() {
    # ...
}
if [ -z "$IS_SOURCED" ]; then
    some_function;
fi

Lebih disukai, saya mencari solusi yang tidak memerlukan skrip penelepon untuk mengatur variabel flag.

sunting : Saya tahu perbedaan antara sumber dan dan menjalankan skrip, apa yang saya coba temukan di sini apakah mungkin untuk mengetahui perbedaan dalam skrip yang sedang digunakan (dalam kedua cara).

hjk
sumber
1
kemungkinan duplikat dari skrip berjalan dengan "." dan dengan "sumber"
cuonglm
@cuonglm mengedit pertanyaan saya, saya tahu perbedaan antara keduanya tetapi saya bertanya-tanya apakah saya bisa membuat skrip shell memberitahu perbedaannya juga.
hjk
4
@cuonglm: Bukan duplikat. Dia tidak bertanya tentang .perintah sama sekali, tetapi tentang mendeteksi apakah skrip telah bersumber atau berjalan secara normal (yaitu dalam subkulit).
Jander
Jawaban yang sangat bagus untuk pertanyaan yang sama di stack overflow: stackoverflow.com/a/28776166/96944
Jannie Theunissen

Jawaban:

19

Ya - variabel $ 0 memberikan nama skrip saat dijalankan:

$ cat example.sh
#!/bin/bash
script_name=$( basename ${0#-} ) #- needed if sourced no path
this_script=$( basename ${BASH_SOURCE} )
if [[ ${script_name} = ${this_script} ]] ; then
    echo "running me directly"
else
    echo "sourced from ${script_name}"
fi 

$ cat example2.sh
#!/bin/bash
. ./example.sh

Yang berjalan seperti:

$ ./example.sh
running me directly
$ ./example2.sh
example.sh sourced from example2.sh

Itu tidak memenuhi menjadi sumber dari shell interaktif, tetapi Anda mendapatkan ide ini (saya harap).

Diperbarui untuk menyertakan BASH_SOURCE - terima kasih hjk

Hati gelap
sumber
Cukup dekat. :) Rintangan kecil yang saya perlukan untuk menentukan nama skrip setidaknya, tapi saya akan mengambil jawaban ini jika tidak ada solusi lain yang layak ...
hjk
7

Menggabungkan jawaban @ DarkHeart dengan variabel lingkungan BASH_SOURCEtampaknya melakukan trik:

$ head example*.sh
==> example2.sh <==
#!/bin/bash
. ./example.sh

==> example.sh <==
#!/bin/bash
if [ "$(basename $0)" = "$(basename $BASH_SOURCE)" ]; then
    echo "running directly"
else
    echo "sourced from $0"
fi
$ ./example2.sh
sourced from ./example2.sh
$ ./example.sh
running directly

edit Tampaknya menjadi solusi yang lebih sederhana jika saya hanya menghitung jumlah elemen dalam BASH_SOURCEarray:

if [ ${#BASH_SOURCE[@]} -eq 1 ]; then echo "running directly"; else echo "sourced from $0"; fi
hjk
sumber
1
Sepertinya kami menemukan variabel 'bash_source' secara bersamaan. :)
DarkHeart
@DarkHeart saya telah menambahkan ke jawaban saya penggunaan menghitung ukuran array juga ... terima kasih telah memberi saya petunjuk di jalan ini! : D
hjk
1

Saya baru saja membuat jenis naskah perpustakaan yang sama yang berfungsi seperti BusyBox. Di dalamnya, saya menggunakan fungsi berikut untuk menguji apakah itu bersumber ...

function isSourced () {
  [[ "${FUNCNAME[1]}" == "source" ]]  && return 0
  return 1
}

Array FUNCNAME yang dikelola Bash pada dasarnya adalah tumpukan panggilan fungsi. $FUNCNAME(atau ${FUNCNAME[0]}) adalah nama fungsi yang sedang dijalankan. ${FUNCNAME[1]}adalah nama fungsi yang memanggilnya, dan sebagainya.

Item paling atas adalah nilai khusus untuk skrip itu sendiri. Ini akan berisi ...

  • kata "sumber" jika skrip sedang dibuat
  • kata "main" jika skrip dieksekusi DAN tes sedang dilakukan dari dalam suatu fungsi
  • "" (null) jika skrip dieksekusi DAN tes sedang dilakukan di luar fungsi apa pun, yaitu ... pada level skrip itu sendiri.

Fungsi di atas sebenarnya hanya berfungsi ketika dipanggil pada level skrip (yang saya butuhkan). Akan gagal jika dipanggil dari dalam fungsi lain karena nomor item array akan salah. Untuk membuatnya bekerja di mana saja membutuhkan menemukan bagian atas tumpukan dan menguji nilai itu, yang lebih rumit.

Jika Anda membutuhkannya, Anda bisa mendapatkan nomor item larik "atas tumpukan" dengan ...

  local _top_of_stack=$(( ${#FUNCNAME[@]} - 1 ))

${#FUNCNAME[@]}adalah jumlah item dalam array. Sebagai array berbasis nol, kita kurangi 1 untuk mendapatkan item terakhir #.

Tiga fungsi ini digunakan bersama-sama untuk menghasilkan jejak tumpukan fungsi yang mirip dengan Python dan mereka dapat memberi Anda ide yang lebih baik bagaimana semua ini bekerja ...

function inspFnStack () {
  local T+="  "
  local _at=
  local _text="\n"
  local _top=$(inspFnStackTop)
  local _fn=${FUNCNAME[1]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local i=_top; ((--i))
  #
  _text+="$i item function call stack for $_fn ...\n"
  _text+="| L   BASH_SOURCE{BASH_LINENO called from}.FUNCNAME  \n"
  _text+="| ---------------------------------------------------\n"
  while (( $i > 0 ))
  do
    _text+="| $i ${T}$(inspFnStackItem $i)\n"
    T+="  "
    ((--i))
  done
  #
  printf "$_text\n"
  #
  return 0
}

function inspFnStackItem ()  {
  local _i=$1
  local _fn=${FUNCNAME[$_i]}; [[ $_fn =~ source|main ]]  || _fn+="()"
  local _at="${BASH_LINENO[$_i-1]}"; [[ $_at == 1 ]]  && _at="trap"
  local _item="${BASH_SOURCE[$_i]}{${_at}}.$_fn"
  #
  printf "%s" "$_item"
  return 0
}

function inspFnStackTop () {
  # top stack item is 1 less than length of FUNCNAME array stack
  printf "%d\n" $(( ${#FUNCNAME[@]} - 1 ))
  #
  return 0
}

Perhatikan bahwa FUNCNAME, BASH_SOURCE, dan BASH_LINENO adalah 3 array yang dikelola oleh bash seolah-olah mereka adalah array tiga dimensi.

DocSalvager
sumber
0

Hanya ingin menambahkan bahwa penghitungan array tampaknya tidak dapat diandalkan dan orang mungkin seharusnya tidak berasumsi sourcetelah digunakan karena menggunakan titik ( .) juga sangat umum (dan mendahului sourcekata kunci).

Misalnya, untuk sourced.shskrip yang hanya berisi echo $0:


$ . sourced.sh 
bash
$ source sourced.sh 
bash
$ chmod +x sourced.sh 
$ ./sourced.sh 
./sourced.sh
$ cat ./sourced.sh 
echo $0

Solusi perbandingan yang disarankan berfungsi lebih baik.

ka1l
sumber
0

Salah satu cara yang juga berfungsi saat mencari dari shell interaktif :

if [ $BASH_LINENO -ne 0 ]; then
    some_function;
fi

The BASH_LINENOvariabel juga merupakan array dengan semua baris fungsi menelepon dieksekusi di. Ini akan menjadi nol jika Anda memanggil skrip secara langsung, atau bilangan bulat yang sesuai dengan nomor baris.

Dokumen variabel BASH_ *

Borisu
sumber