Skip to content

Repository files navigation

goscript

Run small, standalone Go scripts without creating a module or repository. Supports importing any Go module automatically — no go.mod required.


Inline mode

Run Go snippets directly — just pass the code as an argument:

goscript 'fmt.Println("hello")'

Auto-print

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: 2025

Explicit fmt.Print* calls are detected and left untouched (no double-printing).


Script mode

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/ myscript

Scripts must use package main and define func main() — it's real Go, not a DSL.


Pipe mode (automatic)

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)

Per-line processing

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 numbers

Batch processing

Reference 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)
'

Field separator (-F)

-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

Parallel processing (-j N)

-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

Regex pipe mode (-r pattern)

-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)'

Helpers

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"]'

Inspect generated code (--explain)

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.


Cache control

goscript --no-cache 'fmt.Println("fresh compile")'   # skip cache, always recompile
goscript --clear-cache                                # wipe entire cache

Cache 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.


Installation

go install github.com/zcag/goscript@latest

Make sure $GOPATH/bin (or $HOME/go/bin) is in your PATH.


How it works

  1. Source is read (from file or inline snippet)
  2. For inline mode: magic variables are detected → appropriate template is chosen
  3. Auto-print: if the last statement is a bare expression, it's wrapped in fmt.Println
  4. Source is hashed → cache is checked
  5. On miss: imports are resolved (via goimports), go.mod is generated, binary is compiled
  6. Compiled binary is executed (or printed with --explain, or saved with -o)

Subsequent runs with the same source reuse the cached binary instantly.

About

Go scripting, write single file executable, run compiled. Shebang magic.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages