Mengapa lebih baik menggunakan "#! / Usr / bin / env NAME" daripada "#! / Path / to / NAME" sebagai shebang saya?

459

Saya perhatikan bahwa beberapa skrip yang saya peroleh dari orang lain memiliki shebang #!/path/to/NAMEsementara yang lain (menggunakan alat yang sama, NAME) memiliki shebang #!/usr/bin/env NAME.

Keduanya tampaknya berfungsi dengan baik. Dalam tutorial (pada Python, misalnya), tampaknya ada saran bahwa shebang yang terakhir lebih baik. Tapi, saya tidak begitu mengerti mengapa demikian.

Saya menyadari bahwa, untuk menggunakan shebang yang terakhir, NAME harus di PATH sedangkan shebang pertama tidak memiliki batasan ini.

Selain itu, tampaknya (bagi saya) bahwa yang pertama akan menjadi shebang yang lebih baik, karena ia menentukan dengan tepat di mana NAME berada. Jadi, dalam hal ini, jika ada beberapa versi NAME (mis., / Usr / bin / NAME, / usr / local / bin / NAME), case pertama menentukan mana yang akan digunakan.

Pertanyaan saya adalah mengapa shebang pertama lebih disukai daripada yang kedua?

TheGeeko61
sumber
12
Lihat jawaban ini ...
jasonwryan
@ TheGeeko61: Dalam kasus saya, saya memiliki sesuatu yang rusak dan beberapa variabel tidak ada dalam env. Jadi saya sarankan untuk menggunakan shebang ini untuk memverifikasi apakah env dimuat dengan benar.
Gigamegs

Jawaban:

462

Belum tentu lebih baik.

Keuntungannya #!/usr/bin/env pythonadalah bahwa ia akan menggunakan apa pun yang pythondapat dieksekusi muncul pertama kali di pengguna $PATH.

The Kerugian dari #!/usr/bin/env pythonadalah bahwa ia akan menggunakan apa pun yang pythondieksekusi muncul pertama kali di pengguna $PATH.

Itu berarti bahwa skrip dapat berperilaku berbeda tergantung pada siapa yang menjalankannya. Untuk satu pengguna, mungkin menggunakan /usr/bin/pythonyang diinstal dengan OS. Untuk yang lain, mungkin menggunakan eksperimen /home/phred/bin/pythonyang tidak berfungsi dengan benar.

Dan jika pythonhanya dipasang di /usr/local/bin, pengguna yang tidak memiliki /usr/local/bindi $PATHbahkan tidak akan dapat menjalankan script. (Itu mungkin tidak terlalu mungkin pada sistem modern, tetapi itu bisa dengan mudah terjadi untuk penerjemah yang lebih tidak jelas.)

Dengan menentukan #!/usr/bin/pythonAnda menentukan penerjemah mana yang akan digunakan untuk menjalankan skrip pada sistem tertentu .

Masalah potensial lainnya adalah bahwa #!/usr/bin/envtriknya tidak memungkinkan Anda meneruskan argumen ke intrepreter (selain nama skrip, yang diteruskan secara implisit). Ini biasanya bukan masalah, tetapi bisa saja. Banyak skrip Perl yang ditulis #!/usr/bin/perl -w, tetapi use warnings;merupakan pengganti yang disarankan akhir-akhir ini. Skrip Csh harus digunakan #!/bin/csh -f- tetapi skrip csh tidak disarankan sejak awal. Tapi mungkin ada contoh lain.

Saya memiliki sejumlah skrip Perl dalam sistem kontrol sumber pribadi yang saya instal ketika saya membuat akun pada sistem baru. Saya menggunakan skrip penginstal yang mengubah #!baris setiap skrip saat menginstalnya di saya $HOME/bin. (Saya tidak harus menggunakan apa pun selain #!/usr/bin/perlakhir - akhir ini; ini kembali ke masa ketika Perl sering tidak diinstal secara default.)

Poin minor: #!/usr/bin/envtrik ini bisa dibilang merupakan penyalahgunaan envperintah, yang semula dimaksudkan (sesuai namanya) untuk memohon perintah dengan lingkungan yang diubah. Selain itu, beberapa sistem yang lebih lama (termasuk SunOS 4, jika saya ingat dengan benar) tidak memiliki envperintah /usr/bin. Tak satu pun dari ini cenderung menjadi perhatian yang signifikan. envtidak bekerja dengan cara ini, banyak skrip yang menggunakan #!/usr/bin/envtrik, dan penyedia OS tidak akan melakukan apa pun untuk memecahkannya. Ini mungkin menjadi masalah jika Anda ingin naskah Anda untuk berjalan pada sistem benar-benar tua, tapi kemudian Anda mungkin perlu mengubah pula.

Masalah lain yang mungkin, (terima kasih kepada Sopalajo de Arrierez untuk menunjukkannya dalam komentar) adalah bahwa pekerjaan cron dijalankan dengan lingkungan terbatas. Secara khusus, $PATHbiasanya sesuatu seperti /usr/bin:/bin. Jadi jika direktori yang berisi penerjemah tidak kebetulan berada di salah satu direktori itu, bahkan jika itu di default Anda $PATHdi shell pengguna, maka /usr/bin/envtriknya tidak akan berhasil. Anda dapat menentukan jalur yang tepat, atau Anda dapat menambahkan baris ke crontab Anda untuk mengatur $PATH( man 5 crontabuntuk detail).

Keith Thompson
sumber
4
Jika / usr / bin / perl adalah perl 5.8, $ HOME / bin / perl adalah 5.12, dan sebuah skrip yang membutuhkan 5.12 hardcodes / usr / bin / perl di shebang, ini bisa menjadi kesulitan besar untuk menjalankan skrip. Saya jarang melihat perl / usr / bin / env perl perl dari PATH menjadi masalah, tetapi seringkali sangat membantu. Dan itu jauh lebih cantik daripada hack eksekutif!
William Pursell
8
Perlu dicatat bahwa, jika Anda ingin menggunakan versi juru bahasa tertentu, / usr / bin / env masih lebih baik. hanya karena biasanya ada beberapa versi juru bahasa yang diinstal pada mesin Anda bernama perl5, perl5.12, perl5.10, python3.3, python3.32, dll. dan jika aplikasi Anda hanya diuji pada versi tertentu, Anda masih dapat menentukan #! / usr / bin / env perl5.12 dan baiklah meskipun pengguna telah menginstalnya di tempat yang tidak biasa. Dalam pengalaman saya, 'python' biasanya hanya symlink ke versi standar sistem (belum tentu versi terbaru pada sistem).
root
6
@root: Untuk Perl, use v5.12;melayani beberapa tujuan itu. Dan #!/usr/bin/env perl5.12akan gagal jika sistem memiliki Perl 5.14 tetapi tidak 5.12. Untuk Python 2 vs. 3, #!/usr/bin/python2dan #!/usr/bin/python3kemungkinan akan berfungsi.
Keith Thompson
3
@ GoodPerson: Misalkan saya menulis skrip Perl untuk diinstal /usr/local/bin. Saya kebetulan tahu bahwa itu berfungsi dengan benar /usr/bin/perl. Saya tidak tahu apakah itu berfungsi dengan apa pun yang perldapat dieksekusi yang dilakukan oleh beberapa pengguna acak dalam dirinya $PATH. Mungkin seseorang bereksperimen dengan beberapa versi Perl kuno; karena saya tentukan #!/usr/bin/perl, skrip saya (yang pengguna bahkan tidak tahu atau peduli adalah skrip Perl) tidak akan berhenti bekerja.
Keith Thompson
5
Jika tidak berhasil /usr/bin/perl, saya akan mengetahuinya dengan sangat cepat, dan merupakan tanggung jawab pemilik / administrator sistem untuk selalu memperbaruinya. Jika Anda ingin menjalankan skrip saya dengan perl Anda sendiri, jangan ragu untuk mengambil dan memodifikasi salinan atau memintanya melalui perl foo. (Dan Anda mungkin mempertimbangkan kemungkinan bahwa 55 orang yang mengangkat jawaban ini juga mengetahui satu atau dua hal. Tentu saja mungkin Anda benar dan mereka semua salah, tetapi itu bukan cara saya bertaruh.)
Keith Thompson
101

Karena / usr / bin / env dapat menafsirkan Anda $PATH, yang membuat skrip lebih portabel.

#!/usr/local/bin/python

Hanya akan menjalankan skrip Anda jika python diinstal di / usr / local / bin.

#!/usr/bin/env python

Akan menginterpretasikan Anda $PATH, dan menemukan python di direktori mana pun di Anda $PATH.

Jadi skrip Anda lebih portabel, dan akan berfungsi tanpa modifikasi pada sistem tempat python diinstal /usr/bin/python, atau /usr/local/bin/python, atau bahkan direktori khusus (yang telah ditambahkan ke $PATH), seperti /opt/local/bin/python.

Portabilitas adalah satu-satunya alasan penggunaan envlebih disukai daripada jalur kode keras.

Tim Kennedy
sumber
19
Direktori khusus untuk pythonfile yang dapat dieksekusi sangat umum karena virtualenvpenggunaan meningkat.
Xiong Chiamiov
1
Bagaimana #! python, mengapa itu tidak digunakan?
kristianp
6
#! pythontidak digunakan karena Anda harus berada di direktori yang sama dengan biner python, karena bareword pythonditafsirkan sebagai path lengkap ke file. Jika Anda tidak memiliki biner python di direktori saat ini, Anda akan mendapatkan error seperti bash: ./script.py: python: bad interpreter: No such file or directory. Sama seperti jika Anda menggunakan#! /not/a/real/path/python
Tim Kennedy
47

Menentukan jalur absolut lebih tepat pada sistem yang diberikan. Kelemahannya adalah terlalu tepat. Misalkan Anda menyadari bahwa instalasi sistem Perl terlalu lama untuk skrip Anda dan Anda ingin menggunakan skrip Anda sendiri: maka Anda harus mengedit skrip dan mengubahnya #!/usr/bin/perlmenjadi #!/home/myname/bin/perl. Lebih buruk lagi, jika Anda memiliki Perl /usr/binpada beberapa mesin, /usr/local/binpada yang lain, dan /home/myname/bin/perlpada mesin lain, maka Anda harus mempertahankan tiga salinan skrip yang terpisah dan menjalankan yang sesuai pada masing-masing mesin.

#!/usr/bin/envistirahat jika PATHburuk, tetapi begitu juga hampir semua hal. Mencoba untuk beroperasi dengan yang buruk PATHsangat jarang berguna, dan menunjukkan bahwa Anda tahu sedikit tentang sistem yang sedang dijalankan skrip, jadi Anda tidak dapat mengandalkan jalur absolut apa pun.

Ada dua program yang lokasinya dapat Anda andalkan di hampir setiap varian unix: /bin/shdan /usr/bin/env. Beberapa varian Unix yang tidak jelas dan sebagian besar sudah /bin/envtidak ada /usr/bin/env, tetapi Anda tidak akan menemukannya. Sistem modern /usr/bin/envjustru karena penggunaannya yang luas di shebangs. /usr/bin/envadalah sesuatu yang dapat Anda andalkan.

Selain itu /bin/sh, satu-satunya waktu Anda harus menggunakan jalur absolut di shebang adalah ketika skrip Anda tidak dimaksudkan untuk dibawa-bawa, sehingga Anda dapat mengandalkan lokasi yang diketahui untuk penerjemah. Misalnya, skrip bash yang hanya berfungsi di Linux dapat digunakan dengan aman #!/bin/bash. Sebuah skrip yang hanya dimaksudkan untuk digunakan di rumah dapat bergantung pada konvensi lokasi juru bahasa.

#!/usr/bin/envmemang memiliki kelemahan. Ini lebih fleksibel daripada menentukan jalur absolut tetapi masih membutuhkan mengetahui nama juru bahasa. Kadang-kadang Anda mungkin ingin menjalankan juru bahasa yang tidak ada dalam $PATH, misalnya di lokasi yang relatif terhadap skrip. Dalam kasus seperti itu, Anda sering dapat membuat skrip polyglot yang dapat diartikan baik oleh shell standar maupun oleh juru bahasa yang Anda inginkan. Misalnya, untuk membuat skrip Python 2 portabel untuk sistem di mana pythonPython 3 dan python2Python 2, dan ke sistem di mana pythonPython 2 dan python2tidak ada:

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0" "$@"
else
  exec python "$0" "$@"
fi
'''
# real Python script starts here
def …
Gilles
sumber
22

Khusus untuk perl, menggunakan #!/usr/bin/envadalah ide yang buruk karena dua alasan.

Pertama, ini tidak portabel. Pada beberapa platform tidak jelas, env tidak ada di / usr / bin. Kedua, seperti yang dicatat Keith Thompson , ini dapat menyebabkan masalah dengan melewatinya argumen di jalur shebang. Solusi portabel maksimal adalah ini:

#!/bin/sh
exec perl -x "$0" "$@"
#!perl

Untuk detail tentang cara kerjanya, lihat 'perldoc perlrun' dan apa yang dikatakannya tentang argumen -x.

DrHyde
sumber
Tepatnya server yang saya cari: cara menulis porable "shebang" untuk skrip perl yang akan memungkinkan tambahan arument yang diteruskan ke perl (baris terakhir pada contoh Anda menerima arahan tambahan).
AmokHuginnsson
15

Alasan ada perbedaan antara keduanya adalah karena bagaimana skrip dieksekusi.

Menggunakan /usr/bin/env(yang, sebagaimana disebutkan dalam jawaban lain, tidak /usr/binaktif pada beberapa OS) diperlukan karena Anda tidak bisa hanya menempatkan nama yang dapat dieksekusi setelah #!- itu harus menjadi jalur absolut. Ini karena #!mekanismenya bekerja pada level yang lebih rendah daripada shell. Itu bagian dari pemuat biner kernel. Ini bisa diuji. Masukkan ini ke dalam file dan tandai itu dapat dieksekusi:

#!bash

echo 'foo'

Anda akan menemukannya mencetak kesalahan seperti ini ketika Anda mencoba menjalankannya:

Failed to execute process './test.sh'. Reason:
The file './test.sh' does not exist or could not be executed.

Jika file ditandai executable dan dimulai dengan #!, kernel (yang tidak tahu tentang $PATHatau direktori saat ini: ini adalah konsep pengguna-tanah) akan mencari file menggunakan path absolut. Karena menggunakan jalur absolut bermasalah (seperti yang disebutkan dalam jawaban lain), seseorang datang dengan sebuah trik: Anda dapat menjalankan /usr/bin/env(yang hampir selalu di lokasi itu) untuk menjalankan sesuatu menggunakan $PATH.

Tiffany Bennett
sumber
11

Ada dua masalah lagi dengan penggunaan #!/usr/bin/env

  1. Itu tidak memecahkan masalah menentukan path lengkap ke penerjemah, itu hanya memindahkannya ke env.

    envtidak lebih dijamin akan di /usr/bin/envdari bashdijamin di /bin/bashatau python di /usr/bin/python.

  2. env menimpa ARGV [0] dengan nama penerjemah (e..g bash atau python).

    Ini mencegah nama skrip Anda muncul di, misalnya, psoutput (atau mengubah bagaimana / di mana ia muncul) dan membuatnya tidak mungkin untuk menemukannya dengan, misalnya,ps -C scriptname.sh

[perbarui 2016-06-04]

Dan masalah ketiga:

  1. Mengubah PATH Anda lebih berfungsi daripada hanya mengedit baris pertama skrip, terutama ketika skrip pengeditan tersebut sepele. misalnya:

    printf "%s\n" 1 i '#!'$(type -P python2) . w | ed foo.py

    Menambahkan atau menunda pendahuluan direktori ke $ PATH cukup mudah (walaupun Anda masih harus mengedit file untuk membuatnya permanen - Anda ~/.profileatau apa pun - dan itu jauh dari mudah untuk skrip BAHWA edit karena PATH dapat diatur di mana saja di skrip , tidak di baris pertama).

    Mengubah urutan direktori PATH secara signifikan lebih sulit .... dan jauh lebih sulit daripada hanya mengedit #!baris.

    Dan Anda masih memiliki semua masalah lain yang menggunakan #!/usr/bin/envmemberi Anda.

    @jlliagre menyarankan dalam komentar yang #!/usr/bin/envberguna untuk menguji skrip Anda dengan beberapa versi juru bahasa, "dengan hanya mengubah urutan PATH / PATH mereka"

    Jika Anda perlu melakukannya, jauh lebih mudah untuk hanya memiliki beberapa #!baris di bagian atas skrip Anda (mereka hanya komentar di mana saja tetapi baris pertama) dan memotong / menyalin-rekatkan yang ingin Anda gunakan sekarang untuk baris pertama.

    Di vi, itu akan sesederhana memindahkan kursor ke #!baris yang Anda inginkan, lalu mengetik dd1GPatau Y1GP. Bahkan dengan editor yang sepele nano, butuh beberapa detik untuk menggunakan mouse untuk menyalin dan menempel.


Secara keseluruhan, keuntungan menggunakan #!/usr/bin/envminimal, dan tentu saja tidak mendekati kerugiannya. Bahkan keuntungan "kenyamanan" sebagian besar adalah ilusi.

IMO, itu adalah ide konyol yang dipromosikan oleh jenis programmer tertentu yang berpikir bahwa sistem operasi bukanlah sesuatu untuk dikerjakan, mereka adalah masalah yang harus dikerjakan (atau diabaikan sama sekali).

PS: inilah skrip sederhana untuk mengubah penerjemah beberapa file sekaligus.

change-shebang.sh:

#!/bin/bash

interpreter="$1"
shift

if [ -z "$(type -P $interpreter)" ] ; then
  echo "Error: '$interpreter' is not executable." >&2
  exit 1
fi

if [ ! -d "$interpreter" ] && [ -x "$interpreter" ] ; then
  shebang='#!'"$(realpath -e $interpreter)" || exit 1
else
  shebang='#!'"$(type -P $interpreter)"
fi

for f in "$@" ; do
  printf "%s\n" 1 i "$shebang" . w | ed "$f"
done

Jalankan sebagai, misalnya, change-shebang.sh python2.7 *.pyatauchange-shebang.sh $HOME/bin/my-experimental-ruby *.rb

cas
sumber
4
Tidak ada orang lain di sini yang menyebutkan masalah ARGV [0]. Dan tidak ada yang menyebutkan masalah / path / ke / env dalam bentuk yang secara langsung membahas salah satu argumen untuk menggunakannya (yaitu bahwa bash atau perl mungkin berada di lokasi yang tidak terduga).
cas
2
Masalah jalur juru bahasa mudah diperbaiki oleh sysadmin dengan symlinks, atau oleh pengguna yang mengedit skrip mereka. Tentu saja bukan masalah yang cukup signifikan untuk mendorong orang untuk menghadapi semua masalah lain yang disebabkan oleh penggunaan env pada baris shebang yang disebutkan di sini dan pada pertanyaan lain. Itu mempromosikan solusi yang buruk dengan cara yang sama seperti mendorong cshscripting mempromosikan solusi yang buruk: itu bekerja tetapi ada alternatif yang jauh lebih baik.
cas
3
non-sysadmin dapat meminta sysadmin mereka untuk melakukannya. atau mereka cukup mengedit skrip dan mengubah #!baris.
cas
2
Itulah tepatnya yang membantu dihindari oleh env: tergantung pada sysadmin atau pengetahuan teknis khusus OS yang tidak terkait dengan python atau apa pun.
jlliagre
1
Saya berharap saya dapat memperbaiki ini ribuan kali, karena itu sebenarnya jawaban yang bagus - jika seseorang menggunakan /usr/bin/envdan saya harus menyingkirkannya secara lokal, atau jika mereka tidak menggunakannya dan saya perlu menambahkannya. Kedua kasus dapat terjadi, dan karena itu skrip yang disediakan dalam jawaban ini adalah alat yang berpotensi berguna untuk dimiliki dalam kotak alat.
jstine
8

Menambahkan contoh lain di sini:

Penggunaan envjuga berguna ketika Anda ingin berbagi skrip antara beberapa rvmlingkungan, misalnya.

Menjalankan ini pada baris cmd, menunjukkan versi ruby ​​mana yang akan digunakan saat #!/usr/bin/env rubydigunakan di dalam skrip:

env ruby --version

Karena itu, ketika Anda menggunakan env, Anda dapat menggunakan versi ruby ​​yang berbeda melalui rvm, tanpa mengubah skrip Anda.

Tidak sekarang
sumber
1
//, ide bagus. Inti dari beberapa penafsir TIDAK untuk memecahkan kode, atau memiliki kode tergantung pada yang spesifik interpreter .
Nathan Basanese
4

Jika Anda menulis murni untuk diri sendiri atau untuk pekerjaan Anda dan lokasi juru bahasa yang Anda panggil selalu berada di tempat yang sama, tentu saja gunakan jalur langsung. Dalam semua kasus lain gunakan #!/usr/bin/env.

Inilah alasannya: dalam situasi Anda, pythonjuru bahasa berada di tempat yang sama terlepas dari sintaksis mana yang Anda gunakan, tetapi bagi banyak orang itu bisa dipasang di tempat yang berbeda. Meskipun sebagian besar penerjemah pemrograman utama berada di /usr/bin/banyak perangkat lunak yang lebih baru secara default /usr/local/bin/.

Saya bahkan berpendapat untuk selalu menggunakan #!/usr/bin/envkarena jika Anda memiliki beberapa versi dari juru bahasa yang sama diinstal dan Anda tidak tahu apa shell Anda akan default, maka Anda mungkin harus memperbaikinya.

Mauvis Ledford
sumber
5
"Dalam semua kasus lain, gunakan #! / Usr / bin / env" terlalu kuat. // Seperti yang ditunjukkan oleh @KeithThompson dan lainnya, #! / Usr / bin / env berarti skrip dapat berperilaku berbeda tergantung pada siapa / bagaimana menjalankannya. Terkadang perilaku yang berbeda ini bisa menjadi bug keamanan. // Misalnya, JANGAN PERNAH menggunakan "#! / Usr / bin / env python" untuk skrip setuid atau setgid, karena pengguna yang menggunakan skrip dapat menempatkan malware di file yang disebut "python" di PATH. Apakah skrip setuid / gid merupakan ide yang baik adalah masalah yang berbeda - tetapi yang pasti, tidak ada setuid / gid yang dapat dieksekusi yang dapat memercayai lingkungan yang disediakan pengguna.
Krazy Glew
@KrazyGlew, Anda tidak dapat mengatur skrip di Linux. Anda melakukannya melalui executable. Dan ketika menulis executable ini, ini adalah praktik yang baik, dan dilakukan secara luas, untuk menghapus variabel lingkungan. Beberapa variabel lingkungan juga sengaja diabaikan .
MayeulC
3

Untuk alasan portabilitas dan kompatibilitas, lebih baik digunakan

#!/usr/bin/env bash

dari pada

#!/usr/bin/bash

Ada beberapa kemungkinan, di mana biner mungkin berada pada sistem Linux / Unix. Periksa halaman manual hier (7) untuk deskripsi terperinci dari hierarki sistem file.

FreeBSD misalnya menginstal semua perangkat lunak, yang bukan bagian dari sistem dasar, di / usr / local / . Karena bash bukan bagian dari sistem dasar, bash binary diinstal di / usr / local / bin / bash .

Ketika Anda menginginkan skrip bash / csh / perl / portable, yang berjalan di sebagian besar Distribusi Linux dan FreeBSD, Anda harus menggunakan #! / Usr / bin / env .

Juga perhatikan, bahwa sebagian besar instalasi Linux juga (keras) menautkan binary env ke / bin / env atau softlinked / usr / bin ke / bin yang tidak boleh digunakan di shebang. Jadi jangan gunakan #! / Bin / env .

Mirko Steiner
sumber