Universal Node.js shebang?

42

Node.js sangat populer hari ini dan saya telah menulis beberapa skrip di atasnya. Sayangnya, kompatibilitas adalah masalah. Secara resmi, interpreter Node.js seharusnya dipanggil node, tetapi Debian dan Ubuntu mengirim eksekutabel yang dipanggil nodejs.

Saya ingin skrip portabel yang dapat digunakan Node.js dalam situasi sebanyak mungkin. Dengan asumsi nama file adalah foo.js, saya benar-benar ingin skrip berjalan dalam dua cara:

  1. ./foo.jsmenjalankan skrip jika ada nodeatau nodejsada di $PATH.
  2. node foo.jsjuga menjalankan skrip (dengan asumsi penerjemah disebut node)

Catatan: Jawaban oleh xavierm02 dan saya sendiri adalah dua variasi skrip polyglot. Saya masih tertarik dengan solusi shebang murni, jika ada.

dancek
sumber
Saya pikir tidak ada solusi nyata untuk ini, karena orang diizinkan untuk menamai executable secara arbiter oleh sistem build. Tidak ada yang mencegah Anda menamai interpreter python alphacentauri, Anda cukup mengikuti konvensi dan menamainya python. Saya sarankan menggunakan nama standar nodeuntuk skrip Anda, atau memiliki semacam skrip make yang memodifikasi shebang.
Di samping G.Kayaalp Politik dan konvensi, ada banyak pengguna Debian / Ubuntu / Fedora, dan saya ingin membuat skrip yang berfungsi untuk mereka. Saya tidak ingin mengatur sistem build untuk ini (siapa yang membuat skrip shell sebelum menjalankannya?), Saya juga tidak ingin mendukung alphacentauridan semacamnya. Jika ada executable yang dipanggil nodejs, Anda bisa 99% yakin itu Node.js. Mengapa tidak mendukung keduanya nodejsdan node?
dancek
Instal paket nodejs-legacy. Alasan ini diperlukan adalah bahwa nama itu terlalu arogan generik, orang lain mendapatkan nama itu terlebih dahulu. Namun, paket lain itu rela membagikan namanya.
user3710044

Jawaban:

54

Yang terbaik yang saya hasilkan adalah skrip "dua baris" yang benar-benar adalah skrip polyglot (Bourne shell / Node.js):

#!/bin/sh
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@"

console.log('Hello world!');

Baris pertama adalah, jelas, shell Bourne shebang. Node.js memintas setiap shebang yang ditemukannya, jadi ini adalah file javascript yang valid sejauh menyangkut Node.js.

Baris kedua memanggil no-op shell :dengan argumen //dan kemudian mengeksekusi nodejsatau nodedengan nama file ini sebagai parameter. command -vdigunakan bukan whichuntuk portabilitas. Sintaks substitusi perintah $(...)tidak sepenuhnya Bourne, jadi pilihlah backticks jika Anda menjalankan ini pada 1980-an.

Node.js hanya mengevaluasi string ':', yang seperti no-op, dan sisa baris diuraikan sebagai komentar.

Sisa file hanyalah javascript lama. Subshell berhenti setelah execbaris kedua selesai, sehingga sisa file tidak pernah dibaca oleh shell.

Terima kasih kepada xavierm02 untuk inspirasi dan semua komentator untuk informasi tambahan!

dancek
sumber
1
Solusi bagus Alternatif dari ':'pendekatan ini adalah dengan menggunakan // 2>/dev/null(yang nvmartinya): untuk mem-bash, ini adalah kesalahan ( bash: //: Is a directory), yang diabaikan oleh pengalihan 2>/dev/null; ke JavaScript, seluruh baris menjadi komentar. Juga - dan saya tidak berharap ini menjadi masalah IRL - commandmemiliki kekhasan di mana ia juga melaporkan fungsi dan alias shell -v- meskipun itu tidak benar-benar memanggil mereka. Dengan demikian, jika Anda telah mengekspor fungsi shell atau bahkan alias (dengan asumsi shopt -s expand_aliases) bernama nodeatau nodejs, semuanya dapat rusak.
mklement0
1
Nah, POSIX sh ada di mana-mana hari ini (kecuali Solaris / bin / sh - tetapi Solaris memang datang dengan AT&T ksh di luar kotak, yang kompatibel dengan POSIX), dan Markus Kuhn merekomendasikan (dengan justifikasi) untuk tidak menggunakannya. IMHO Anda tidak pernah membutuhkannya. Tapi ya, itu cukup untuk mksh, bash, dashdan kerang lainnya di zaman modern Unix dan GNU sistem.
mirabilos
1
Solusi bagus; meskipun lebih portabel akan menggunakan #!/usr/bin/env shalih-alih #!/bin/sh(per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089
2
Saya akan berhati-hati tentang periklanan #!/usr/bin/env shsebagai "lebih portabel". Artikel Wikipedia yang sama mengatakan, "Ini sebagian besar bekerja karena path / usr / bin / env umumnya digunakan untuk utilitas env ..." Itu bukan dukungan yang luar biasa, dan tebakan saya adalah bahwa Anda akan menjalankan sistem tanpa /usr/bin/envlebih sering daripada Anda mengalami sistem tanpa /bin/sh, jika ada perbedaan frekuensi sama sekali.
sheldonh
1
@dancek Hai mulai 2018, bukankah ini //bin/sh -c :; exec ...versi yang lebih bersih dari baris kedua?
har-wradim
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseadalah hal yang sama dengan /bin/falsekecuali bahwa garis miring kedua mengubahnya menjadi komentar untuk simpul, dan itulah sebabnya ada di sini. Kemudian, sisi kanan yang pertama ||dievaluasi. 'which node || which nodejs'dengan backquotes alih-alih tanda kutip meluncurkan node dan <<feed itu apa pun yang ada di sebelah kanan. Saya bisa menggunakan pembatas dimulai dengan //seperti dancek lakukan, itu akan berhasil tetapi saya merasa lebih bersih untuk memiliki hanya dua baris di awal jadi saya dulu tail -n +2 $0membuat file membaca sendiri kecuali dua baris pertama.

Dan jika Anda menjalankannya dalam simpul, baris pertama dikenali sebagai shebang dan diabaikan dan yang kedua adalah komentar satu baris.

(Rupanya, sed dapat digunakan untuk mengganti konten file Cetak ekor tanpa baris pertama dan terakhir )


Jawab sebelum edit:

#!/bin/sh
`which node || which nodejs` <<__HERE__
console.log('ok');
__HERE__

Anda tidak dapat melakukan apa yang Anda inginkan sehingga yang Anda lakukan adalah menjalankan skrip shell, karenanya #!/bin/sh. Script shell itu akan mendapatkan path file yang diperlukan untuk mengeksekusi node, yaitu which node || which nodejs. Backquotes ada di sini sehingga dieksekusi, jadi 'which node || which nodejs'(dengan backquotes bukan tanda kutip) cukup memanggil node. Kemudian, Anda cukup memberi makan skrip Anda dengannya <<. The __HERE__adalah pembatas script Anda. Dan console.log('ok');ini adalah contoh skrip yang harus Anda ganti dengan skrip Anda.

xavierm02
sumber
sebuah penjelasan akan menyenangkan
0xC0000022L
Lebih baik mengutip pembatas di sini-dokumen untuk ekspansi parameter menghindari, substitusi perintah dan ekspansi aritmatika yang akan dilakukan pada kode JavaScript sebelum eksekusi: <<'__HERE__'.
manatwork
Ini semakin menarik! Meskipun //bin/falsetidak berfungsi di lingkungan MSYS saya, dan saya perlu tanda kutip di sekitar backticks ketika saya nodeberada di C:\Program Files\.... Ya, saya bekerja di lingkungan yang mengerikan ...
dancek
1
//bin/falsejuga tidak berfungsi di Mac OS X. Maaf, tapi ini sepertinya tidak lagi portabel.
dancek
2
The whichperintah juga tidak portabel.
jordanm
9

Ini hanya masalah pada sistem berbasis Debian, di mana kebijakan mengatasi akal sehat.

Saya tidak tahu kapan Fedora menyediakan biner yang disebut nodejs, tapi saya tidak pernah melihatnya. Paket ini disebut nodejs dan menginstal biner yang disebut simpul.

Cukup gunakan symlink untuk menerapkan akal sehat ke sistem berbasis Debian Anda, dan kemudian Anda dapat menggunakan shebang yang waras. Orang lain akan menggunakan shebang waras, jadi Anda akan memerlukan symlink itu.

#!/usr/bin/env node

console.log("Spread the love.");
sheldonh
sumber
Maaf, tapi ini tidak menjawab pertanyaan. Saya mengerti sudut pandang politik, tetapi itu bukan poin di sini.
dancek
Sebagai catatan, beberapa sistem Fedora memang memiliki nodejsexecutable , tetapi itu bukan kesalahan Fedora. Maaf telah salah menggambarkan fakta.
dancek
8
Tidak perlu minta maaf. Anda benar. Tanggapan saya tidak menjawab pertanyaan Anda. Itu berbicara untuk pertanyaan Anda, menunjukkan bahwa pertanyaan membatasi ruang lingkup untuk solusi yang kurang bermanfaat daripada yang tersedia. Saya menawarkan saran praktis yang diinformasikan oleh sejarah. Node bukan penerjemah pertama yang menemukan dirinya di sini. Anda seharusnya melihat keributan yang menyebabkan Larry Wall menyatakan bahwa perl shebang HARUS #!/usr/bin/perl. Karena itu, Anda tidak wajib menyukai atau menerapkan saran saya. Perdamaian.
sheldonh
3

Jika Anda tidak keberatan membuat .shfile kecil , saya punya sedikit solusi untuk Anda. Anda bisa membuat skrip shell kecil untuk menentukan simpul mana yang dapat dieksekusi untuk digunakan, dan menggunakan skrip ini di shebang Anda:

shebang.sh :

#!/bin/sh
`which node || which nodejs` $@

script.js :

#!./shebang.sh
console.log('hello');

Tandai keduanya executable, dan jalankan ./script.js.

Dengan cara ini, Anda menghindari skrip polyglot. Saya tidak berpikir menggunakan beberapa garis shebang mungkin dilakukan, meskipun sepertinya ide yang bagus.

Meskipun ini menyelesaikan masalah seperti yang Anda inginkan, tampaknya tidak ada yang peduli tentang ini. Misalnya, uglifyjs dan CoffeeScript penggunaan #!/usr/bin/env node, NPM menggunakan script shell sebagai pintu masuk, yang lagi-lagi menyebut dieksekusi secara eksplisit dengan nama node. Saya adalah pengguna Ubuntu, dan saya tidak tahu ini karena saya selalu mengkompilasi simpul. Saya sedang mempertimbangkan untuk melaporkan ini sebagai bug.


sumber
Saya cukup yakin Anda setidaknya harus meminta pengguna untuk chmod +xmenggunakan skrip sh ... jadi Anda mungkin juga meminta mereka untuk menetapkan variabel yang memberikan lokasi ke node yang dapat dieksekusi ...
xavierm02
@ xavierm02 Seseorang dapat merilis skrip chmod +x'd. Dan saya setuju bahwa variabel NODE lebih baik daripada which node || which nodejs. Tetapi penanya ingin memberikan pengalaman out-of-the-box, meskipun banyak proyek simpul utama hanya menggunakan #!/usr/bin/env node.
1

Hanya untuk melengkapi, berikut adalah beberapa cara lain untuk melakukan baris kedua:

// 2>/dev/null || echo yes
false //|| echo yes

Tetapi tidak ada yang memiliki keunggulan atas jawaban yang dipilih:

':' //; || echo yes

Juga, jika Anda tahu bahwa salah satu nodeatau nodejs(tetapi tidak keduanya) akan ditemukan, berikut ini berfungsi:

exec `command -v node nodejs` "$0" "$@"

Tapi itu "jika" besar, jadi saya pikir jawaban yang dipilih tetap yang terbaik.

Dave Mason
sumber
Selain itu, Anda bisa menjalankannya foobar // 2>/dev/nullselama foobarbukan perintah. Dan banyak utilitas yang ditemukan pada sistem POSIX dapat dijalankan dengan //argumen.
dancek
1

Saya mengerti bahwa ini tidak menjawab pertanyaan, tetapi saya yakin bahwa pertanyaan itu diajukan dengan premis yang salah.

Ubuntu salah di sini. Menulis universal shebang untuk skrip Anda sendiri tidak akan mengubah paket lain yang tidak dapat Anda kendalikan yang menggunakan standar de-facto #!/usr/bin/env node. Sistem Anda harus memberikan nodedi PATHjika ingin untuk script menargetkan nodejs untuk berjalan di atasnya.

Misalnya, bahkan npmpaket yang disediakan Ubuntu tidak menulis ulang paket dalam paket:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- [email protected]
  `-- [email protected]

npm WARN test_npm No description
npm WARN test_npm No repository field.
npm WARN test_npm No README data
npm WARN test_npm No license field.
$ node_modules/.bin/mkdirp testdir
/usr/bin/env: 'node': No such file or directory
$ head -n1 node_modules/.bin/mkdirp
#!/usr/bin/env node
$ npm --version
3.5.2
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
binki
sumber