Универсальный Node.js Шебанг?

42

Node.js очень популярен в наши дни, и я написал для него несколько скриптов. К сожалению, совместимость является проблемой. Официально должен вызываться интерпретатор Node.js node, но nodejsвместо этого Debian и Ubuntu поставляют исполняемый файл .

Мне нужны переносимые скрипты, с которыми Node.js может работать в максимально возможном количестве ситуаций. Предполагая, что имя файла есть foo.js, я действительно хочу, чтобы скрипт выполнялся двумя способами:

  1. ./foo.jsзапускает скрипт, если либо nodeили nodejsнаходится в $PATH.
  2. node foo.jsтакже запускает скрипт (при условии, что вызывается переводчик node)

Примечание . Ответы xavierm02 и меня являются двумя вариантами сценария полиглота. Я все еще заинтересован в чистом решении Шебанга, если таковое существует.

dancek
источник
Я думаю, что нет реального решения этого вопроса, поскольку разрешено произвольно называть исполняемый файл системами сборки. Ничто не мешает назвать имя интерпретатора Python alphacentauri, вы просто следуете соглашениям и называете его Python. Я бы предложил либо использовать стандартное имя nodeдля вашего скрипта, либо использовать скрипт make, который изменяет shebang.
@ G.Kayaalp Помимо политики и соглашений, есть много пользователей Debian / Ubuntu / Fedora, и я хочу сделать сценарии, которые будут работать для них. Я не хочу настраивать систему сборки для этого (кто собирает сценарии оболочки перед их выполнением?), И я не хочу поддерживать alphacentauriи тому подобное. Если есть исполняемый файл nodejs, вы можете быть на 99% уверены, что это Node.js. Почему бы не поддержать и то, nodejsи другое node?
Dancek
Установите пакет nodejs-legacy. Причина, по которой это необходимо, в том, что имя слишком высокомерно, кто-то другой получил имя первым. Этот другой пакет, однако, хотел поделиться своим именем.
user3710044

Ответы:

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 просто оценивает строку ':', которая похожа на no-op, а остальная часть строки анализируется как комментарий.

Остальная часть файла - просто старый javascript. Подоболочка закрывается после завершения execвторой строки, поэтому остальная часть файла никогда не читается оболочкой.

Спасибо xavierm02 за вдохновение и всем комментаторам за дополнительную информацию!

dancek
источник
1
Отличное решение. Альтернативой этому ':'подходу является использование // 2>/dev/null(что и nvmделает): для bash это ошибка ( 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.
Мирабилось
1
Отличное решение; хотя #!/usr/bin/env shвместо этого #!/bin/sh( вместо en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability ) можно было бы использовать даже более переносимый
user7089
2
Я был бы осторожен с рекламой #!/usr/bin/env shкак с «более портативной». В той же статье в Википедии написано: «Это в основном работает, потому что путь / usr / bin / env обычно используется для утилиты env ...» Это не замечательное подтверждение, и я предполагаю, что вы будете работать с системами без /usr/bin/envболее частого Вы сталкиваетесь с системами без /bin/sh, если есть какая-либо разница в частоте вообще.
Шелдон
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'с кавычками вместо кавычек запускает узел и <<кормит его тем, что находится справа. Я мог бы использовать разделитель, начинающийся с //того же, что и dancek, он бы сработал, но я нахожу более чистым, чтобы в начале было только две строки, поэтому я имел обыкновение tail -n +2 $0читать сам файл, кроме первых двух строк.

И если вы запускаете его в узле, первая строка распознается как шебанг и игнорируется, а вторая - как однострочный комментарий.

(Очевидно, sed можно использовать для замены содержимого файла tail tail без первой и последней строк )


Ответ перед редактированием:

#!/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');это пример скрипта, который вы должны заменить своим скриптом.

xavierm02
источник
объяснение было бы неплохо
0xC0000022L
Лучше процитировать документ-здесь ограничитель для расширения параметров предотвращения, подстановки команд и арифметического расширения , которые должны выполняться на коде JavaScript перед выполнением: <<'__HERE__'.
manatwork
Это становится интересным! Хотя //bin/falseэто не работает в моей среде MSYS, и мне нужны кавычки вокруг обратных галочек, так как мой nodeнаходится по адресу C:\Program Files\.... Да, я работаю в ужасной обстановке ...
dancek
1
//bin/falseна Mac OS X тоже не работает. Извините, но это больше не кажется переносимым.
Dancek
2
Команда whichтакже не является переносимой.
Иордания
9

Это проблема только в системах на основе Debian, где политика преодолела смысл.

Я не знаю, когда Fedora предоставила двоичный файл с именем nodejs, но я никогда не видел его. Пакет называется nodejs и устанавливает двоичный файл с именем node.

Просто используйте символическую ссылку, чтобы применить здравый смысл к системам, основанным на Debian, и тогда вы сможете использовать разумный шебанг. В любом случае, другие люди будут использовать вменяемые шебанги, поэтому вам понадобится эта символическая ссылка.

#!/usr/bin/env node

console.log("Spread the love.");
sheldonh
источник
Извините, но это не отвечает на вопрос. Я понимаю политическую точку зрения, но здесь дело не в этом.
dancek
Напомним, что в некоторых системах Fedora есть nodejsисполняемый файл , но это не вина Fedora. Извините за искажение факта.
Dancek
8
Не нужно извиняться. Ты совершенно прав. Мой ответ не отвечает на ваш вопрос. Это говорит о вашем вопросе, предполагая, что вопрос ограничивает область применения менее полезными решениями, чем доступные. Я предлагаю практические советы, основанные на истории. Узел не первый переводчик, который оказался здесь. Вы бы видели волнение, которое привело к тому, что Ларри Уолл объявил, что Perl Shebang ДОЛЖЕН быть #!/usr/bin/perl. Тем не менее, вы не обязаны любить или применять мои предложения. Мир.
Шелдон
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, могут быть запущены с //аргументом.
Dancek
1

Я понимаю, что это не отвечает на вопрос, но я убежден, что вопрос задается с ложной предпосылкой.

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

Например, даже npmпакет, предоставленный Ubuntu, не переписывает shebangs в пакетах:

$ 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"
Бинки
источник