Format Go Code Safely with gofmt
gofmt can print a diff, list mismatched files, or rewrite your source in place, and the wrong pick turns a quick style check into an unplanned edit. This guide builds a small, repeatable workflow: check the diff, keep a backup before any overwrite, and keep formatting separate from optional rewrites. Allow about ten minutes for a first pass.
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, a readable Go source tree and the gofmt command from the Debian golang-go package. No step needs elevated privileges. Do not use sudo to format code in a project you do not own: root can create root-owned files and hide an ordinary permission problem instead of fixing it.
1. Confirm the installed formatter
Check the command and package version before relying on its exact behaviour. This is read-only:
$ command -v gofmt
/home/linuxbrew/.linuxbrew/bin/gofmt
$ gofmt -h | sed -n '1,12p'
usage: gofmt [flags] [path ...]
-cpuprofile string
write cpu profile to this file
-d display diffs instead of rewriting files
-e report all errors (not just the first 10 on different lines)
-l list files whose formatting differs
-r string
rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
-s simplify code
-w write result to (source) file instead of stdout
$ dpkg-query -W -f='${Package} ${Version}\n' golang-go:amd64
golang-go:amd64 2:1.22~2build1
The path printed by command -v is the binary you are actually invoking. The Debian package version identifies the installed distribution package, while the command itself does not offer a --version flag on this installation. Keep that distinction in mind when comparing output between machines.
Checkpoint
You have confirmed the executable and recorded the local package version.
2. Inspect a file without changing it
By default, gofmt writes the reformatted source to standard output. That makes this a safe first check, but redirecting it carelessly can still overwrite a file, so start with a diff instead. Create a disposable example if you do not already have a Go file to inspect:
$ tmpdir=$(mktemp -d)
$ printf '%s\n' 'package main' '' 'import "fmt"' '' 'func main(){fmt.Println("hello")}' > "$tmpdir/main.go"
$ gofmt -d "$tmpdir/main.go"
diff "$tmpdir/main.go" gofmt/main.go
--- gofmt/main.go
+++ gofmt/main.go
@@
-func main(){fmt.Println("hello")}
+func main() { fmt.Println("hello") }
The exact diff header includes a temporary path, and gofmt's output uses tabs for indentation, which may display as varying spacing in a terminal. The important result is that the file itself is still unchanged. Remove the disposable directory after the experiment with rm -rf "$tmpdir"; it holds nothing but the file you just made.
For a real project, replace the temporary path with a known file:
$ gofmt -d -- ./path/to/file.go
The -- marks the end of options. It is useful when a path comes from a script, although a path beginning with a hyphen is best rejected outright rather than guessed at.
3. Check a tree without rewriting it
Use -l in a pre-commit check or before reviewing a branch. It prints the names of files whose formatting differs, and prints nothing when they are already formatted:
$ gofmt -l -- ./path/to/package
./path/to/package/main.go
./path/to/package/worker.go
$ gofmt -l -- ./path/to/package | tee /tmp/gofmt-files.txt
$ test ! -s /tmp/gofmt-files.txt && echo 'all Go files are formatted'
all Go files are formatted
A directory is searched recursively for .go files, but files whose names start with a period are ignored. The command does not understand your build graph, does not run tests, and does not prove the program is correct. It only reports formatting differences.
Checkpoint
Use -l when you need a yes-or-no answer, and -d when you need to review the actual edits.
4. Apply ordinary formatting after reviewing the diff
Once the diff is what you expect, use -w to update the source file in place:
$ cp --preserve=all ./path/to/file.go ./path/to/file.go.bak
$ gofmt -w -- ./path/to/file.go
$ gofmt -d -- ./path/to/file.go
$ test ! -s <(gofmt -l -- ./path/to/file.go) && echo 'file is formatted'
file is formatted
The backup is an explicit recovery point. If the result is wrong for your workflow, restore it with cp --preserve=all ./path/to/file.go.bak ./path/to/file.go. Do not remove the backup until you have reviewed the complete change and, ideally, run the project's tests. Deleting it with rm is irreversible.
Tip
-w normally restores the original file if an error occurs while overwriting, but it is still worth keeping your own backup when a change matters. Formatting can alter whitespace throughout a file, so inspect the diff rather than assuming a clean exit means a small diff.
5. Format standard input and fragments
With no path, gofmt reads standard input and prints formatted text. This is useful in a pipeline and does not touch a source file:
$ printf '%s\n' 'func main(){println("hello")}' | gofmt
func main() {
println("hello")
}
Standard input can be a full Go program or a syntactically valid declaration list, statement list or expression, for example:
$ printf '%s\n' 'x:=1+2' | gofmt
x := 1 + 2
Leading indentation and leading or trailing spaces have special preservation rules for fragments. If a pipeline's output is going back into a larger file, verify the surrounding context rather than treating the fragment as a complete program.
6. Use simplification and rewrites deliberately
The -s option asks gofmt to simplify code after applying any rewrite rule. In the installed tool, that includes forms such as s[a:len(s)] becoming s[a:], and a loop that discards both range values becoming for range v.
Warning
The manual warns that simplification can produce code incompatible with earlier Go versions, so review and test the resulting diff:
$ gofmt -s -d -- ./path/to/file.go
$ gofmt -s -w -- ./path/to/file.go
The -r option applies a rule in the form pattern -> replacement. Lowercase single-character identifiers in the pattern act as wildcards:
$ gofmt -r '(a) -> a' -l -- ./path/to/package
$ gofmt -r '(a) -> a' -w -- ./path/to/package
Do not combine an unreviewed rewrite with a broad tree and an automatic commit. Run it first with -l or -d on a small, known set, then test the result: a rewrite is a source change, not merely presentation.
7. Diagnose failures without guessing
For malformed input, use -e when you need all available errors rather than the first ten reported on different lines:
$ gofmt -e -- ./path/to/file.go
$ printf 'exit status: %s\n' "$?"
exit status: 0
Replace the example path with the file that failed. A non-zero status means gofmt could not complete its requested operation. Check the path, read permission and source syntax first:
$ test -r ./path/to/file.go && echo readable
$ gofmt -d -- ./path/to/file.go
Do not fix an access problem by running as root unless the project owner and ownership change are both understood. If a generated file is repeatedly reported, find the generator or build step that writes it; formatting the generated output may only hide the real source of the difference.
Done means
- Version confirmed: you confirmed the installed gofmt binary and Debian package version.
- Diff checked first: you used
-dor-lbefore changing any source. - Backup kept: you kept a recoverable backup for an in-place change.
- Rewrites treated as real changes: you treated
-sand-ras source transformations that need testing. - Formatting verified: the final
gofmt -lcheck reports no files needing formatting.