50

Node.js is very popular these days and I've been writing some scripts on it. Unfortunately, compatibility is a problem. Officially, the Node.js interpreter is supposed to be called node, but Debian and Ubuntu ship an executable called nodejs instead.

I want portable scripts that Node.js can work with in as many situations as possible. Assuming the filename is foo.js, I really want the script to run in two ways:

  1. ./foo.js runs the script if either node or nodejs is in $PATH.
  2. node foo.js also runs the script (assuming the interpreter is called node)

Note: The answers by xavierm02 and myself are two variations of a polyglot script. I'm still interested in a pure shebang solution, if such exists.

3
  • I think there is no real solution to this, as one is allowed to name the executable arbitarily by the build systems. There is nothing that keeps you from naming the python interpreter alphacentauri, you just follow the conventions and name it python. I'd suggest either using standard name node for your script, or having a kind of make script that modifies the shebang.
    – user27225
    Feb 20, 2013 at 12:17
  • @G.Kayaalp Politics and conventions aside, there are a lot of Debian/Ubuntu/Fedora users, and I want to make scripts that work for them. I don't want to setup a build system for this (whoever builds shell scripts before running them?), nor do I want to support alphacentauri and such. If there's an executable called nodejs, you can be 99% sure it's Node.js. Why not support both nodejs and node? Feb 20, 2013 at 13:28
  • Install the nodejs-legacy package. The reason this is needed is that the name is too arrogantly generic, someone else got the name first. That other package was, however, willing to share the name. May 27, 2018 at 11:16

6 Answers 6

66

The best I have come up with is this "two-line shebang" that really is a polyglot (Bourne shell / Node.js) script:

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

console.log('Hello world!');

The first line is, obviously, a Bourne shell shebang. Node.js bypasses any shebang that it finds, so this is a valid javascript file as far as Node.js is concerned.

The second line calls the shell no-op : with the argument // and then executes nodejs or node with the name of this file as parameter. command -v is used instead of which for portability. The command substitution syntax $(...) isn't strictly Bourne, so opt for backticks if you run this in the 1980s.

Node.js just evaluates the string ':', which is like a no-op, and the rest of the line is parsed as a comment.

The rest of the file is just plain old javascript. The subshell quits after the exec on second line is completed, so the rest of the file is never read by the shell.

Thanks to xavierm02 for inspiration and all the commenters for additional information!

11
  • 1
    Great solution. An alternative to the ':' approach is to use // 2>/dev/null (which is what nvm does): to bash, it's an error (bash: //: Is a directory), which the redirection 2>/dev/null quietly ignores; to JavaScript, the entire line becomes a comment. Also - and I don't expect this to be a problem IRL - command has a quirk where it also reports shell functions and aliases with -v - even though it wouldn't actually invoke them. Thus, if you happen to have exported shell functions or even aliases (assuming shopt -s expand_aliases) named node or nodejs, things could break.
    – mklement0
    Dec 17, 2013 at 17:35
  • 1
    Well, POSIX sh is ubiquitous these days (except Solaris /bin/sh – but Solaris does come with AT&T ksh out of the box, which is POSIX compatible), and Markus Kuhn recommends (with justification) to stay off it. IMHO you never need it. But yes, it's enough for mksh, bash, dash and other shells in modern Unix and GNU systems.
    – mirabilos
    Dec 29, 2013 at 20:50
  • 1
    Great solution; though even more portable would be to use #!/usr/bin/env sh instead of #!/bin/sh (per en.wikipedia.org/wiki/Shebang_%28Unix%29#Portability)
    – user7089
    Feb 7, 2014 at 20:35
  • 3
    I would be careful about advertising #!/usr/bin/env sh as "more portable". That same Wikipedia article says "This mostly works because the path /usr/bin/env is commonly used for the env utility..." That's not a wonderful endorsement, and my guess is that you'll run into systems without /usr/bin/env more frequently than you run into systems without /bin/sh, if there's any difference in frequency at all.
    – sheldonh
    Apr 10, 2015 at 11:52
  • 1
    @dancek Hi from 2018, wouldn't be //bin/sh -c :; exec ... a cleaner version of the second line?
    – alephreish
    Jan 27, 2018 at 17:43
17

This is only a problem on Debian-based systems, where policy overcame sense.

I don't know when Fedora provided a binary called nodejs, but I never saw it. The package is called nodejs and it installs a binary called node.

Just use a symlink to apply common sense to your Debian-based systems, and then you can use a sane shebang. Other people will be using sane shebangs anyway, so you're going to need that symlink.

#!/usr/bin/env node

console.log("Spread the love.");
3
  • 1
    I'm sorry, but this does not answer the question. I do understand the political point of view, but that is not the point here. Aug 5, 2013 at 8:58
  • For the record, some Fedora systems do have a nodejs executable, but that is not Fedora's fault. Sorry for misrepresenting the fact. Aug 5, 2013 at 9:01
  • 11
    No need to apologise. You're quite right. My response doesn't answer your quesiton. It speaks to your question, suggesting that the question limits scope to less useful solutions than are available. I'm offering practical advice informed by history. Node is not the first interpreter to find itself here. You should have seen the commotion that led to Larry Wall declaring that the perl shebang MUST be #!/usr/bin/perl. That said, you aren't obliged to like or apply my suggestions. Peace.
    – sheldonh
    Aug 6, 2013 at 9:14
10
#!/bin/sh
//bin/false || `which node || which nodejs` << `tail -n +2 $0`
console.log('ok');

//bin/false is the same thing as /bin/false except that the second slash transforms it into a comment for node, and that's why it's here. Then, the right side of the first || gets evaluated. 'which node || which nodejs' with backquotes instead of quotes launches node and the << feeds it whatever is on the right. I could've used a delimiter starting with // like dancek did, it would've worked but I find it cleaner to have only two lines at the beginning so I used tail -n +2 $0 to have the file read itself except the first two lines.

And if you run it in node, the first line is recognized as a shebang and ignored and the second is a one-line comment.

(Apparently, sed could be used to replace tail Print file content without the first and last lines)


Answer before edit:

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

You can't do what you want so what you do instead is run a shell script, hence the #!/bin/sh. That shell script will get the path of the file needed to execute node, that is which node || which nodejs. The backquotes are here so that it gets executed, so 'which node || which nodejs' (with the backquotes instead of the quotes) simply calls node. Then, you just feed your script to it with <<. The __HERE__ are the delimiters of yours script. And the console.log('ok'); is an example of script that you should replace with your script.

8
  • an explanation would be nice Feb 18, 2013 at 22:14
  • Better quote the here-document delimiter to avoid parameter expansion, command substitution and arithmetic expansion to be performed on the JavaScript code before execution: <<'__HERE__'.
    – manatwork
    Feb 19, 2013 at 9:02
  • This is getting interesting! Although //bin/false doesn't work in my MSYS environment, and I need quotes around the backticks as my node resides at C:\Program Files\.... Yes, I work in a horrible environment... Feb 19, 2013 at 14:14
  • 1
    //bin/false doesn't work on Mac OS X, either. I'm sorry, but this doesn't seem very portable anymore. Feb 20, 2013 at 8:09
  • 2
    The which command is also not portable.
    – jordanm
    Feb 20, 2013 at 14:26
4

If you won't mind creating a little .sh file, I've a little solution for you. You can create a little shell script to determine which node executable to use, and use this script in your shebang:

shebang.sh:

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

script.js:

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

Mark both executable, and run ./script.js.

This way, you avoid polyglot scripting. I do not think using multiple shebang lines are possible, although it seems like a good idea.

Although this solves the problem the way you want, it seems that no-one cares about this. For example, uglifyjs and coffeescript uses #!/usr/bin/env node, npm uses a shell script as an entry point, which again calls the executable explicitly with name node. I'm an Ubuntu user, and I didn't know this as I always compile node. I'm considering to report this as a bug.

2
  • I'm quite sure you'd at least have to ask the user to chmod +x on the sh script... so you might as well ask them to set a variable giving the location to their node executable...
    – xavierm02
    Feb 20, 2013 at 14:57
  • @xavierm02 One can release the script chmod +x'd. And I concur that a NODE variable is better than which node || which nodejs. But the inquirer wants to provide an out-of-the-box experience, although many major node projects just use #!/usr/bin/env node.
    – user27225
    Feb 20, 2013 at 15:44
1

Just for completeness here are a couple of other ways of doing the second line:

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

But neither has any advantage over the selected answer:

':' //; || echo yes

Also, if you knew that either node or nodejs (but not both) would be found, the following works:

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

But that's a big "if", so I think the selected answer remains the best one.

1
1

I understand that this does not answer the question, but I am convinced that the question is asked with a false premise.

Ubuntu is in the wrong here. Writing a universal shebang for your own script will not change other packages you have no control over which use the de-facto standard of #!/usr/bin/env node. Your system must provide node in PATH if it wishes for any scripts targeting nodejs to run on it.

For example, not even the Ubuntu-provided npm package rewrites shebangs in packages:

$ 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"

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .