64

I'm confused about following script (hello.go).

//usr/bin/env go run $0 $@ ; exit

package main
import "fmt"
func main() {
    fmt.Printf("hello, world\n")
}

It can execute. (on MacOS X 10.9.5)

$ chmod +x hello.go
$ ./hello.go
hello, world

I haven't heard about shebang starting with //. And it still working when I insert a blank line at the top of the script. Why does this script work?

kawty
  • 743

3 Answers3

74

It isn't a shebang, it is just a script that gets run by the default shell. The shell executes the first line

//usr/bin/env go run $0 $@ ; exit 

which causes go to be invoked with the name of this file, so the result is that this file is run as a go script and then the shell exits without looking at the rest of the file.

But why start with // instead of just / or a proper shebang #! ?

This is because the file need to be a valid go script, or go will complain. In go, the characters // denote a comment, so go sees the first line as a comment and does not attempt to interpret it. The character # however, does not denote a comment, so a normal shebang would result in an error when go interprets the file.

This reason for the syntax is just to build a file that is both a shell script and a go script without one stepping on the other.

casey
  • 14,754
  • Nice. I didn't know that shells don't care about the number of slashes in a path. Bug or feature? I'm surprised that ///bin//ls and ls ~//.///.ssh/ runs fine. –  Oct 16 '14 at 16:03
  • 10
  • 3
    @HermanTorjussen Feature - the synax of paths is pretty well defined, allowing lots of useful variants - and with power comes complexity: / as path suffix is defined as /.; When a is not a symlink, a is the same as a/ which is the same as a/. Thera are cases where a path can get an additional / with no change in meaning. When deriving a canonical path, there is a normalisation step contracting consecutive slashes to one. Granted, it's not a clean part of the formal syntax, though. – Volker Siegel Oct 16 '14 at 16:56
  • 13
    Actually, POSIX says that multiple slashes are the same as a single slash except when there are exactly two slashes exactly at the very beginning of the path. As is the case here. In that case, the interpretation of the path is implementation-dependent: "If a pathname begins with two successive characters, the first component following the leading characters may be interpreted in an implementation-defined manner, although more than two leading characters shall be treated as a single character." – Jörg W Mittag Oct 17 '14 at 04:20
  • 12
    So, to make it portable one should instead write ///usr/bin/env go run $0 $@ ; exit... – Ruslan Oct 17 '14 at 06:56
  • @casey how the line "hello, world" is getting printed if the shell exits after the first line? – Geek Dec 18 '14 at 12:19
  • 1
    @geek the shell exits but not before launching the go interpreter. Go is printing hello world, not the shell. – casey Dec 18 '14 at 12:42
  • Apparently a triple backslash in front is "more standards compliant". See here. – Gabriel Staples Feb 18 '23 at 17:55
8

It runs because by default executable file is assumed to be /bin/sh script. I.e. if you didn't specify any particular shell - it is #!/bin/sh.

The // is just ignored in paths - you can consider is at as single '/'.

So you can consider that you have shell script with first line:

/usr/bin/env go run $0 $@ ; exit

What does this line do? It runs 'env' with paramenters 'go run $0 $@'. there 'go' is command and 'run $0 $@' are args and exits script afterwards. $0 is this script name. $@ are original script arguments. So this line runs go which runs this script with it's arguments

There are quite interesting details, as pointed in comments, that two slashes are implementation-defined, and this script would become POSIX-correct if it specify three or more slashes. Refer to http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html for details on how slashes should be handled in paths.

Note also that there is another mistake in script the $@ it's correct to use "$@" instead, because otherwise if any parameter contains spaces it will be split to many parameters. For example you can't pass file name with spaces if you not using the "$@"

This particular script obviously rely on the idea that '//' is equal to '/'

gena2x
  • 2,397
  • 9
    "The // is just ignored in paths" – That's not guaranteed: "If a pathname begins with two successive characters, the first component following the leading characters may be interpreted in an implementation-defined manner" (http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html) – Jörg W Mittag Oct 17 '14 at 04:24
  • Very interesting, updated answer. – gena2x Oct 17 '14 at 09:33
  • 1
    ...AFS in particular implemented // differently, but it's not common anymore. – Charles Duffy Oct 18 '14 at 19:13
0

This will work for C++ (and C if that C allows // for comments)

//usr/bin/env sh -c 'p=$(expr '"_$0"' : "_\(.*\)\.[^.]*"); make $p > /dev/null && $p'; exit