Saya memiliki skrip yang ingin saya jalankan di dua mesin. Kedua mesin ini mendapatkan salinan skrip dari repositori git yang sama. Skrip harus dijalankan dengan penerjemah yang tepat (mis zsh
.).
Sayangnya, keduanya env
dan zsh
tinggal di lokasi yang berbeda di mesin lokal dan jarak jauh:
Mesin jarak jauh
$ which env
/bin/env
$ which zsh
/some/long/path/to/the/right/zsh
Mesin lokal
$ which env
/usr/bin/env
$which zsh
/usr/local/bin/zsh
Bagaimana saya bisa mengatur shebang sehingga menjalankan skrip seperti /path/to/script.sh
biasa menggunakan yang Zsh
tersedia di PATH
?
env
tidak menggunakan keduanya / bin dan / usr / bin? Cobawhich -a env
konfirmasi.Jawaban:
Anda tidak dapat menyelesaikan ini melalui shebang secara langsung, karena shebang sepenuhnya statis. Yang bisa Anda lakukan adalah memiliki beberapa »pengali paling umum« (dari perspektif shell) di shebang dan jalankan kembali skrip Anda dengan shell yang tepat, jika LCM ini tidak zsh. Dengan kata lain: Apakah skrip Anda dieksekusi oleh shell yang ditemukan pada semua sistem, uji untuk
zsh
fitur -hanya dan jika tes ternyata salah, miliki skripexec
denganzsh
, di mana tes akan berhasil dan Anda hanya melanjutkan.Salah satu fitur unik dalam
zsh
, misalnya, adalah keberadaan$ZSH_VERSION
variabel:Dalam kasus sederhana ini, skrip pertama kali dieksekusi oleh
/bin/sh
(semua sistem post-80 seperti Unix memahami#!
dan memiliki/bin/sh
, baik Bourne atau POSIX tetapi sintaks kami kompatibel untuk keduanya). Jika$ZSH_VERSION
ini tidak diatur, scriptexec
's sendiri melaluizsh
. Jika$ZSH_VERSION
diatur (resp. Skrip sudah dijalankan melaluizsh
), tes hanya dilewati. Voa.Ini hanya gagal jika
zsh
tidak ada$PATH
sama sekali.Sunting: Untuk memastikan, Anda hanya
exec
azsh
di tempat biasa, Anda bisa menggunakan sesuatu sepertiIni bisa menyelamatkan Anda dari
exec
sesuatu$PATH
yang tidak sengajazsh
Anda temui.sumber
zsh
di$PATH
bukan yang Anda harapkan.zsh
biner di lokasi standar benar-benar azsh
.zsh
mana ia beradazsh -c 'whence zsh'
. Lebih sederhana, Anda bisa sajacommand -v zsh
. Lihat jawaban saya untuk cara path secara dinamis#!bang
.zsh
biner dari$PATH
untuk mendapatkan jalurzsh
biner tidak akan cukup mengatasi masalah yang ditunjukkan @RyanReich, bukan? :)zsh
sendiri, tidak, saya kira tidak. Tetapi jika Anda menanamkan string yang dihasilkan dalam hash bang Anda dan kemudian mengeksekusi skrip Anda sendiri, setidaknya Anda tahu apa yang Anda dapatkan. Namun, itu akan membuat untuk tes yang lebih sederhana daripada untuk mengulanginya.Selama bertahun-tahun saya telah menggunakan sesuatu yang mirip untuk menangani berbagai lokasi Bash pada sistem yang saya butuhkan untuk menjalankan skrip saya.
Bash / Zsh / dll.
Di atas dapat dengan mudah disesuaikan untuk berbagai penerjemah. Kuncinya adalah bahwa skrip ini awalnya berjalan sebagai Bourne shell. Itu kemudian secara rekursif menyebut dirinya untuk kedua kalinya, tetapi mem-parsing semuanya di atas
### BEGIN
menggunakan komentarsed
.Perl
Berikut trik serupa untuk Perl:
Metode ini memanfaatkan kemampuan Perl ketika diberi file untuk dijalankan akan menguraikan file yang dilewati semua baris yang ada sebelum baris
#! perl
.sumber
$*
alih - alih"$@"
, penggunaan eval yang tidak berguna, status keluar tidak dilaporkan (Anda tidak menggunakanexec
yang pertama), hilang-
/--
, pesan kesalahan tidak pada stderr, 0 status keluar untuk kondisi kesalahan , menggunakan / bin / sh untuk LIN_BASH, titik koma yang tidak berguna (kosmetik), menggunakan semua huruf besar untuk variabel non-env.uname -s
sepertiuname
(uname adalah untuk nama Unix). Anda lupa menyebutkan bahwa lompatan dipicu oleh-x
opsi untukperl
.CATATAN: @ jw013 mengajukan keberatan yang tidak didukung berikut dalam komentar di bawah:
Aku menjawab keberatan keamanannya dengan menunjukkan bahwa setiap izin khusus yang hanya diperlukan sekali per install / update tindakan untuk menginstal / memperbarui yang self-menginstal naskah - yang saya pribadi sebut cukup aman. Saya juga mengarahkannya ke
man sh
referensi untuk mencapai tujuan yang sama dengan cara yang sama. Saya tidak, pada saat itu, repot-repot menunjukkan bahwa keamanan apa pun cacat atau praktik - praktik yang umumnya tidak disarankan yang mungkin atau mungkin tidak terwakili dalam jawaban saya, mereka lebih cenderung berakar pada pertanyaan itu sendiri daripada mereka dalam jawaban saya untuk itu:Tidak puas, @ jw013 terus menolak dengan melanjutkan argumennya yang belum didukung dengan setidaknya beberapa pernyataan salah:
Di tempat pertama:
KODE YANG HANYA DILAKSANAKAN DALAM SEGERA SCRIPT SHELL EXECUTABLE ADALAH
#!
SENDIRI(meskipun bahkan
#!
secara resmi tidak ditentukan )Sebuah skrip shell hanyalah sebuah file teks - agar dapat memiliki efek sama sekali, ia harus dibaca oleh file executable lain, instruksinya kemudian ditafsirkan oleh file executable lainnya, sebelum akhirnya file executable lainnya kemudian menjalankan interpretasinya terhadap skrip shell. Hal ini tidak mungkin untuk eksekusi file shell script untuk melibatkan kurang dari dua file. Ada pengecualian yang mungkin ada di
zsh
kompiler sendiri, tetapi dengan ini saya memiliki sedikit pengalaman dan sama sekali tidak diwakili di sini.Hashbang skrip shell harus menunjuk ke penerjemah yang dimaksud atau dibuang sebagai tidak relevan.
PENGAKUAN TOKEN SHELL'S / PERLUASAN EKSEKUSI ADALAH DITETAPKAN STANDAR
Shell memiliki dua mode dasar untuk mem-parsing dan menginterpretasikan inputnya: baik input saat ini mendefinisikan
<<here_document
atau mendefinisikan{ ( command |&&|| list ) ; } &
- dengan kata lain, shell mengartikan token sebagai pembatas untuk perintah yang harus dijalankan setelah ia membacanya di atau sebagai instruksi untuk membuat file dan memetakannya ke file descriptor untuk perintah lain. Itu dia.Saat menginterpretasikan perintah untuk mengeksekusi token pembatas shell pada sekumpulan kata yang disimpan. Ketika shell menemukan token pembuka itu harus terus membaca dalam daftar perintah sampai daftar baik dibatasi oleh token penutup seperti baris baru - bila berlaku - atau token penutup seperti
})
untuk({
sebelum eksekusi.Shell membedakan antara perintah sederhana dan perintah majemuk. The perintah senyawa adalah seperangkat perintah yang harus dibaca sebelum eksekusi, tetapi shell tidak melakukan
$expansion
apapun dari penyusunnya perintah sederhana sampai secara tunggal mengeksekusi masing-masing.Jadi, dalam contoh berikut,
;semicolon
kata-kata yang dicadangkan membatasi masing-masing perintah sederhana sedangkan karakter yang tidak luput\newline
membatasi antara dua perintah majemuk:Itu adalah penyederhanaan pedoman. Itu menjadi jauh lebih rumit ketika Anda mempertimbangkan shell-builtin, subshell, lingkungan saat ini dan lain-lain, tetapi, untuk tujuan saya di sini, itu sudah cukup.
Dan berbicara tentang built-in dan daftar perintah, a
function() { declaration ; }
hanyalah sarana untuk menetapkan perintah majemuk ke perintah sederhana. Shell tidak boleh melakukan apapun$expansions
pada pernyataan deklarasi itu sendiri - untuk memasukkan<<redirections>
- tetapi sebagai gantinya harus menyimpan definisi sebagai string tunggal, literal dan menjalankannya sebagai shell khusus built-in ketika dipanggil.Jadi fungsi shell yang dideklarasikan dalam skrip shell yang dapat dieksekusi disimpan dalam memori shell interpreting dalam bentuk string literalnya - tidak diperluas untuk menyertakan dokumen-dokumen yang ditambahkan di sini sebagai input - dan dieksekusi secara independen dari file sumbernya setiap kali ia disebut sebagai shell built- selama lingkungan shell saat ini berlangsung.
Sebuah
<<HERE-DOCUMENT
IS AN INLINE FILEKamu melihat? Untuk setiap shell di atas shell membuat file dan memetakannya ke deskriptor file. Dalam
zsh, (ba)sh
shell membuat file biasa/tmp
, membuang output, memetakannya ke deskriptor, lalu menghapus/tmp
file sehingga salinan kernel dari deskriptor adalah yang tersisa.dash
menghindari semua omong kosong itu dan hanya membuang pemrosesan outputnya ke|pipe
file anonim yang ditujukan untuk<<
target pengalihan .Ini membuat
dash
:secara fungsional setara dengan
bash
:sementara
dash
implementasi setidaknya POSIXly portable.YANG MEMBUAT BEBERAPA FILES
Jadi dalam jawaban di bawah ini ketika saya melakukannya:
Berikut ini terjadi:
Saya pertama kali
cat
isi file apapun shell diciptakan untukFILE
menjadi./file
, membuatnya menjadi executable, kemudian jalankan.Kernel menginterpretasikan
#!
dan memanggil/usr/bin/sh
dengan<read
deskriptor file yang ditugaskan./file
.sh
memetakan string ke dalam memori yang terdiri dari perintah majemuk yang dimulai pada_fn()
dan berakhir padaSCRIPT
.Ketika
_fn
disebut,sh
harus terlebih dahulu menafsirkan kemudian peta untuk deskriptor file didefinisikan dalam<<SCRIPT...SCRIPT
sebelum memohon_fn
sebagai khusus built-in utilitas karenaSCRIPT
merupakan_fn
's<input.
String keluaran oleh
printf
dancommand
ditulis ke_fn
's keluar standar>&1
- yang diarahkan ke shell saat iniARGV0
- atau$0
.cat
menyatukan deskriptor<&0
-input file standar -SCRIPT
- atas argumen>
shell saat ini yang terpotongARGV0
, atau$0
.Menyelesaikan perintah majemuk saat ini yang sudah dibaca ,
sh exec
adalah$0
argumen yang dapat dieksekusi - dan baru ditulis ulang - .Dari saat
./file
dipanggil sampai instruksi yang terkandung menentukan bahwa itu harusexec
d lagi,sh
membacanya dalam perintah majemuk tunggal pada saat dieksekusi, sementara./file
itu sendiri tidak melakukan apa-apa kecuali dengan senang hati menerima konten baru. File yang sebenarnya sedang bekerja adalah/usr/bin/sh, /usr/bin/cat, /tmp/sh-something-or-another.
TERIMA KASIH, SETELAH SEMUA
Jadi ketika @ jw013 menetapkan bahwa:
... di tengah kritiknya yang keliru tentang jawaban ini, dia sebenarnya tanpa sadar mengampuni satu-satunya metode yang digunakan di sini, yang pada dasarnya hanya berfungsi untuk:
MENJAWAB
Semua jawaban di sini baik, tetapi tidak ada yang sepenuhnya benar. Semua orang tampaknya mengklaim Anda tidak dapat secara dinamis dan permanen melacak Anda
#!bang
. Berikut ini adalah demonstrasi pengaturan jalur independen shebang:DEMO
KELUARAN
Kamu melihat? Kami hanya membuat skrip menimpa dirinya sendiri. Dan itu hanya terjadi sekali setelah
git
sinkronisasi. Sejak saat itu ada jalan yang benar di baris bang # !.Sekarang hampir semua itu hanya ada bulu. Untuk melakukan ini dengan aman, Anda perlu:
Fungsi yang didefinisikan di bagian atas dan disebut di bagian bawah yang melakukan penulisan. Dengan cara ini kami menyimpan semua yang kami butuhkan dalam memori dan memastikan seluruh file terbaca sebelum kami mulai menulisnya.
Beberapa cara menentukan jalan apa yang seharusnya.
command -v
cukup bagus untuk itu.Heredocs sangat membantu karena itu adalah file yang sebenarnya. Sementara itu, mereka akan menyimpan skrip Anda. Anda dapat menggunakan string juga tetapi ...
Anda harus memastikan bahwa shell membaca dalam perintah yang menimpa skrip Anda dalam daftar perintah yang sama dengan yang mengeksekusinya.
Lihat:
Perhatikan bahwa saya hanya memindahkan
exec
perintah ke satu baris. Sekarang:Saya tidak mendapatkan bagian kedua dari output karena skrip tidak dapat membaca di perintah berikutnya. Tetap saja, karena satu-satunya perintah yang hilang adalah yang terakhir:
Naskahnya dibuat sebagaimana mestinya - sebagian besar karena semuanya ada di heredoc - tetapi jika Anda tidak merencanakannya dengan benar, Anda dapat memotong filestream Anda, yang merupakan apa yang terjadi pada saya di atas.
sumber
man command
pendapat yang bertentangan.man command
pendapat yang bertentangan - tidak menemukan satu. Bisakah Anda mengarahkan saya ke bagian / paragraf spesifik yang Anda bicarakan?man sh
- cari 'command -v'. Saya tahu itu di salah satuman
halaman yang saya lihat di hari lain.command -v
contoh yang Anda bicarakanman sh
. Itu adalah skrip pemasang yang tampak normal, dan bukan skrip modifikasi sendiri . Bahkan installer mandiri hanya mengandung input pra-modifikasi, dan output modifikasinya di tempat lain. Mereka tidak menulis ulang sendiri seperti yang Anda rekomendasikan.Berikut adalah salah satu cara untuk memiliki skrip modifikasi diri yang memperbaiki shebang-nya. Kode ini harus diawali dengan skrip Anda yang sebenarnya.
Beberapa komentar:
Ini harus dijalankan sekali oleh seseorang yang memiliki hak untuk membuat dan menghapus file di direktori tempat skrip berada.
Ia hanya menggunakan sintaksis shell bourne legacy karena, meskipun diyakini banyak orang,
/bin/sh
tidak dijamin menjadi shell POSIX, bahkan en OSIX yang sesuai dengan POSIX.Ini mengatur PATH ke POSIX compliant diikuti oleh daftar lokasi zsh yang mungkin untuk menghindari memilih zsh "palsu".
Jika karena alasan tertentu, skrip modifikasi sendiri tidak diinginkan, akan sepele untuk mendistribusikan dua skrip bukan satu, yang pertama adalah yang ingin Anda tambal dan yang kedua, yang saya sarankan sedikit dimodifikasi untuk memproses yang pertama.
sumber
/bin/sh
point adalah salah satu yang baik - tetapi dalam kasus yang Anda perlu premodified#!
sama sekali? Dan bukankahawk
hanya palsu sepertizsh
itu?sed -i
lakukan. Saya pribadi berpikir$PATH
masalah yang dicatat dalam komentar pada jawaban lain dan yang Anda atasi dengan aman seperti yang saya bayangkan di sini dalam beberapa baris lebih baik ditangani dengan mendefinisikan dependensi secara sederhana dan eksplisit dan / atau pengujian yang ketat dan eksplisit - misalnya, sekaranggetconf
mungkin palsu, tetapi kemungkinannya hampir nol, sama seperti untukzsh
danawk.
$(getconf PATH)
bukan Bourne.cp $0 $0.old
adalah sintaks zsh. Setara dengan Bourne akan seperti yangcp "$0" "$0.old"
Anda inginkancp -- "$0" "$0.old"