Run small, standalone Go scripts without creating a module or repository.
Supports importing any Go module automatically — no go.mod required.
Run Go snippets directly — just pass the code as an argument:
goscript 'fmt.Println("hello")'The last expression in your snippet is automatically printed — no fmt.Println needed:
goscript '"hello " + "world"' # prints: hello world
goscript 'os.Getenv("HOME")' # prints: /home/you
goscript 'time.Now().Year()' # prints: 2025Explicit fmt.Print* calls are detected and left untouched (no double-printing).
Write a single executable file with a shebang and run it like a script.
#!/usr/bin/env goscript
package main
import (
"fmt"
"os"
"github.com/pkg/errors" // external deps installed automatically
)
func main() {
fmt.Println("args:", os.Args[1:])
fmt.Println(errors.New("boom"))
}chmod +x myscript
./myscript a b c
# Compile to a standalone binary
goscript -o mybin myscript
./mybin a b c
# Graduate script to a proper Go module
goscript -m myproject/ myscriptScripts must use package main and define func main() — it's real Go, not a DSL.
When your snippet references any of these magic variables, pipe mode is activated automatically — no flags required:
| Variable | Type | Value |
|---|---|---|
x / line |
string |
current input line |
i / idx |
int |
line index (0-based) |
f / fields |
[]string |
whitespace-split fields of x |
lines |
[]string |
all lines (read before the loop) |
Reference x or line → loops over stdin line by line:
ls | goscript 'strings.ToUpper(x)'
ps aux | goscript 'f[0]' # first field of each line
cat data.csv | goscript 'f[1]' -F ',' # CSV second column
cat file.txt | goscript 'fmt.Sprintf("%3d %s", i, x)' # add line numbersReference lines → reads all stdin first, then runs your code once:
wc -l < file.txt # shell way
cat file.txt | goscript 'len(lines)' # Go way, auto-printed
# Sort lines
cat file.txt | goscript 'sort.Strings(lines); strings.Join(lines, "\n")'
# Count unique words
cat file.txt | goscript '
counts := map[string]int{}
for _, l := range lines { counts[l]++ }
pp(counts)
'-F sep changes how f/fields is split. Default is whitespace (like strings.Fields).
cat data.csv | goscript 'f[2]' -F ',' # third CSV column
cat /etc/passwd | goscript 'f[0]' -F ':' # usernames-j N runs the loop body across N goroutines (output order not guaranteed):
cat urls.txt | goscript 'fetch(x)' -j 20
# Square numbers in parallel
seq 100 | goscript 'atoi(x) * atoi(x)' -j 8-r pattern compiles a regex and activates pipe mode. Only lines matching the pattern
are processed (non-matching lines are skipped). Inside the loop:
| Variable/func | Type | Description |
|---|---|---|
r |
*regexp.Regexp |
the compiled regex |
m |
[]string |
submatches: m[0]=full match, m[1+]=capture groups |
n |
map[string]string |
named capture groups |
sub(repl, s) |
string |
replace first match (supports $1, $2, ${name}) |
suball(repl, s) |
string |
replace all matches |
# filter to matching lines only
cat file | goscript -r '\d+' 'x'
# extract first capture group
cat file | goscript -r '(\d+)' 'm[1]'
# named capture groups
cat /etc/passwd | goscript -r '(?P<user>[^:]+):.*:(?P<shell>[^:]+)$' \
'n["user"] + " → " + n["shell"]'
# replace first match
cat file | goscript -r '(\w+)@(\w+)' 'sub("$1 at $2", x)'
# replace all matches
cat file | goscript -r '\d+' 'suball("NUM", x)'
# use r directly for full regexp API
cat file | goscript -r '\w+' 'r.ReplaceAllStringFunc(x, strings.ToUpper)'Every inline/pipe/batch script gets these functions for free:
| Function | Description |
|---|---|
die(err) |
Print error and exit 1. No-op if err is nil. |
must(v, err) |
Generic unwrap: n := must(strconv.Atoi(s)) |
atoi(s) |
Parse int, exit on failure. |
atof(s) |
Parse float64, exit on failure. |
pp(v) string |
Format any value as indented JSON. Auto-print friendly. |
trim(s) |
strings.TrimSpace(s) shortcut. |
splitlines(s) |
Split a multi-line string into []string. |
match(pat, s) |
[]string of submatches, nil if no match. Cached. |
named(pat, s) |
map[string]string of named captures, nil if no match. Cached. |
Examples:
seq 5 | goscript 'atoi(x) * atoi(x)' # squares
echo '{"a":1}' | goscript 'pp(map[string]int{"x": 42})'
# match/named without -r (handle nil yourself for non-matching lines)
cat file | goscript 'match(`v(\d+)`, x)[1]'
cat file | goscript 'named(`(?P<maj>\d+)\.(?P<min>\d+)`, x)["maj"]'Print the Go source that would be compiled, without actually running it:
ls | goscript --explain 'strings.ToUpper(x)'
goscript --explain 'pp(os.Environ())'Useful for understanding what's generated or debugging unexpected output.
goscript --no-cache 'fmt.Println("fresh compile")' # skip cache, always recompile
goscript --clear-cache # wipe entire cacheCache lives at ~/.cache/goscript/ (respects $XDG_CACHE_HOME):
~/.cache/goscript/
├── bin/<hash>/app ← compiled binary
└── work/<hash>/
├── main.go
└── go.mod
The cache key is the SHA-256 of the generated source, so any change to your snippet produces a fresh compile.
go install github.com/zcag/goscript@latestMake sure $GOPATH/bin (or $HOME/go/bin) is in your PATH.
- Source is read (from file or inline snippet)
- For inline mode: magic variables are detected → appropriate template is chosen
- Auto-print: if the last statement is a bare expression, it's wrapped in
fmt.Println - Source is hashed → cache is checked
- On miss: imports are resolved (via
goimports),go.modis generated, binary is compiled - Compiled binary is executed (or printed with
--explain, or saved with-o)
Subsequent runs with the same source reuse the cached binary instantly.