I really like Go. I want to use it to write scripts with a shebang the same way I write bash, python and node scripts. The localization of the code with the executable enables a very nice and simple workflow compared to creating a new Go package or module.
$GOPATH/bin
.type binaryname
to figure out where my source code is.This is not a new idea. It has been argued many, many times in the past on this tracker and on the mailing list.
The main argument against this feature has been the performance implications. As summarized by Rob Pike:
Running compilers and linkers, doing megabytes of I/O, and creating temporary
binary files is not a justifiable expense for running a small program. For large
programs, the amortization is even more in favour of not doing this.
I am firmly against adding a feature to Go that encourages abuse of resources.
I think its time to revisit this as the performance argument is not valid anymore with the introduction of the build cache. If the script hasn't changed, it will not be rebuilt on every go run
.
Furthermore with the introduction of Go modules, $GOPATH
is being phased out and it's become idiomatic for Go code to be anywhere on the system. Given Go has become more flexible with project layout in line with other languages, I argue it's natural the same apply to scripts. Rust, node, python, ruby and almost every other modern programming language supports the shebang.
Moreover, with the amount of discussion on the mailing list and this tracker, it's clear there is strong demand for this feature.
https://github.com/erning/gorun enables shebang support now. But for the reasons outlined above, shebangs should become a first class feature.
The concrete changes I propose:
gox
binary with the Go distribution. It would act exactly as go run
but would be the interpreter in the shebang line for scripts. See this gist for why this is necessary.go run
would be that it proxies the exit code instead of consuming it. See https://github.com/golang/go/issues/13440Under the proposed changes, a script hello-world
would look like:
#!/usr/bin/env gox
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
And could be executed with ./hello-world
.
Its unfortunate to break the convention that all Go code is in a file with a .go
extension but I think it's a fair tradeoff for the convenience.
Duplicate of https://github.com/golang/go/issues/24118
@robpike Could you elaborate on why you closed it as a duplicate? I acknowledged its been discussed in the past and explained why it should be revisited.
Could you elaborate on why you closed it as a duplicate?
I think the answer is very obvious. The issue is a duplicate of another issue.
I acknowledged its been discussed in the past and explained why it should be revisited.
Indeed, you acknowledged this in your proposal, and even added an excerpt of Rob Pike’s comment, but disregarded the other parts of the comment that pretty much answer your question of “why was this closed?”. Here is the full comment from March 1, 2012:
I have never said it cannot be done. I have always said it should not be done, and I have explained why.
"Useful" is not an argument for a feature. All features are useful; otherwise they would not be features. The issue is whether the feature justifies its cost. That is a judgement call, and my judgement is, no. Running compilers and linkers, doing megabytes of I/O, and creating temporary binary files is not a justifiable expense for running a small program. For large programs, the amortization is even more in favor of not doing this.
I am firmly against adding a feature to Go that encourages abuse of resources.
If you want the feature, use gorun or an equivalent wrapper program. That's what it's for. I think believe gorun is a mistake, but I'm not stopping you from installing it and using it as you see fit.
-rob
Source: https://groups.google.com/d/msg/golang-nuts/iGHWoUQFHjg/_dbLKomrPmUJ
Do not turn Go into a scripting language, that’s not the way to _go_ 🙂
but disregarded the other parts of the comment that pretty much answer your question of “why was this closed?”. Here is the full comment from March 1, 2012:
I did not do that. There is nothing in his comment that explains why this was closed aside from the performance argument I already mentioned.
I think the answer is very obvious. The issue is a duplicate of another issue.
Yes of course the issue is a duplicate in that it raises the same concern as another issue, but I argued why it should be revisited. Is there something about my argument you find unconvincing?
You might be now aware, but if you go build
or go install
your program, you get an executable binary. What is an executable binary? It is a program that can be run on its own. Let me respond to each of your proposed advantages and tell you what it looks like if you use the approach I described:
I don't have to worry about $GOPATH/bin.
You don't. The executable binary is completely independent.
I can place my scripts wherever I want.
You can place the binary wherever you want. Even on another computer with the same CPU architecture and OS.
I can just use type binaryname to figure out where my source code is.
Okay, you can't do that, but you go to GitHub for source.
There is no install step.
There isn't! Once the binary is compiled, it can be dropped anywhere without any install step.
As you can see, compiling a binary has all the advantages (except the source code) of your approach, and has some more:
Small note @nhooyr - with module support you have a lot of options for "scripting" with just the typical shell. The possibilities are endless, but the first thing to pop into my head is an alias for "GOX" that puts the .go file directly next to the compiled version.
First I'll put an alias in my bashrc:
~$ cat << 'GOX' > gox.sh
> export GOBIN=~/gobin
> export PATH="$PATH:$GOBIN"
> mkdir -p $GOBIN
> gox() {
> printf "package main\n\n" > "${GOBIN}/${1}.go"
> cat >> "${GOBIN}/${1}.go"
> go install "${GOBIN}/${1}.go" && "${1}"
> }
> GOX
~$ . gox.sh # source it to run example below, or add "source ~/gox.sh" to bashrc
Now I can build and run a script:
~$ cat <<GO | gox gobinprinter
> import (
> "fmt"
> "os"
> )
> func main() {
> fmt.Println("STATIC:", "$GOBIN", "RUNTIME:", os.Getenv("GOBIN"))
> }
> GO
STATIC: /home/cstockton/gobin RUNTIME: /home/cstockton/gobin
~$ GOBIN="foobar" gobinprinter
STATIC: /home/cstockton/gobin RUNTIME: foobar
Pretty poor user interface here, but maybe it gives you some ideas for something nicer- like writing a function that exec's a "gox" binary would allow a lot more clever detection of script invocation. Point is that aliases, functions and playing with the current strengths of Go might bring about something useful for you.
There's this project by @crawshaw which looks like it has a lot of promise for scripting (although no updates for a while... is it abandoned?):
https://neugram.io/
Most helpful comment
Duplicate of https://github.com/golang/go/issues/24118