The second shebang line is passed to the interpreter, and is not a valid comment in javascript.
test.sh:
#! /usr/bin/env nix-shell
#! nix-shell -i node -p nodejs
console.log("oh no")
$ ./test.sh
/Users/nick/Source/test.sh:2
#! nix-shell -i node -p nodejs
^
SyntaxError: Invalid or unexpected token
at createScript (vm.js:80:10)
at Object.runInThisContext (vm.js:139:10)
at Module._compile (module.js:617:28)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
at Function.Module.runMain (module.js:694:10)
at startup (bootstrap_node.js:204:16)
at bootstrap_node.js:625:3
I was surprised that this was the case: obviously neither shebang is part of the script, I wouldn't expect either to be part of the input to the other interpreter.
Unfortunately (and we'll see why later,) the shebang is always passed to the interpreter. For example, here the script uses the interpreter cat:
$ ./test.sh
#!cat
hello!
$ cat ./test.sh
#!cat
hello!
In fact, if we use the binary echo as the interpreter, the nature of how shebangs work is revealed to us:
$ cat ./test.sh
#!echo
hello!
$ ./test.sh
./test.sh
When using a shebang, the name of the file containing the shebang is the first argument to the interpreter specified by the shebang. So when test.sh uses the shebang echo, the program echo ./test.sh is called. When test.sh used cat, cat ./test.sh is executed.
Now, the mystery to resolve is: How can this possibly be true when nodejs can be used in a shebang, but doesn't support the # comment character?
Well, the answer is a bit disappointing, and, indeed, bad news for Nix users like you and I: they special case it.
In other words, I'm pretty sure there is nothing we can do to solve this.
_(edited to remove an Actually.)_
Sure, but as far as the system is concerned, isn't the interpreter nix-shell? Why can't it choose what gets passed to the "real" interpreter?
And doesn't it currently do some weird special-case things for ruby and perl?
The interpreter _is_ nix-shell, you're right. And, it does indeed special-case ruby and perl... but neither of those include modifying the file being interpreted by ruby or perl. Nix just goes ahead and re-exec's the intended interpreter + the file with the shebang.
In order for Nix to strip the shebang, it would need to either alter the file on disk (not really a viable option) or duplicate the file to a new temporary file, minus the #!nix-shell lines. This second option isn't viable, either, because many scripts depend upon the their own name or location on disk.
Ah, that last sentence, I hadn't really thought about that. Many scripts, perhaps, but many nix-shell scripts?...but no, either way, how could we universally make the change backwards compatible for all languages? I'm not even sure if it's possible in any.
Multiline shebangs aren't common, but are they outright non-standard? Would it be completely crazy to try to get a change merged to node to support them? If it's going to be a special case in _someone's_ codebase it might as while be there.
Yeah, basically multi-line shebangs are completely a nix invention. Maybe we could convince them to add them, though? Not sure :)
I can't think of a way to address the problem with changes to nix, so I'll close this issue. Thank you for giving an explanation not only to me, but to the next person who tries to open a similar one.
@nicknovitski would it not be possible to change nix-shell to accept alternative second-line comment types, and use whatever comment type nodejs will accept on the second line? (// I guess?)
@grahamc @nicknovitski how about a --exec-temporary-stripped-copy flag to nix-shell that lets people opt into the copied semantics? If I'm writing the nix-shell shebang'd script and pass in that option, I should know that my filename won't stay the same. Basically, Nix can't assume that semantics won't change, but it can assume that if the author of the script says it's fine, it's indeed fine.
Or as @Shados says (sorry, I misunderstood earlier if you saw an earlier comment of mine), we could just expand what Nix accepts on the second line. To be as friendly as possible, if nix-shell is called as the interpreter, we could just strip \W+ from the beginning of the next line. That would cover all comment characters that I know of.
You mean like \W.+? \W+ would strip the #! but then leave nix-shell for the interpreter to choke on. Unfortunately // is out because it is a Nix operator. Invalid Nix, but Nix none the less. \W is out because <?php for PHP, and { for bash scripts. The truth is, we can't know that the second lines' #!nix-shell is not semantically part of the script.
PS: nix-shell supports #!nix-shell lines anywhere in the file. For example, this runs bash and prints hello world to the screen:
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p hello
print "hello"
print "world"
This one is deceptive, it runs python and prints hello world to the screen:
#!/usr/bin/env nix-shell
#!nix-shell -i bash -p hello
print "hello"
#!nix-shell -p python2 -i python2
print "world"
I only just put two-and-two together, but check this out:
$ cat test.sh
#! /usr/bin/env nix-shell
/*
#! nix-shell -i node -p nodejs
*/
console.log("oh yes")
$ ./test.sh
oh yes
Note: In the past I considered this behavior a _bug_, but seeing that it could be used to solve this problem, maybe it should be considered a feature?
Another follow-up since @shlevy is laughing at me :) the reason I said we might should consider it a feature, is we should write a test for the behavior and ensure it continues working. Until now I considered it to mostly just accidentally work. If we're going to say, "the way to use a nix-shell line with nodejs is wrap it in a comment" then we need to make sure that doesn't change later.
@grahamc This is really interesting, I hope this will be considered as a feature and be maintained :smile:
I was fighting this problem with Erlang. Solved with:
#!/usr/bin/env nix-shell
-define(NOT_MY_SHEBANG, <<"
#!nix-shell -i escript -p erlang
">>).
馃槷
$ cat nix-shell-node
#! /usr/bin/env nix-shell
`
#! nix-shell --packages nodejs-10_x -i node
`
console.log('oh my god')
$ nix-shell-node
oh my god
I assume this would work with any language with multiline strings, heredocs, etc.
Most helpful comment
You mean like
\W.+?\W+would strip the#!but then leave nix-shell for the interpreter to choke on. Unfortunately//is out because it is a Nix operator. Invalid Nix, but Nix none the less.\Wis out because<?phpfor PHP, and{for bash scripts. The truth is, we can't know that the second lines' #!nix-shell is not semantically part of the script.PS: nix-shell supports #!nix-shell lines anywhere in the file. For example, this runs bash and prints hello world to the screen:
This one is deceptive, it runs python and prints hello world to the screen:
I only just put two-and-two together, but check this out: