Cara mendefinisikan skrip shell yang akan bersumber tidak berjalan

40

Saya mendefinisikan skrip shell yang harus sourcedieksekusi oleh pengguna.

Apakah ada cara konvensional atau cerdas untuk memberi petunjuk kepada pengguna bahwa ini adalah kasusnya, misalnya melalui ekstensi file?

Apakah ada kode shell yang dapat saya tulis di file itu sendiri, yang akan menyebabkannya mengulangi pesan dan berhenti jika dieksekusi alih-alih bersumber, sehingga saya dapat membantu pengguna menghindari kesalahan yang jelas ini?

ganggang
sumber
1
Jadi, jika pengguna menulis skrip shell satu baris x, yang hanya berisi perintah . your-script-to-be-sourced, tidak apa-apa, tetapi jika ia ingin mengeksekusinya bash your-script-to-be-sourcedharus dilarang? Apa gunanya pembatasan ini?
user1934428
8
@ user1934428 Tentu saja. Ini normal untuk skrip yang menghitung sejumlah envvariabel dan membiarkannya sebagai output skrip de facto . Seorang pemula akan terjebak selama berhari-hari dengan teka-teki jika Anda membiarkan mereka mengeksekusi.
kubanczyk

Jawaban:

45

Dengan asumsi bahwa Anda menjalankan bash, letakkan kode berikut di dekat awal skrip yang ingin Anda gunakan tetapi tidak dijalankan:

if [ "${BASH_SOURCE[0]}" -ef "$0" ]
then
    echo "Hey, you should source this script, not execute it!"
    exit 1
fi

Di bawah bash, ${BASH_SOURCE[0]}akan berisi nama file saat ini yang dibaca oleh shell terlepas dari apakah itu bersumber atau dieksekusi.

Sebaliknya, $0adalah nama file saat ini sedang dieksekusi.

-efmenguji apakah kedua file ini adalah file yang sama. Jika ya, kami memperingatkan pengguna dan keluar.

Baik -efatau BASH_SOURCEyang POSIX. Sementara -efdidukung oleh ksh, yash, zsh dan Dash, BASH_SOURCEmembutuhkan bash. Dalamzsh Namun, ${BASH_SOURCE[0]}bisa diganti oleh ${(%):-%N}.

John1024
sumber
2
Cukup echo "Usage: source \"$myfile\""
kubanczyk
6
@kubanczyk sourcetidak portabel. Mempertimbangkan bahwa jawaban ini khusus untuk bash, ini tidak terlalu buruk, tetapi kebiasaan yang baik untuk menggunakan portable.
gronostaj
33

File yang tidak dapat dieksekusi dapat bersumber tetapi tidak dieksekusi, jadi, sebagai garis pertahanan pertama, tidak menetapkan bendera yang dapat dieksekusi harus menjadi petunjuk yang baik ...

Sunting: trik Saya baru saja menemukan: membuat shebang menjadi executable yang bukan shell interpreter, /bin/falsemembuat skrip mengembalikan kesalahan (rc! = 0)

#!/bin/false "This script should be sourced in a shell, not executed directly"
xenoid
sumber
7
Namun, file yang tidak dapat dieksekusi masih dapat dieksekusi melalui mis bash somefile.sh...
twalberg
1
Jika Anda tahu Anda dapat menjalankannya dengan bash(vd perl, python, awk ...), maka Anda telah melihat sumbernya dan melihat komentar yang mengatakan untuk tidak melakukannya :)
xenoid
1
Script Perl umumnya dinamai somefile.pldan Python sebagai somefile.py, jadi tidak, saya mungkin belum membaca komentar (apa itu, bahkan, tetap?) Dan bash somefile.shlebih pendek untuk mengetik daripada chmod +x somefile.sh; ./somefile.sh...
twalberg
Juga beberapa cangkang mirip Bourne, termasuk bash, pertama-tama akan mencoba execvesuatu file, tetapi jika gagal, mereka memeriksa file secara manual dan menafsirkan secara manual #!dan memintanya melalui penerjemah itu: ini adalah warisan dari hari-hari ketika ruang #!itu murni ruang pengguna konvensi, alih-alih ditangani oleh kernel itu sendiri. Saya pikir bash , setidaknya, tidak akan melakukan ini untuk file yang tidak dapat dieksekusi, tapi saya tidak tahu apakah itu portabel untuk mengharapkan perilaku yang waras dari semua shell sehingga pengguna mungkin akan meminta script.
mtraceur
"Jika kamu tahu kamu bisa mengeksekusinya dengan bash" Um, tidak, kadang-kadang pengguna tidak tahu bahwa ada kerang lain selain bash dan itu bash script.shbisa berbahaya.
Sergiy Kolodyazhnyy
10

Ada beberapa metode yang disarankan dalam posting Stack Overflow ini , di antaranya, saya menyukai yang berbasis fungsi yang disarankan oleh Wirawan Purwanto dan mr.spuratic best:

Cara yang paling kuat, seperti yang disarankan oleh Wirawan Purwanto, adalah memeriksa FUNCNAME[1] dalam suatu fungsi :

function mycheck() { declare -p FUNCNAME; }
mycheck

Kemudian:

$ bash sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="main")'
$ . sourcetest.sh
declare -a FUNCNAME='([0]="mycheck" [1]="source")'

Ini setara dengan memeriksa output caller, nilai-nilai maindan sourcemembedakan konteks pemanggil. Menggunakan FUNCNAME[]save yang Anda ambil dan parsing calleroutput. Anda perlu tahu atau menghitung kedalaman panggilan lokal Anda untuk menjadi benar. Kasus-kasus seperti skrip yang bersumber dari dalam fungsi atau skrip lain akan menyebabkan array (tumpukan) menjadi lebih dalam. ( FUNCNAMEadalah variabel array bash khusus, harus memiliki indeks berdekatan yang sesuai dengan tumpukan panggilan, selama tidak pernah unset.)

Jadi, Anda dapat menambahkan ke awal skrip:

function check()
{
    if [[ ${FUNCNAME[-1]} != "source" ]]   # bash 4.2+, use ${FUNCNAME[@]: -1} for older
    then
        printf "Usage: source %s\n" "$0"
        exit 1
    fi
}
check
muru
sumber
7

Anggap saja tidak ada gunanya, daripada merusak, untuk mengeksekusi skrip, Anda dapat menambahkan

return 0 || printf 'Must be sourced, not executed\n' >&2

sampai akhir skrip. returndi luar fungsi memiliki kode keluar non-nol kecuali file tersebut bersumber.

chepner
sumber
3
Perhatikan bahwa ini mengembalikan status keluar 0. Coba ungkapan serupa ini yang saya gunakan sebagai gantinya:return 2>/dev/null; echo "$0: This script must be sourced" 1>&2; exit 1
wjandrea
5

Saat Anda mendapatkan skrip shell, garis shebang diabaikan. Dengan memasukkan shebang yang tidak valid, Anda dapat memberi tahu pengguna bahwa skrip itu dieksekusi dengan keliru:

#!/bin/bash source-this-script
# ...

Pesan kesalahannya adalah ini:

/bin/bash: source-this-script: No such file or directory

Nama argumen (sewenang-wenang) sudah memberikan petunjuk yang kuat, tetapi pesan kesalahan masih belum 100% jelas. Kami dapat memperbaikinya dengan skrip utilitas source-this-scriptyang ditempatkan di suatu tempat di Anda PATH:

#!/bin/sh
echo >&2 "This script must be sourced, not executed${1:+: }${1:-!}"
exit 1

Sekarang, pesan kesalahannya adalah ini:

This script must be sourced, not executed: path/to/script.sh

Perbandingan dengan pendekatan lain

Dibandingkan dengan jawaban lain, pendekatan ini hanya memerlukan perubahan minimal untuk setiap skrip (dan memiliki garis shebang membantu dengan deteksi tipe file di editor dan menentukan dialek skrip shell, sehingga bahkan ada manfaatnya). Kelemahannya adalah pesan kesalahan yang agak tidak jelas, atau penambahan (satu kali) dari skrip shell lain.

Itu tidak mencegah permintaan eksplisit melalui bash path/to/script.sh, (terima kasih @muru!).

Ingo Karkat
sumber
1
Kelemahan lain adalah bahwa ini tidak akan melindungi bash some/script.sh, yang juga akan mengabaikan shebang.
muru
4
Anda dapat membuat pesan lebih jelas dengan membuat shebang #!/bin/echo 'You must source this script!'atau semacamnya.
Chris
1
@ Chris: Benar, tapi kemudian saya akan kehilangan deteksi tipe file (misalnya dalam Vim) dan dokumentasi yang dialek shell ini. Jika Anda tidak peduli dengan ini, saran Anda tentu akan menyingkirkan naskah kedua!
Ingo Karkat