8

I get the error

/usr/bin/env: zsh -: No such file or directory

...when I run an executable zsh script that starts with the following shebang line:

#!/usr/bin/env zsh -

Also, FWIW, replacing - with -- causes /usr/bin/env to print a similar complaint about zsh --.

I have only seen this error under ubuntu, and only in the context of the shebang hack. Under darwin the same script runs fine. And under ubuntu, running

% /usr/bin/env zsh -

from the command line succeeds. (Actually, "under ubuntu" should be understood as short-hand for "under ubuntu 12.04 LTS and env (GNU coreutils) 8.13".)

My question is: how can I modify the shebang above to avoid this error?

Of course, I know that removing the trailing - will eliminate the error, but this is not an acceptable solution. The rest of this post explains why.

The shebang line that is causing the error comes about as an attempt to comply with two entirely independent guidelines:

  1. To make scripts portable, use #!/usr/bin/env <cmd ...> rather than #!/path/to/cmd <...>.

  2. Putting - as the sole argument to zsh in the shebang line of zsh scripts thwarts certain types of attacks.

So, I can restate my question more precisely as follows: Is it possible to satisfy both of these guidelines without triggering the error shown above under ubuntu?

kjo
  • 15,339
  • 25
  • 73
  • 114
  • 1
    The 'guideline' "To make scripts portable, use #!/usr/bin/env <cmd ...> rather than #!/path/to/cmd <...>." is bad advice. The correct solution to portability is to modify the scripts to contain the correct path (/bin/zsh, /usr/bin/zsh, /usr/local/bin/zsh) at installation time, and there is example code as part of the SUS standard for how to do this. – Random832 May 02 '13 at 17:59
  • 1
    @Random832, can you provide link to SUS standard, never heard of that before. – slm May 02 '13 at 18:03
  • @slm sure - SUS stands for "single unix specification", the old name for the Open Group Base Specification that I linked in my answer. – Random832 May 02 '13 at 18:04
  • @Random832 - thanks. Just learned something new today! – slm May 02 '13 at 18:08
  • What attacks are you refering to? You're invoking whatever program is called zsh in the $PATH anyway. – Gilles 'SO- stop being evil' May 02 '13 at 23:00
  • @Gilles: I've added a link to my post; that link refers to attacks exploiting shebang lines that use #!/bin/sh, but the same attacks, it seems to me, would be possible with #!/bin/zsh. – kjo May 02 '13 at 23:13
  • 1
    @kjo Right. Those attacks are against scripts that run with elevated privileges. You can't usefully use env here anyway because you need to grab the interpreter from a well-known location (it's ok, but not very useful, to get it from $PATH if that is set by sudo). – Gilles 'SO- stop being evil' May 02 '13 at 23:21
  • 1
    Related: http://stackoverflow.com/questions/4303128/how-to-use-multiple-arguments-with-a-shebang-i-e –  May 03 '13 at 00:22

2 Answers2

5

Linux (you mentioned "only under Ubuntu" but the only OS you mentioned it working under was Darwin) does not support passing multiple arguments to a 'shebang' interpreter. It passes the entire string (in your case, "zsh -") as a single argument.

The correct way to ensure your package does not depend on the location of an interpreter is to, as part of the installation process, locate the interpreter and modify the script to include the correct path. Some example code to do this (for sh) is provided at http://pubs.opengroup.org/onlinepubs/9699919799/utilities/sh.html#tag_20_117_16 .

Random832
  • 10,666
5

I think the answer to your question is basically "no". The shebang mechanism just isn't that flexible.

The #! line only lets you specify a command to execute, and (optionally) a single argument to that command. The name of the script is passed as another argument. So if foo.zsh starts with:

#!/usr/bin/env zsh

the running foo.zsh is equivalent to running /usr/bin/env zsh foo.zsh.

If you specified the path to zsh directly, you could pass an additional argument:

#!/usr/bin/zsh -

But with the #!/usr/bin/env hack, zsh is the additional argument.

Apparently this behavior varies from system to system, as you've seen. BTW, I'd check to make sure that the - isn't being ignored on Ubuntu.

If you can rely on zsh being in a consistent location on all the systems you care about, you can use #!/usr/bin/zsh - (ignoring your first guideline).

If you can't, then you can either create a symlink to zsh in a consistent location on each system, or you can modify the #! line in your script as you install it. (I've done this myself for Perl scripts, back in the days when Perl wasn't always /usr/bin/perl.)

Or you can write a wrapper that, when invoked with no arguments, invokes zsh with a - argument, and change your shebang to:

#!/usr/bin/env zsh-wrapper

which only requires zsh-wrapper to be somewhere in $PATH, not necessarily in a consistent location.

See also this question and my answer to it for a discussion of the advantages and drawbacks of the #!/usr/bin/env hack.

And see this page for more information about shebang behavior on various Unix-like systems (thanks to Stephane Chazelas for the link).