Peculiarities of shebang

Posted on September 30, 2014
Tags: software, haskell

I have no idea how, but somehow while programming in Haskell, I managed to create files in my current directory named Integer and [Integer]:

-rw-r--r--  1 ppelleti  staff  0 Sep 19 19:44 Integer
-rw-r--r--  1 ppelleti  staff  0 Sep 19 19:44 [Integer]

Actually, now that I think about it, I do have an idea. I had been writing a Haskell script, and put the following at the top:

#!/usr/bin/runhaskell

which always worked fine for me on Linux. But, I’d just recently installed the Haskell Platform on my Mac, and it appears that:

  1. OS X doesn’t allow the interpreter of a script to be another script.

  2. On the OS X install of the Haskell Platform, /usr/bin/runhaskell is a shell script which invokes the “real” binary for runhaskell.

  3. Apparently, when the interpreter for one script is another script, instead of giving an error message like “I’m sorry, Dave,” it simply runs the original script using the default shell.

So, it had tried running my Haskell program as a shell script. Of course, the shell wasn’t very happy with that, and I didn’t think much of it at the time, but that must have been how these weird zero-length files were created.

The solution was just to do:

#!/bin/env runhaskell

I’d always thought the only point of using #!/bin/env was just if you didn’t know the absolute path of the interpreter, but I guess it has the added benefit of being an additional layer of binary you can “sandwich” between one script that’s trying to interpret another script.

Furthermore, it appears that it depends on what shell I’m using. The behavior of running it as a shell script is what happens when I’m using bash. But if I’m using zsh, it correctly follows the double-indirection of scripts and runs the Haskell program. Which is weird, because I thought that #! was handled by exec(), not by the shell.

So, I did a little experiment to see what happens if I’m not running it directly from a shell. I did:

/bin/env ./foo.hs

where foo.hs is my program with the #!/usr/bin/runhaskell line. Run this way, foo.hs always gets run in the shell.

So, it appears that bash is just doing exec(), and OS X’s exec() doesn’t handle double script indirection.

Apparently, zsh must be interpreting the #! itself, rather than just calling exec. I’m not sure if that’s good (zsh is working around an OS X bug, and doing what I want), or bad (further hiding the bug, and making me unaware my script won’t work properly if not run under zsh).

Update: Rereading this post in 2016, I notice that I don’t have a /bin/env on my Mac, only a /usr/bin/env. So I’m not sure if that changed in a new version of Mac OS X somewhere along the way, or if my post was somehow always incorrect.