Core Go Commands
Core Go Commands
The go command is the whole toolchain: run, build, test, format, vet, document, list, env, clean. Learn these nine well and you can ignore the rest for a long time.
Mental model
You type go <verb> in a module directory. The verb looks at go.mod, the .go files in the package, and (for tests) the *_test.go files. Output is deterministic enough to script. None of these verbs need an IDE.
go run is for trying a program. go build is for keeping a binary. go test is how you know the program still works. gofmt / go fmt are how the team shares one layout. go vet catches mistakes the compiler allows. go doc and go list answer questions. go env prints the machine. go clean removes build debris.
Worked examples
Case 1: go run and go build
Save as open.go in a module (go mod init desk if you do not have one):
// open.go
package main
import "fmt"
func main() {
fmt.Println("desk open")
}Run:
go run .Output:
desk open
Build and run the binary:
go build -o desk .
./deskOutput:
desk open
go run throws the executable away. go build -o desk . keeps it. Ship the build, not the run.
Case 2: go test
Two files in the same package. hours.go is the program. hours_test.go is the test. go test compiles them together.
Save as hours.go:
// hours.go
package main
import "fmt"
func openHours() string {
return "09:00-17:00"
}
func main() {
fmt.Println(openHours())
}Save as hours_test.go:
// hours_test.go
package main
import "testing"
func TestOpenHours(t *testing.T) {
got := openHours()
want := "09:00-17:00"
if got != want {
t.Fatalf("openHours() = %q, want %q", got, want)
}
}Run:
go testOutput:
PASS
ok desk 0.001s
The module path in the ok line matches your go.mod. The time varies. FAIL plus a Fatalf message means the strings did not match. go test does not run main.
Case 3: go fmt and gofmt
gofmt is the formatter. go fmt is a thin wrapper that runs it on packages. Save this as messy.go (it is deliberately ugly, and it still compiles):
// messy.go
package main
import "fmt"
func main() {
fmt.Println("aligned")
}The listing above is already formatted. To see the tool work, type a messy copy yourself: extra spaces around Println, tabs mixed with spaces, braces on their own strange lines. Then:
gofmt -w messy.go-w writes the file in place. Without -w, gofmt prints the formatted source to stdout. go fmt ./... formats every package under the current directory. There is no style debate. The output of gofmt is the style.
Case 4: go vet
Save as badprint.go. The compiler accepts it. go vet does not.
// badprint.go
package main
import "fmt"
func main() {
ticket := "12"
fmt.Printf("ticket %d\n", ticket)
}Run:
go vet badprint.goOutput:
badprint.go:8:21: fmt.Printf format %d has arg ticket of wrong type string
Run it anyway:
go run badprint.goOutput:
ticket %!d(string=12)
The program “works.” The line is garbage. Change %d to %s (or ticket to an int) and go vet is silent. Case 5 of the next chapter writes the fix in full.
Case 5: go doc, go list, go env, go clean
Documentation for a stdlib function:
go doc fmt.PrintlnOutput starts with:
package fmt // import "fmt"
func Println(a ...any) (n int, err error)
Println formats using the default formats for its operands and writes to
standard output.
List the package and the module:
go list .
go list -mOutput (module name from go.mod):
desk
desk
Environment:
go env GOVERSION GOMOD GOCACHETypical output:
go1.27.0
/home/you/desk/go.mod
/home/you/.cache/go-build
Clean the default binary name after go build (directory desk, no -o):
go build
go cleango clean removes the executable go build wrote in this directory. It does not delete your .go files. go clean -cache wipes the build cache — leave that alone unless a cache bug is the problem.
The trap
Using go run as the production start command, or wrapping every verb in a Makefile before you can recite them. This program is three lines. The trap is the process around it.
// serve.go
package main
import "fmt"
func main() {
fmt.Println("would listen on :8080")
}Run:
go run serve.goOutput:
would listen on :8080
In a container or a service unit, run the binary from go build (or go install). go run rebuilds from source every time, hides compile errors behind a convenience wrapper, and is not how you pin what you shipped.
The other trap: skipping go vet because the compiler was green. Case 4 is the exhibit.
The boring rule
go run .while learning.go build -o <name> .when you keep a binary.go testin the package directory. Tests are*_test.go.gofmt -w(orgo fmt ./...) on every save. No personal layout.go vet ./...before you call the change done.go doc,go list,go envinstead of guessing paths.go cleanfor leftover binaries. Not-cacheas a ritual.
Try this
- In the
deskmodule, rungo build -o desk .thengo run .. Confirm both print the same line. - Break
TestOpenHoursby changingwant. Rungo testand read theFatalfline. Fix it. - Run
go doc -all fmtand findPrintf. Thengo list -f '{{.GoFiles}}' .in a package that has more than one.gofile.