Created
July 5, 2016 01:15
-
-
Save rachidbch/5985f5fc8230b45c4b516ce1c14f0832 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
On linux, shebang accepts only one argument. | |
As we use nvm we can't adress node executable by path and have to resort to /usr/bin/env. | |
Unfortunately `#!usr/bin/env node --harmony` won't work since it needs 2 arguments ... | |
This [article](http://sambal.org/2014/02/passing-options-node-shebang-line/) proposes a hack. | |
The content of the article is copied below. | |
{ 2014 02 01 } | |
Passing options to node on the shebang (#!) line | |
I was chatting with someone on #node.js who wanted his script to pass a command-line option to node, so that his script was run in a particular node environment. The problem is that under linux you get to pass exactly one argument on the shebang (#!) line. If you use #!/usr/bin/env node, you’ve already used your one argument. When I suggested he use the “-x” hack, we discovered that node didn’t have this hack. So I made a pull request complete with a TL;DR justification for why -x is necessary. | |
Turns out there’s a tidier hack that doesn’t require any changes to node, which relies on the interaction between bash and node. Here’s an example, lifted from pm2 and lightly modified for clarity: | |
#!/bin/sh | |
":" //# comment; exec /usr/bin/env node --noharmony "$0" "$@" | |
console.log('javascript'); | |
Here’s how it works: | |
The #!/bin/sh causes the script to be identified as a shell script, and passed to /bin/sh for execution. /bin/sh reads and executes scripts one line at a time, and we’re taking advantage of that below. | |
The second line, as interpreted by the shell, consists of two commands. | |
2a. The first command is ":", which is the quoted version of the rarely-used bash command :, which means “expand arguments and no-op”. The only argument to : is //, which is a valid path. The following # is a bash comment, which is valid until the command separator ;. | |
2b. The second command is exec /usr/bin/env node --noharmony "$0" "$@" which executes the node interpreter with the desired arguments and passes argument 0 (this script file) and the rest of the arguments to the bash script ("$@") | |
The exec causes the bash process to be replaced by the node process, so bash does not attempt to process any further lines. | |
Now we’re running under node, with the desired command line arguments set. Unlike bash, node wants to read and parse the whole file. So let’s see what node sees: | |
The #!/bin/sh line is ignored due to a special one-off in node – when loading a module, the contents of the first line will be ignored from #! up to the first \n. | |
The second line contains a string constant, the quoted string ":", followed by a Javascript comment introduced with //. Automatic semicolon insertion happens so the constant is interpreted as a string in a statement context. Then the comment is parsed, and everything up to the end of the line is ignored by node. | |
This won’t lint clean. jslint and jshint both complain: | |
$ jslint test | |
test:2:1: Expected an assignment or function call and instead saw an expression. | |
test:2:4: Expected ';' and instead saw 'console'. | |
$ jshint test | |
test: line 2, col 1, Missing semicolon. | |
1 error | |
But it works right now, as a hack-around for the Linux one-argument shebang problem. | |
Note that there’s a spot in the line where you can insert a comment (as long as it doesn’t contain anything that bash interprets, notably ;). What to put there? I recommend a link to a web page (such as the one you’re reading now, http://sambal.org/?p=1014) that explains WTF this weird-looking line is all about. For example: | |
#!/bin/sh | |
":" //# http://sambal.org/?p=1014 ; exec /usr/bin/env node --noharmony "$0" "$@" | |
console.log('javascript'); | |
Happy hacking! | |
Comments: | |
Raphaël Jakse - May 3, 2014 | |
I felt on this post by accident; for fun, I found a way to pass jslint and jshint tests with this hack, for those who might be interested, but this makes the code even more ugly. | |
If you add .charAt(0); just after the second line, jshint and jslint both “see” the ":".charAt(0) expression as a function call (even if it does nothing) and this fixes the missing semicolon at the same time. | |
#!/bin/sh | |
":" //# comment; exec /usr/bin/env node --noharmony "$0" "$@"; | |
.charAt(0); | |
console.log('If you are seeing this in your code, ugliness should sound like a familiar concept to you.'); | |
Note that I had to indent the third line in order to pass jslint tests. | |
Andrei - May 29, 2015 | |
I think this may brake auto-generated cmd file on windows, when module is installed from NPM globally. | |
Alexey - 9 months ago | |
Addtional work to remove ‘\r’ should be added to the script. | |
console.dir(process.argv) shows ‘\r’ adding to last argument passed to the script. If no arguments are passed, there will be one argument (‘\r’) in process.argv. | |
Alexey - replied:9 months ago | |
‘\r’ is added if there is Windows EOL after shebang. | |
Moritz - 2 months ago | |
It’s also possible to work around this problem using awk: | |
#!/usr/bin/awk END {system("node --noharmony " FILENAME)} | |
Explanation: | |
Under Linux everything after the shebang path gets parsed to the program as the first argument. Therefore we can’t use ‘#!/usr/bin/env akw’ and so this probably won’t work on every system. This is also the reason why we are using awk ― it just interprets the first argument. With system we then execute a bash command and open the script with node and parse the arguments. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How's it possible to refactor for Windows? Because Windows doesn't have
/bin/sh
shell.