60

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?

  • //&>/dev/null;x="${0%.*}";[ ! "$x" -ot "$0" ]||(rm -f "$x";cc -o "$x" "$0")&&exec "$x" "$@" ... – Jeremy Oct 16 '14 at 20:23
  • 2
    following @g-man and Jörg comments below, and according to gilles answer ( unix.stackexchange.com/a/1919/27616 ), this trick should use ///.... instead of //... to be the most compatible! – Olivier Dulac Oct 17 '14 at 4: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
71

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.

  • 10
    It's handled by the kernel, not the shell; see Gilles's answer to How Linux handles multiple path separators (/home////username///file). – G-Man 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 <slash> characters, the first component following the leading <slash> characters may be interpreted in an implementation-defined manner, although more than two leading <slash> characters shall be treated as a single <slash> character." – Jörg W Mittag Oct 17 '14 at 4:20
  • 11
    So, to make it portable one should instead write ///usr/bin/env go run $0 $@ ; exit... – Ruslan Oct 17 '14 at 6:56
  • 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
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 '/'

  • 9
    "The // is just ignored in paths" – That's not guaranteed: "If a pathname begins with two successive <slash> characters, the first component following the leading <slash> characters may be interpreted in an implementation-defined manner" (pubs.opengroup.org/onlinepubs/9699919799/basedefs/…) – Jörg W Mittag Oct 17 '14 at 4:24
  • Very interesting, updated answer. – gena2x Oct 17 '14 at 9: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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.