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?

Gilles 'SO- stop being evil'
  • 807,993
  • 194
  • 1,674
  • 2,175
kawty
  • 743
  • 5
  • 7
  • `//&>/dev/null;x="${0%.*}";[ ! "$x" -ot "$0" ]||(rm -f "$x";cc -o "$x" "$0")&&exec "$x" "$@"` [`...`](http://stackoverflow.com/a/12907/1114) – Jeremy Oct 16 '14 at 20:23
  • 2
    following @g-man and Jörg comments below, and according to gilles answer ( http://unix.stackexchange.com/a/1919/27616 ), this trick should use `///....` instead of `//...` to be the most compatible! – Olivier Dulac Oct 17 '14 at 04:51
  • 1
    This won't correctly handle arguments (or location in a directory) with spaces without more quotes: `go run "$0" "$@"` – Charles Duffy Oct 18 '14 at 19:12
  • See also: [Stack Overflow: What's the appropriate Go shebang line?](https://stackoverflow.com/q/7707178/4561887) – Gabriel Staples Feb 18 '23 at 17:54

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,584
  • 5
  • 45
  • 62
  • 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
    It's handled by the kernel, not the shell; see Gilles's answer to [How Linux handles multiple path separators (/home////username///file)](http://unix.stackexchange.com/a/1919/80216). – G-Man Says 'Reinstate Monica' Oct 16 '14 at 16:18
  • 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](https://stackoverflow.com/a/30082862/4561887). – 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,387
  • 14
  • 20
  • 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