5

I have this at the top of a script

#!/usr/bin/env mocha

as most people know, this tells the OS which executable to use to execute the script.

However, my question is - how can we include more information to the mocha executable as to how to execute the script?

mocha takes optional arguments, so I would like to do something like this:

#!/usr/bin/env mocha --reporter=tap --output=foo

but I don't think this is allowed.

How can I give the mocha executable more information about how to run the file?

4 Answers4

9

The shebang line is interpreted by the kernel and is not very flexible. On Linux, it's limited to a single argument: the syntax is #!, optional whitespace, path to the interpreter (not containing whitespace), optional whitespace, and optionally a single argument (which may contain whitespace except at the beginning). Furthermore the total size of the shebang line is limited to 128 bytes (BINPRM_BUF_SIZE constant in the kernel sources, used in load_script).

If you want to pass more than one argument, you need a workaround. If you're using #!/usr/bin/env for path expansion, then there's only room for the command name and no other argument.

The most obvious workaround is a wrapper script. Instead of having /path/to/my-script contain the mocha code, you put the mocha code in some other file /path/to/my-script.real and make /path/to/my-script a small shell script. Here's a sample wrapper that assumes that the real code is in a file with the same name as the script, plus .real at the end.

#!/bin/sh
exec mocha --reporter=tap --output=foo "$0.real" "$@"

With a shell wrapper, you can take the opportunity to do more complex things such as define environment variables, look for available interpreter versions, etc.

Using exec before the interpreter ensures that the mocha script will run in the same process as the shell wrapper. Without exec, depending on the shell, it might run as a subprocess, which matters e.g. if you want to send signals to the script.

Sometimes the wrapper script and the actual code can be in the same file, if you manage to write a polyglot — a file that is valid code in two different languages. Writing polyglots is not always easy (or even possible) but it has the advantage of not having to manage and deploy two separate files. Here's a JavaScript/shell polyglot where the shell part executes the JS interpreter on the file (assuming that the JS interpreter ignores the shebang line, there isn't much you can do if it doesn't):

#!/bin/sh
///bin/true; exec mocha --reporter=tap --output=foo "$0" "$@"
… (the rest is the JS code) …
  • 3
    Just as an example of the latter option: the Scala programming language has a "script mode" which is specifically designed for such a thing, in particular, the Scala execution engine ignores anything in between #! and !#, so you can have as complicated a shell / Perl / Python / Ruby script in there as you like to find and start a suitable JVM, locate the Scala installation, figure out appropriate arguments and configuration options for the JVM and the Scala compiler and so on, then just exec whatever command line you cobbled together, and Scala will ignore the script portion. – Jörg W Mittag Mar 25 '17 at 11:59
3

As a follow-up to Gilles' answer, here is a self-contained wrapper. This is a mix of his two solutions. It has the advantages of the polyglot script mentioned by Gilles (one script to maintain and deploy) without its drawbacks (quoting Gilles: "Writing polyglots is not always easy or even possible").

It consists of adding just a few lines at the top of your script:

#!/bin/bash
exec mocha --reporter=tap --output=foo <(sed -n '/^#MOCHA_START#/,$ p' "$0")
#MOCHA_START#
....
....
(mocha script)
....
....

What follows the #MOCHA_START# line is your actual mocha script left untouched. What the three first lines do is extract you mocha script and call mocha with it along with the desired arguments.

However, you may have to force your text editor to interpret the file as a Mocha script and to colorize it accordingly, since it will probably be confused by the first line of your script which indicates this is a Bash script:

enter image description here

xhienne
  • 17,793
  • 2
  • 53
  • 69
  • thanks, upvoted, it may work for some more desperate scenarios, not sure if I can use it though. – Alexander Mills Mar 25 '17 at 00:45
  • 1
    @AlexanderMills There is nothing extreme in this solution. It's akin to the polyglot solution suggested by Gilles. It has its advantages but it's not a polyglot one in the strict sense. – xhienne Mar 25 '17 at 11:22
  • This might work, but it's not pretty; I'd have to save the file as .js to get proper syntax highlighting, but then having the bash call then becomes slightly confusing, and my IDE will visually mangle the bash code - most people's editors will mangle either the bash code or JS code (or whatever language), depending on which file extension you choose. The reason why "it's not pretty" matters, is because I would have to impose this paradigm on the users of my library, which I don't think I want to do. This is not for my code, otherwise I would use this, but instead I need a prettier solution. – Alexander Mills Mar 25 '17 at 18:39
  • 1
    Thanks for your feedback. I have updated the answer to explain the screenshot. This is an interesting side effect. I believe text editors may allow you to force a certain colorization but it may not be a solution for you. Another possible solution would be a deployment script, called by a Makefile or a similar tool, that would add the necessary bash header to your unaltered Mocha scripts. – xhienne Mar 27 '17 at 09:31
1

One of the disadvantages of using #!/usr/bin/env shebang line is that we can't pass arguments.

However, you can pass arguments if you use the path to your interpreter:

#!/path/to/mocha --reporter=tap --output=foo

See also:

0

The only way I currently know how to do this is with env variables, so in my hashbang I have this:

#!/usr/bin/env mocha

...And mocha, or whichever executable, will have to accept environment variables, instead of command line arguments, in order for it to work correctly.

This unfortunately, is very limiting, because many executables do not use env variables, so this makes things very difficult for libraries, which must invoke other libraries, etc.