Bourne | Ash |  #!  | find | ARG_MAX | Shells | whatshell | portability | permissions | UUOC | ancient | - | ../Various | HOME
"$@" | echo/printf | set -e | test | tty defs | tty chars | $() vs ) | IFS | using siginfo | nanosleep | line charset | locale


The #! magic, details about the shebang/hash-bang mechanism on various Unix flavours

2001-08-13 .. 2021-10-20 (see recent changes)

Here you'll find


More reading


Selected issues


Test results from various systems

I used the following as program "showargs":

    #include <stdio.h>
    int main(argc, argv)
	int argc; char** argv;
    {
	int i;
	for (i=0; i<argc; i++)
	    fprintf(stdout, "argv[%d]: \"%s\"\n", i, argv[i]);
	return(0);
    } 

and a one line script named "invoker.sh" to call it, similar to this,

    #!/tmp/showargs -1 -2 -3 

to produce the following results (tried them myself, but I'd like to add your results from yet different systems).

Typically, a result from the above would look like this:

    argv[0]: "/tmp/showargs"
    argv[1]: "-1 -2 -3"
    argv[2]: "./invoker.sh"

... but the following table lists the variations. The meaning of the columns is explained below.

OS (arch) maximum
length of
#! line
cut-off (cut),
error (error) or
ENOEXEC
all args in one,
no arguments,
only the 1st arg,
or separate args
handle #
like a
comment
argv[0]:
invoker,
instead of
interpreter
not full
path in
argv[0]
remove
trailing
white-
space
convert
tabulator
to
space
accept
inter-
preter
do not
search
current
directory
no suid
or allow suid
or optional
4.0BSD / 4.1BSD 32 no n/a X n/a suid
386BSD-0.1p2.3 32 no n/a X n/a
4.2BSD 32 ? ? ? ? X suid
4.3BSD 32 c / - [43bsd] X X suid
4.3BSD-Tahoe/Quasijarus 32 X X
AIX 3.2.5/4.3.2 (rs6k) 256 X X
BIG-IP4.2 [big-ip] 4096 err args ? ? X n/a
Dynix 3.2 32 ? ? X ?
EP/IX 2.2.1 (mips) 1024 X suid
FreeBSD 1.1- / 4.0-4.4 64 args - / X X n/a ?
FreeBSD 4.5- 128 err args X X n/a ?
FreeBSD 6.0-8.1 (i386/amd64, ia64/sparc64/alpha) 4096, 8192 cut X X
FreeBSD 8.1 9/2010 (i386/amd64, ia64/sparc64/alpha) 4096, 8192 X X
HP-UX A.08.07/B.09.03 32 X ? ? ?
HP-UX B.10.10 128 X X ? ? ?
HP-UX B.10.20-11.31 128 X X ?
IRIX 4.0.5 (mips) 64 ? ? X X
IRIX 5.3/6.5 (mips) 256 err X suid
Linux 0.10 / 0.12-0.99.1 1022 / 127 [early-linux] [early-linux] X ?
Linux 0.99.2-2.2.26 127 cut X X ?
Linux 2.4.0-2.6.27.8 / 2.6.27.9- 127 cut X   / X
MacOS X 10.0/.1/.2, xnu 123.5-344 512 ? ? X ? ? ?
MacOS X 10.3, xnu 517 512 X ? ? X X ? ? ?
MacOS X 10.4/.5/.6, xnu 792-1504 512 args X X n/a opt
Minix 2.0.3-3.1.1 257 args X n/a X suid
Minix 3.1.8 257 err args X n/a suid
MUNIX 3.1 (svr3.x, 68k) 32 X ? ? ?
NetBSD 0.9 32 cut [netbsd0.9] opt [netbsd0.9]
NetBSD 1.0-1.6Q / 1.6R- 64 / 1024 opt
OpenBSD 2.0-3.4 64 opt
OSF1 V4.0B-T5.1 1024 X X
OpenServer 5.0.6 [sco] 256 err 1st X X
OpenServer 6.0.0: see UnixWare
SINIX 5.20 (mx300/nsc) 32 ? ?
Plan 9 v4 (i386) 30 args X X X n/a ?
SunOS 4.1.4 (sparc) 32 cut X X
SunOS 5.x (sparc) 1024 1st X X suid
SVR4.0 v2.1 (x386) 256 error 1st ? ? X X suid
Ultrix 4.0 (�vax 3900) 31 X X suid
Ultrix 4.5 (�vax3900) 32/31(suid) cut X X suid
Ultrix 4.3 (vax/mips), 4.5 (vax3100) 32 cut X ? ?
Ultrix 4.5 (risc) 80 cut X ? ?
Unicos 9.0.2.2 (cray) 32 X ? ?
UnixWare 7.1.4, OpenServer 6.0.0 [suid] 256 err 1st X X suid
GNU Hurd cvs-20020529, 0.3/Mach1.3.99 [hurd] 4096 cut X X X
UWIN 4.5 (WinXP prof 5.1) [uwin] 512
Cygwin Beta19 (WinXP prof 5.1) [cygwin] 263 cut args X n/a X ?
Cygwin 1.7.7 (WinXP prof 5.1) [cygwin] 32764 err X
Cygwin 1.7.35 (Win7) [cygwin] 65536 err X X
OS (arch) maximum
length of
#! line
cut-off (cut),
error (error) or
ENOEXEC
all args in one,
no arguments,
only the 1st arg,
or separate args
handle #
like a
comment
argv[0]:
invoker,
instead of
interpreter
not full
path in
argv[0]
remove
trailing
white-
space
convert
tabulator
to
space
accept
inter-
preter
do not
search
current
directory
no suid
or allow suid
or optional
Untested, but some information or even source available:
first implementation between Version 7 and 8 (unreleased, see above) 16 no n/a ? X n/a ? suid
Version 8 (aka 8th edition) 32 1st n/a ? X ? ? suid
Demos / "Демос" [Demos] ? ? args ? ? ? ? ? ? ? ?

Meaning of the columns:

A questionmark means that a detail couldn't be tested yet (especially if the column was added later or the system had no compiler).
"n/a" means that the attribute is not relevant in this case.

Footnotes in the table:

[orig]             4.0BSD and 386BSD-0.1 don't hand over any argument at all.
The called interpreter only receives argv[0] with it's own path, argv[1] with the script, and optionally further arguments from the call of the script.

[43bsd]             The code in kern_exec.c tests if the byte after the struct containing the #! line is null. Otherwise it throws an ENOEXEC.
However, reading the line from the file is also limited to 32 bytes, and the following byte (not from the file)
is often zeroed out by coincidence. It then looks as if the line was cut to 32 bytes. But sometimes, you
actually get an ENOEXEC.

[netbsd0.9]             If the line is longer than 32 bytes, it triggers a bug: the scriptname is appended to argv[1] and argv[2] contains an environment variable.
setuid support is a compile time option, however not per Makefile but by activating it in kern_exec.c itself.

[big-ip]             This BIG-IP 4.2 (vendor is F5) is based on BSDi BSD/OS 4.1, probably even with very few modifications:
The tools contain the string "BSD/OS 4.1" and there's also a kernel /bsd-generic, which contains "BSDi BSD/OS 4.1".
I had no compiler available on this system, thus some tests are pending.

[sco] John H. DuBois told me that #! was introduced in SCO UNIX 3.2v4.0, but was disabled by default.
If you wanted to use it, it had to be enabled by setting hashplingenable in kernel/space.c ("hashpling" because
it was implemented by programmers in Britain). It was apparently enabled by default in 3.2v4.2, but even then there
were no #! scripts shipped with the OS as a customer might disable it. The first #! scripts (tcl) were shipped in 3.2v5.0 then.

[early-linux] On linux 0.10 until 0.99.1, argv[0] contains both the interpreter and the arguments: argv[0]: "/tmp/showargs -1 -2 -3"

[hurd] Nesting interpreters this way:
    $ ./script2 -2
    script2: #!/path/script1 -1
    script1: #!/path/showargs -0 
results in
    argv[0]: "/path/showargs"
    argv[1]: "-0"
    argv[2]: "/path/script1"
    argv[3]: "-1"
    argv[4]: "./script2"
    argv[5]: "-2"

[uwin] An example for a valid absolute interpreter path is C:/path/to/interpreter
A path with backslashes or without the drive letter is not accepted.
Home of the UWIN package at AT&T

[cygwin] Valid absolute interpreter paths are for example C:/path/to/interpreter and /path/to/interpreter
Backslashes are not accepted. Nested script are only possible if a drive letter is used
argv[0] becomes a path in windows notation C:\path\to\interpreter

nested #!: argv[0] becomes the command which was called last (path in windows notation),
argv[1] becomes the second last (path in unix notation), and so on.
On Cygwin B19 this worked for me until limits like the following (assuming a trailing null byte for each argument)
844 argv, total length of arguments 33611, length of 39 for each of most of the arguments
631 argv, total length of arguments 33398, length of 52 for each of most of the arguments
about a total length of 37826 and 36553 including 4-byte pointers and null bytes for argv[].
On Cygwin 1.7.7 this worked for me until limits like the following:
538 argv, total length of arguments 33305, length of 63 for each of most of the arguments
683 argv, total length of arguments 33450, length of 48 for each of most of the arguments
about a total length of 35995 and 36865 including 4-byte pointers and null bytes for argv[].

cygwin.com: Web-Git (formerly Web-CVS)
The code is in spawn.cc (formerly spawn.cc).
The involved functions changed from time to time, search for "if (*ptr++ == '#' && *ptr++ == '!')",
originally in spawn_guts(), later also in av::fixup() (v1.180 07/2005) and av::setup() (2013-06-19).
The early limit (Beta19) might be related to CYG_MAX_PATH (260) in cygtls.h (formerly cygtls.h)

On cygwin-1.7.55 the call even can succeed with values greater than 65536, but only occasionally.

[Demos] DEMOS / ДЕМОС was a Soviet variant of 2.9BSD (PDP-11 version), or 4.2 BSD (32bit VAX-version), respectively.
See also the Wikipedia entry and gunkies.org.

Demos recognizes $* as special sequence in the shebang line.
An illustration is contained in the demos source, in sys/sys1.c.
You can control where the arguments to the shebang script (including $0) are incorporated:

    #!CMD A1 $* A2 A3

Demos also knows an alternative magic

    /*#!
for interpreters which use /* as comment instead of #.

Thanks to Random821 for pointing out this special implementation on the THUS list.
Earlier, Jason Stevens also had posted some information about Demos.
Find source for Demos 2.2 here or here.


And why shebang? In music, '#' means sharp. So just shorten #! to sharp-bang. Or it might be derived from "shell bang". All this probably under the influence of the american slang idiom "the whole shebang" (everything, the works, everything involved in what is under consideration). See also the wiktionary, jargon dictionary or Merriam-Websters. Sometimes it's also called hash-bang, pound-bang, sha-bang/shabang, hash-exclam, or hash-pling (british, isn't it?).

According to Dennis M. Ritchie (email answer to Alex North-Keys) it seems it had no name originally.
And Doug McIllroy mentioned in the TUHS mailing list, that the slang for # at Bell Labs most probably was "sharp" at the time.


<http://www.in-ulm.de/~mascheck/various/shebang/>

Sven Mascheck