Skip to content

phuslu/gosh

Repository files navigation

gosh

gosh is a compact Bash-style shell written in Go.

It uses mvdan.cc/sh/v3 for shell parsing and interpretation, then adds the pieces that make it usable as an interactive shell: readline editing, history, prompt rendering, tab completion, bind, and Bash-flavored compatibility helpers.

Use it as a small local shell, a script runner, or an embeddable shell runtime inside a Go program.

Quick tour

Build the CLI from a checkout:

git clone https://github.com/phuslu/gosh
cd gosh/cmd/gosh
go build -mod=readonly -o gosh .
./gosh

Run a one-shot command:

./gosh -c 'name=gosh; for n in 1 2 3; do printf "%s:%s\n" "$name" "$n"; done'

Run a script from standard input:

printf 'echo one\necho two\n' | ./gosh

Start an interactive session:

./gosh
sh-0.0$ echo "$BASH_VERSION"
0.0.0(1)-gosh
sh-0.0$ shopt -s extglob
sh-0.0$ shopt -q extglob && echo enabled
enabled

Features

  • Bash-style syntax through mvdan.cc/sh/v3: variables, functions, conditionals, loops, pipelines, redirects, command substitution, arithmetic, globbing, and shell builtins.
  • Interactive line editing with readline.
  • Command and path completion.
  • Prefix history search for the up/down arrow bindings.
  • Bash prompt escape rendering for common PS1 and PS2 sequences.
  • bind support for common cursor and history-search actions.
  • history builtin backed by HISTFILE, HISTSIZE, and HISTCONTROL.
  • shopt -q compatibility, including builtin shopt -q and command shopt -q.
  • BASH_VERSION and $- compatibility variables for shell startup files.
  • Programmatic use through gosh.Run.

Configuration

Interactive gosh loads GOSH_ENV when it is set. If GOSH_ENV is unset, it loads $HOME/.bashrc for interactive sessions. Non-interactive scripts from standard input and commands run with -c do not load a startup file or render prompts.

Example ~/.bashrc or GOSH_ENV file:

if [ -n "${BASH_VERSION-}" ] && case "$-" in *i*) true;; *) false;; esac; then
    bind '"\e[1~": beginning-of-line'
    bind '"\e[4~": end-of-line'
    bind '"\e[5~": previous-screen'
    bind '"\e[6~": next-screen'
    bind '"\e[F": end-of-line'
    bind '"\e[H": beginning-of-line'
    bind '"\e[B": history-search-forward'
    bind '"\e[A": history-search-backward'
fi

export LC_ALL=en_US.UTF-8
export TERM=xterm-256color
export SHELL=/bin/bash
export PATH="$HOME/.local/bin:$PATH"

export HISTFILE="$HOME/.gosh_history"
export HISTSIZE=1000
export HISTCONTROL=ignoreboth

export PS1='\[\e]0;\h:\w\a\]\n\[\e[1;32m\]\u@\H\[\e[0;33m\] \w \[\e[0m[\D{%T}]\n\[\e[1;$((31+3*!$?))m\]\$\[\e[0m\] '

alias ls='ls -p --color'
alias ll='ls -lF --color'
alias grep='grep --color'

Embed in Go

gosh.Run accepts explicit input, output, environment, arguments, and working directory, so it can be used without a process-level shell.

package main

import (
	"bytes"
	"fmt"

	"github.com/phuslu/gosh"
)

func main() {
	var stdout, stderr bytes.Buffer

	err := gosh.Run(gosh.Config{
		Args:   []string{"gosh", "-c", `printf "hello %s\n" "$1"`, "gosh", "world"},
		Stdout: &stdout,
		Stderr: &stderr,
		Env: []string{
			"PATH=/usr/bin:/bin",
			"HOME=/tmp",
			"HISTFILE=/dev/null",
		},
	})
	if err != nil {
		fmt.Println(stderr.String())
		panic(err)
	}
	fmt.Print(stdout.String())
}

Compatibility notes

gosh is Bash-flavored, not a byte-for-byte Bash replacement. Shell syntax and many builtins come from mvdan.cc/sh/v3, while external commands are executed from the host PATH.

Some Bash features that depend on a full POSIX job-control shell, a PTY-managed process tree, or Bash internals may behave differently. Treat gosh as a small, embeddable shell with strong Bash compatibility for common scripts and interactive workflows, not as a login-shell replacement.

Development

Run the library tests from the repository root:

go test ./...

Build the CLI from its module:

cd cmd/gosh
go build -mod=readonly -o gosh .

About

A compact Bash-style shell written in Go

Resources

License

Stars

Watchers

Forks

Contributors

Languages