Universal Node.js shebang?


42

Node.js дуже популярний в наші дні, і я писав на ньому кілька сценаріїв. На жаль, сумісність є проблемою. Офіційно передбачається викликати інтерпретатора Node.js node, але Debian та Ubuntu поставляють виконуваний файл nodejs.

Я хочу переносити сценарії, з якими може працювати Node.js у будь-яких ситуаціях. Якщо припустити, що ім'я файлу є foo.js, я дуже хочу, щоб сценарій запускався двома способами:

  1. ./foo.jsзапускає сценарій, якщо він nodeабо nodejsє $PATH.
  2. node foo.jsтакож запускає скрипт (якщо припустити, що інтерпретатор викликається node)

Примітка . Відповіді xavierm02 та я - це два варіанти поліглотового сценарію. Мені все ж цікаво чисте рішення шебанга, якщо таке існує.


Я думаю, що для цього немає реального рішення, оскільки системам побудови дозволено називати виконуваний файл. Ніщо не заважає іменувати інтерпретатора python alphacentauri, ви просто слідуєте умовам і називаєте його python. Я б запропонував або використовувати стандартне ім'я nodeдля свого сценарію, або мати якийсь сценарій make make, який модифікує shebang.

@ G.Kayaalp Політика та конвенції осторонь, є багато користувачів Debian / Ubuntu / Fedora, і я хочу зробити сценарії, які працюють для них. Я не хочу налаштувати для цього систему складання (хто будує сценарії оболонки перед їх запуском?), А також не хочу підтримувати alphacentauriі таке. Якщо є виконаний файл, який називається nodejs, ви можете бути на 99% впевнені, що це Node.js. Чому б не підтримати і те, nodejsі node?
танцювач

Встановіть застарілий пакет nodejs. Причина цього потрібна в тому, що ім'я занадто зарозуміло загальне, хтось інший отримав ім'я першим. Цей інший пакет, однак, готовий поділитися назвою.
користувач3710044

Відповіді:


54

Найкраще, що я придумав - це "дворядковий шебанг", який насправді є поліглотом (Bourne shell / Node.js):

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

console.log('Hello world!');

Перший рядок - це, очевидно, шеббанг Борна. Node.js обходить будь-який шебанг, який він знайде, тому це дійсний файл JavaScript, що стосується Node.js.

Другий рядок викликає оболонку no-op :з аргументом, //а потім виконує nodejsабо nodeз ім'ям цього файлу як параметр. command -vвикористовується замість whichпортативності. Синтаксис підстановки команд $(...)не є суто Bourne, тому вибирайте зворотні посилання, якщо ви запускаєте це в 1980-х.

Node.js просто оцінює рядок ':', який є як неоперативний, а решта рядка аналізується як коментар.

Решта файлу - це просто звичайний JavaScript. Підзаголовок закривається після завершення execдругого рядка, тому оболонка файлу ніколи не читається.

Дякуємо xavierm02 за натхнення та всім коментаторам за додаткову інформацію!


1
Прекрасне рішення. Альтернативою ':'підходу є використання // 2>/dev/null(що і nvmробить): башти, це помилка ( bash: //: Is a directory), яку перенаправлення 2>/dev/nullмовчки ігнорує; до JavaScript, весь рядок стає коментарем. Крім того, - і я не очікую, що це буде проблемою IRL - commandмає вигадку, де він також повідомляє про функції оболонки та псевдоніми, -v- хоча він насправді не викликає їх. Таким чином, якщо у вас випадково експортовані функції оболонки або навіть псевдоніми (припускаючи shopt -s expand_aliases) названі nodeабо nodejs, речі можуть зламатися.
mklement0

1
Ну, POSIX sh є всюдисущим (окрім Solaris / bin / sh - але Solaris дійсно поставляється з AT&T ksh з коробки, що сумісне з POSIX), і Маркус Кун рекомендує (з обґрунтуванням) залишатися поза ним. ІМХО вам це ніколи не потрібно. Але так, це досить mksh, bash, dashта інших оболонок в сучасних системах Unix і GNU.
mirabilos

1
Прекрасне рішення; хоча ще більш портативним було б використовувати #!/usr/bin/env shзамість #!/bin/sh(per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability )
user7089

2
Я б насторожився щодо реклами #!/usr/bin/env shяк "більш портативної". Ця ж стаття у Вікіпедії говорить: "Це здебільшого працює, тому що шлях / usr / bin / env зазвичай використовується для утиліти env ..." Це не чудове схвалення, і я гадаю, що ви будете працювати в системах не /usr/bin/envчастіше, ніж Ви працюєте в системах без /bin/shбудь-якої різниці в частоті.
sheldonh

1
@dancek Привіт з 2018 року, чи не буде //bin/sh -c :; exec ...чистішою версією другого рядка?
har-wradim

10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/falseте саме, що за /bin/falseвинятком того, що другий косої риски перетворює його в коментар для вузла, і саме тому він тут. Потім права частина першого ||оцінюється. 'which node || which nodejs'з зворотними котируваннями замість лапок запускає вузол і <<подає його все, що праворуч. Я міг би використовувати роздільник, починаючи з //танцю, як це робив, він би спрацював, але мені здається, що на початку є лише два рядки, тому я часто tail -n +2 $0читав сам файл, крім перших двох рядків.

І якщо запустити його у вузлі, перший рядок розпізнається як шебанг та ігнорується, а другий - однорядковий коментар.

(Мабуть, sed можна було б використовувати для заміни вмісту файлу хвостового друку без першого та останнього рядків )


Відповідь перед редагуванням:

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

Ви не можете робити те, що хочете, тому те, що ви робите замість цього, це запуск сценарію оболонки, отже #!/bin/sh. Цей скрипт оболонки отримає шлях до файлу, необхідного для виконання вузла, тобто which node || which nodejs. Зворотні котирування тут знаходяться так, що вони виконуються, тому 'which node || which nodejs'(із зворотними котируваннями замість лапок) просто викликає вузол. Потім ви просто подаєте свій сценарій до нього <<. __HERE__Є роздільниками твого сценарію. І console.log('ok');це приклад сценарію, який слід замінити своїм сценарієм.


пояснення було б добре
0xC0000022L

Краще процитувати документ-тут обмежувач для розширення параметрів запобігання, підстановки команд і арифметичного розширення , які повинні виконуватися на коді JavaScript перед виконанням: <<'__HERE__'.
манатура

Це стає цікавим! Хоча //bin/falseце не працює в моєму середовищі MSYS, і мені потрібні цитати на задніх підставах, коли мій nodeмешкає C:\Program Files\.... Так, я працюю в жахливому оточенні ...
танцюю

1
//bin/falseне працює і на Mac OS X. Вибачте, але це вже не здається дуже портативним.
танець

2
whichКоманда теж не переноситься.
Йорданм

9

Це лише проблема в системах на базі Debian, де політика долає сенс.

Я не знаю, коли Fedora надав двійковий файл під назвою nodejs, але я його ніколи не бачив. Пакет називається nodejs, і він встановлює двійковий код, який називається node.

Просто скористайтеся символьним посиланням, щоб застосувати здоровий глузд до ваших систем на базі Debian, і тоді ви можете використовувати розумний шебанг. Інші люди все одно будуть використовувати розумні шебанги, тож вам знадобиться це посилання.

#!/usr/bin/env node

console.log("Spread the love.");

Вибачте, але це не відповідає на питання. Я розумію політичну точку зору, але тут справа не в цьому.
танцювати

Для запису деякі системи Fedora мають nodejsвиконуваний файл , але це не є виною Fedora. Вибачте за неправильне представлення факту.
танцювати

8
Не потрібно вибачатися. Ви абсолютно праві. Моя відповідь не відповідає на ваше запитання. Він відповідає на ваше запитання, припускаючи, що питання обмежує обсяг менш корисних рішень, ніж є. Я пропоную практичні поради, поінформовані історією. Вузол - не перший перекладач, який опинився тут. Ви, мабуть, бачили суєту, яка призвела до того, що Ларрі Уолл заявив, що ПЕРЕЛО ШЕБАГ ПОВИНЕН бути #!/usr/bin/perl. При цьому ви не зобов’язані подобатися чи застосовувати мої пропозиції. Мир.
sheldonh

3

Якщо ви не заперечуєте створити маленький .shфайл, я маю невелике рішення для вас. Ви можете створити невеликий скрипт оболонки, щоб визначити, який саме вузол виконується, і використовувати цей скрипт у своєму шебангу:

shebang.sh :

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

script.js :

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

Позначте і виконуваний файл, і запустіть ./script.js.

Таким чином, ви уникаєте поліглот сценаріїв. Я не думаю, що використання декількох рядів шебанг можливе, хоча це здається гарною ідеєю.

Хоча це вирішує проблему так, як ви хочете, здається, що ніхто про це не переймається. Наприклад, uglifyjs та coffeescript використовує #!/usr/bin/env node, npm використовує скрипт оболонки як точку входу, яка знову викликає виконуваний файл явно з ім'ям node. Я користувач Ubuntu, і я цього не знав, оскільки завжди збираю вузол. Я розглядаю це як про помилку.


Я впевнений, що вам принаймні доведеться запитати користувача chmod +xна скрипт sh ... так що ви можете також попросити їх встановити змінну, яка надає розташування їх виконуваному вузлу ...
xavierm02

@ xavierm02 Можна випустити сценарій chmod +x'd. І я погоджуюся, що змінна NODE краща за which node || which nodejs. Але запитуючий прагне забезпечити нестандартний досвід, хоча багато основних проектів вузлів просто використовують #!/usr/bin/env node.

1

Для повноти тут є кілька інших способів виконання другого рядка:

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

Але жодна перевага щодо обраної відповіді не має:

':' //; || echo yes

Крім того, якщо ви знали, що буде знайдено nodeабо nodejs( або не обидва), такі дії:

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

Але це велике "якщо", тому я думаю, що обрана відповідь залишається найкращою.


Крім того, ви можете запускати будь-який foobar // 2>/dev/null, доки foobarце не команда. І багато утиліт, знайдених у будь-якій системі POSIX, можна запустити //аргументом.
танцювач

1

Я розумію, що це не відповідає на питання, але я переконаний, що питання задається з помилковою передумовою.

Тут Ubuntu помиляється. Написання універсального шебангу для власного сценарію не змінить інші пакунки, у яких ви не маєте контролю над тим, де використовується стандарт де-факто #!/usr/bin/env node. Ваша система повинна забезпечення nodeв , PATHякщо він бажає для будь-яких сценаріїв , орієнтованих на nodejs , щоб працювати на ньому.

Наприклад, навіть наданий Ubuntu npmпакет не переписує шебанги в пакети:

$ cd
$ rm -rf test_npm
$ mkdir test_npm
$ cd test_npm
$ npm install mkdirp 2>&1 | grep -ve home
`-- mkdirp@0.5.1
  `-- minimist@0.0.8

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"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.