Apakah shebang menentukan shell yang menjalankan skrip?

84

Ini mungkin pertanyaan konyol, tapi saya tetap bertanya. Jika saya telah menyatakan shebang

#!/bin/bash 

di awal my_shell_script.sh, jadi saya selalu harus memanggil skrip ini menggunakan bash

[my@comp]$bash my_shell_script.sh

atau bisakah saya menggunakan mis

[my@comp]$sh my_shell_script.sh

dan skrip saya menentukan shell yang berjalan menggunakan shebang? Apakah sama halnya dengan kshshell? Saya menggunakan AIX.

jrara
sumber
6
ada sedikit kebingungan pada bagian Anda: ketika Anda melakukan "_some_shell some_script" itu dimulai _some_shell dan memintanya untuk menafsirkan some_script. Jadi tidak, jika Anda melakukan "sh my_shell_script.sh" itu tidak akan menafsirkan shebang, tetapi akan menafsirkan skrip di sh sebagai gantinya. Untuk menggunakan shebang: chmod +x my_shell_script.sh ; /path/to/my_shell_script.sh # or ./my_shell_script.sh if you happen to be in its directory
Olivier Dulac

Jawaban:

117

The shebang #! adalah turunan dibaca manusia dari angka ajaib yang terdiri dari string byte 0x23 0x21, yang digunakan oleh exec()keluarga fungsi untuk menentukan apakah file yang akan dieksekusi adalah script atau biner. Ketika shebang hadir, exec()akan menjalankan executable yang ditentukan setelah shebang sebagai gantinya.

Perhatikan bahwa ini berarti bahwa jika Anda menjalankan skrip dengan menentukan penerjemah pada baris perintah, seperti yang dilakukan dalam kedua kasus yang diberikan dalam pertanyaan, exec()akan mengeksekusi penerjemah yang ditentukan pada baris perintah, itu bahkan tidak akan melihat skrip.

Jadi, seperti yang telah dicatat orang lain, jika Anda ingin exec()memanggil juru bahasa yang ditentukan pada baris shebang, skrip harus memiliki bit yang dapat dieksekusi dan dipanggil sebagai ./my_shell_script.sh.

Perilaku ini mudah ditunjukkan dengan skrip berikut:

#!/bin/ksh
readlink /proc/$$/exe

Penjelasan:

  • #!/bin/kshmendefinisikan kshmenjadi penerjemah.

  • $$ memegang PID dari proses saat ini.

  • /proc/pid/exe adalah symlink ke executable dari proses (setidaknya di Linux; di AIX, /proc/$$/object/a.out adalah tautan ke executable).

  • readlink akan menampilkan nilai tautan simbolik.

Contoh:

Catatan : Saya mendemonstrasikan ini di Ubuntu, di mana shell default /bin/shadalah symlink ke dash yaitu /bin/dashdan /bin/kshsymlink ke /etc/alternatives/ksh, yang pada gilirannya adalah symlink ke /bin/pdksh.

$ chmod +x getshell.sh
$ ./getshell.sh 
/bin/pdksh
$ bash getshell.sh 
/bin/bash
$ sh getshell.sh 
/bin/dash
Thomas Nyman
sumber
terima kasih Thomas atas jawaban ini. Berpura-pura kita meluncurkan skrip sebagai proses anak dari Node.js atau Java atau apa pun. Bisakah kita meluncurkan proses "exec", dan kemudian exec akan menjalankan skrip shell? Saya bertanya karena saya mencari jawaban untuk pertanyaan ini: stackoverflow.com/questions/41067872/…
Alexander Mills
1
@AlexanderMills Yang exec()dimaksud dalam jawaban ini adalah panggilan sistem, perintahnya execadalah shell builtin, itulah sebabnya Anda tidak dapat menjalankan exec program dari Node.js atau Java. Namun, perintah shell apa pun yang dijalankan oleh mis. Runtime.exec()Di Java akhirnya diproses oleh exec()panggilan sistem.
Thomas Nyman
Huh, ya saya memang akrab dengan Java API yang baru saja Anda sebutkan, saya bertanya-tanya apakah ada cara untuk memanggil panggilan tingkat rendah exec () dari Node.js, entah bagaimana
Alexander Mills
@AlexanderMills Saya akan membayangkan child_process.{exec(),execFile(),spawn()} semua akan diimplementasikan menggunakan C exec()(through process).
Thomas Nyman
10

Ya itu. Ngomong-ngomong itu bukan pertanyaan konyol. Referensi untuk jawaban saya ada di sini . Memulai Script Dengan #!

  • Ini disebut garis shebang atau "bang".

  • Itu tidak lain adalah jalur absolut menuju juru bahasa Bash.

  • Ini terdiri dari tanda angka dan karakter tanda seru (#!), Diikuti oleh path lengkap ke penerjemah seperti / bin / bash.

    Semua skrip di Linux dijalankan menggunakan interpreter yang ditentukan pada baris pertama. Hampir semua skrip bash sering dimulai dengan #! / Bin / bash (dengan asumsi bahwa Bash telah diinstal di / bin) Ini memastikan bahwa Bash akan digunakan untuk menafsirkan skrip, bahkan jika dijalankan di bawah shell lain. Shebang diperkenalkan oleh Dennis Ritchie antara Versi 7 Unix dan 8 di Bell Laboratories. Itu kemudian juga ditambahkan ke jalur BSD di Berkeley.

Mengabaikan Garis Juru Bahasa (shebang)

Jika Anda tidak menentukan jalur juru bahasa, standarnya biasanya adalah / bin / sh. Namun, Anda disarankan untuk menyetel #! / Bin / bash line.

vfbsilva
sumber
3
Untuk menguraikan, kernel hanya tahu bagaimana menjalankan binari yang terhubung secara statis dan di mana menemukan informasi juru bahasa untuk orang lain (bidang khusus dalam biner, atau baris shebang). Menjalankan skrip shell berarti mengikuti garis shebang ke shell, dan kemudian mengikuti bidang DT_INTERP dalam biner shell ke dynamic linker.
Simon Richter
5
Perhatikan juga bahwa ini tidak terbatas pada skrip shell. Semua file skrip berbasis teks menggunakan ini. misalnya #!/usr/bin/perl #!/usr/local/bin/python #!/usr/local/bin/rubyentri shebang umum lainnya yang digunakan untuk mendukung banyak sistem adalah dengan menggunakan env untuk menemukan juru bahasa yang ingin Anda gunakan, seperti#!/usr/bin/env perl #!/usr/bin/env python
sambler
@sambler berbicara tentang env, yang mana yang sebenarnya lebih disukai? Python dan Perl sering digunakan env, sedangkan pada shellscripts, ini sering dihilangkan dan shebang menunjuk ke shell yang dimaksud.
polemon
1
@pemonemon lebih sedikit yang lebih disukai dan lebih banyak di jalur mana yang bervariasi. Kerang dasar berada di jalur yang sama pada semua sistem. Versi terkini dari perl dan python dapat diinstal di lokasi yang berbeda pada sistem yang berbeda sehingga menggunakan env memungkinkan shebang yang sama untuk selalu berfungsi, itulah sebabnya env lebih banyak digunakan dengan skrip perl dan python daripada skrip shell.
sambler
envuntuk menemukan program dalam $ PATH sedikit meretas. Itu tidak mengatur variabel lingkungan seperti namanya. $ PATH mungkin hasil yang berbeda untuk pengguna yang berbeda. Tapi ini membantu skrip berjalan tanpa modifikasi pada sistem yang menempatkan penerjemah perl yang wajar di beberapa tempat yang aneh.
John Mahowald
4

The execsystem call dari kernel Linux mengerti shebangs ( #!) native

Ketika Anda melakukannya di bash:

./something

di Linux, ini memanggil execsystem call dengan path ./something.

Baris kernel ini dipanggil pada file yang diteruskan ke exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Itu membaca byte pertama file, dan membandingkannya #!.

Jika perbandingannya benar, maka seluruh baris diuraikan oleh kernel Linux, yang membuat execpanggilan lain dengan path /usr/bin/env pythondan file saat ini sebagai argumen pertama:

/usr/bin/env python /path/to/script.py

dan ini berfungsi untuk bahasa skrip apa pun yang digunakan #sebagai karakter komentar.

Dan ya, Anda dapat membuat loop tanpa batas dengan:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

Bash mengenali kesalahan:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! Kebetulan bisa dibaca manusia, tapi itu tidak wajib.

Jika file dimulai dengan byte yang berbeda, maka execpanggilan sistem akan menggunakan penangan yang berbeda. Handler internal terpenting lainnya adalah file yang dapat dieksekusi ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305 yang memeriksa byte 7f 45 4c 46(yang juga merupakan manusia dapat dibaca untuk .ELF). Mari kita konfirmasi dengan membaca 4 byte pertama /bin/ls, yang merupakan executable ELF:

head -c 4 "$(which ls)" | hd 

keluaran:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Jadi ketika kernel melihat byte-byte itu, ia mengambil file ELF, memasukkannya ke dalam memori dengan benar, dan memulai proses baru dengannya. Lihat juga: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-running-under-linux/31394861#31394861

Akhirnya, Anda dapat menambahkan penangan shebang Anda sendiri dengan binfmt_miscmekanismenya. Misalnya, Anda dapat menambahkan penangan khusus untuk .jarfile . Mekanisme ini bahkan mendukung penangan melalui ekstensi file. Aplikasi lain adalah untuk menjalankan executable dari arsitektur yang berbeda dengan QEMU .

Saya tidak berpikir POSIX menentukan shebangs, namun: https://unix.stackexchange.com/a/346214/32558 , meskipun ia menyebutkannya di bagian rasional, dan dalam bentuk "jika skrip yang dapat dieksekusi didukung oleh sistem sesuatu mungkin terjadi ".

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
sumber
1
Berlari ./somethingdari shell tidak akan melewatkan path lengkap ke exec, tetapi persis path yang dimasukkan. Bisakah Anda memperbaiki ini dalam jawaban Anda? Lakukan echo "$0"dalam skrip Anda dan Anda akan melihat ini masalahnya.
AndiDog
2

Bahkan, jika Anda mengambilnya secara konsekuen, executable yang dicatat di baris shebang, hanyalah executable. Masuk akal untuk menggunakan beberapa penerjemah teks sebagai yang dapat dieksekusi, tetapi itu tidak perlu. Hanya untuk klarifikasi dan demonstrasi, saya membuat tes yang agak tidak berguna:

#!/bin/cat
useless text
more useless text
still more useless text

Bernama file test.txt dan mengatur bit exectuable chmod u+x test.txt, kemudian "disebut" itu: ./test.txt. Seperti yang diharapkan, isi file adalah output. Dalam hal ini, kucing tidak mengabaikan garis shebang. Ini hanya menampilkan semua lini. Setiap penerjemah yang berguna karenanya harus dapat mengabaikan garis shebang ini. Untuk bash, perl dan PHP, ini hanyalah sebuah baris komentar. Jadi ya, ini mengabaikan garis shebang.

Siegfried
sumber
-1

Dari apa yang saya kumpulkan, setiap kali file memiliki bit yang dapat dieksekusi dan dipanggil, kernel menganalisis header file untuk menentukan bagaimana melanjutkan (sejauh yang saya tahu, Anda dapat menambahkan penangan khusus untuk format file khusus melalui LKMs). Jika file tersebut muncul sebagai file teks dengan #! kombinasi pada awalnya, pelaksanaannya dikirim ke executable lain (biasanya shell of macam), jalan yang akan ditentukan secara langsung setelah shebang tersebut, di baris yang sama. Kernel kemudian melanjutkan untuk mengeksekusi shell dan meneruskan file untuk ditangani.

Singkatnya, tidak masalah shell mana yang Anda gunakan skrip - kernel akan mengirimkan eksekusi ke salah satu cara yang sesuai.

Vladimir Parka
sumber
4
Ada perbedaan yang mencolok antara bash ./myscript.shdan ./myscript.sh.
CVn
Apa yang Anda maksud dengan "perbedaan mencolok" ini?
jrara
3
@ jrara Lihat jawaban saya, pernyataan bahwa "tidak masalah shell mana yang Anda gunakan skripnya" sama sekali tidak benar.
Thomas Nyman