A shell parser, formatter, and interpreter with bash support; includes shfmt

sh

Go Reference

A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.14 or later.

Quick start

To parse shell scripts, inspect them, and print them out, see the syntax examples.

For high-level operations like performing shell expansions on strings, see the shell examples.

shfmt

GO111MODULE=on go get mvdan.cc/sh/v3/cmd/shfmt

shfmt formats shell programs. See canonical.sh for a quick look at its default style. For example:

shfmt -l -w script.sh

For more information, see its manpage, which can be viewed directly as Markdown or rendered with scdoc.

Packages are available on Alpine, Arch, Docker, FreeBSD, Homebrew, MacPorts, NixOS, Scoop, Snapcraft, Void and webi.

gosh

GO111MODULE=on go get mvdan.cc/sh/v3/cmd/gosh

Proof of concept shell that uses interp. Note that it's not meant to replace a POSIX shell at the moment, and its options are intentionally minimalistic.

Fuzzing

This project makes use of go-fuzz to find crashes and hangs in both the parser and the printer. The fuzz-corpus branch contains a corpus to get you started. For example:

git checkout fuzz-corpus
./fuzz

Caveats

  • When indexing Bash associative arrays, always use quotes. The static parser will otherwise have to assume that the index is an arithmetic expression.
$ echo '${array[spaced string]}' | shfmt
1:16: not a valid arithmetic operator: string
$ echo '${array[dash-string]}' | shfmt
${array[dash - string]}
  • $(( and (( ambiguity is not supported. Backtracking would complicate the parser and make streaming support via io.Reader impossible. The POSIX spec recommends to space the operands if $( ( is meant.
$ echo '$((foo); (bar))' | shfmt
1:1: reached ) without matching $(( with ))
  • Some builtins like export and let are parsed as keywords. This allows statically building their syntax tree, as opposed to keeping the arguments as a slice of words. Note that this means expansions like declare {a,b}=c are not supported.

JavaScript

A subset of the Go packages are available as an npm package called mvdan-sh. See the _js directory for more information.

Docker

To build a Docker image, checkout a specific version of the repository and run:

docker build -t my:tag -f cmd/shfmt/Dockerfile .

This creates an image that only includes shfmt. Alternatively, if you want an image that includes alpine, add --target alpine. To use the Docker image, run:

docker run --rm -v $PWD:/mnt -w /mnt my:tag <shfmt arguments>

pre-commit

It is possible to use shfmt with pre-commit and a local repo configuration like:

  - repo: local
    hooks:
      - id: shfmt
        name: shfmt
        minimum_pre_commit_version: 2.4.0
        language: golang
        additional_dependencies: [mvdan.cc/sh/v3/cmd/[email protected]]
        entry: shfmt
        args: [-w]
        types: [shell]

Related projects

The following editor integrations wrap shfmt:

Other noteworthy integrations include:

Owner
Comments
  • syntax: add mksh support

    syntax: add mksh support

    Initially suggested by @D630 in #77.

    Open questions needed to be answered before we decide whether or not to support it:

    • Where is the formal specification of its syntax? The man page (https://www.mirbsd.org/htman/i386/man1/false.htm) seems to be the only reference.
    • What language features does it have that we don't yet support via either POSIX Shell or Bash?
    • Is it compatible with the POSIX Shell syntax? i.e., do they conflict?
    • Is it compatible with the Bash syntax? i.e., do they conflict?

    And of course, whether or not this is worth the effort. I myself haven't seen any mksh scripts in the wild, whereas sh and bash are both very common.

  • cmd/shfmt: official docker images

    cmd/shfmt: official docker images

    I'd be great if you could provide a docker image on docker hub. My use case is to use it with drone.io, there are other CIs that can use docker images natively. Something like:

    FROM alpine:latest
    ENTRYPOINT ["/usr/bin/shfmt"]
    ADD https://github.com/mvdan/sh/releases/download/v1.2.0/shfmt_v1.2.0_linux_amd64 /usr/bin/shfmt
    RUN chmod +x /usr/bin/shfmt
    

    would work for me.

    And maybe the following for automated builds:

    FROM golang:alpine
    ENTRYPOINT ["shfmt"]
    COPY . /go/src/app
    WORKDIR /go/src/app
    RUN set -ex \
           && apk add --no-cache --virtual .build-deps git \
           && go-wrapper download ./... \
           && go-wrapper install ./... \
           && apk del .build-deps 
    
  • syntax: newline style around &&, || and | configurable in the printer

    syntax: newline style around &&, || and | configurable in the printer

    Right now the code:

    [ -z "$HOME" ] && foo || bar
    [ -z "$HOME" ] &&
        foo ||
        bar
    

    will be formatted:

    [ -z "$HOME" ] && foo || bar
    [ -z "$HOME" ] \
        && foo \
        || bar
    

    Can you add an option or something to control the formatting of the line breaks ?

  • syntax: configurable formatting of opening braces

    syntax: configurable formatting of opening braces

    Thanks for the great tool. I have started using this tool with the VS Code. I would like to provide one feedback w.r.t formatting functions (which would at least help couple of us).

    The opening brace of the function definition should be in next line.

    Currently it's as below...

    function fun() {
    }
    

    Suggested way...

    function fun()
    {
    }
    

    Refer examples here: https://www.shellscript.sh/functions.html

    One advantage of following the suggested format is, in the terminal editors like (vim), you can jump between functions by pressing [ and ] character twice. This is very important feature for specially scripts - as once you are done developing the script on your desktop, you would copy/run it on a system where there may not be graphical interfaces or IDEs.

  • syntax: add option to configure how many statements in a list can fit in a single line

    syntax: add option to configure how many statements in a list can fit in a single line

    Thanks for this tool. In our shell script, we have lines like

     	local cmd="docker build -t $DOCKER_REGISTRY/$IMG:$TAG ."
    	echo $cmd; $cmd
    

    We would like to not break lines like echo $cmd; $cmd into 2 separate lines. I did not see any flag to support this. Can this be supported?

  • cmd/shfmt: add a way to skip custom directories when walking directories

    cmd/shfmt: add a way to skip custom directories when walking directories

    When recursing it would be nice to be able to ignore certain directories or files (for example, node_modules).

    Either a --exclude=node_modules, or --ignore=node_modules, or a .shfmtignore-type functionality would be nice.

  • Introduce an option to disable changing white space within source lines

    Introduce an option to disable changing white space within source lines

    I'm starting to use shfmt (via Visual Studio Code editor), but one behaviour that prevents me from using it wholeheartedly is the way shfmt changes the whitespace inside source lines. I wonder if there can be an option to disable this behaviour, but it would continue to change the white spaces at the beginning or end of the line.

    The motivation is to not mess up custom alignment of code, creating layout that might be used to help the code read better.

  • interp: builtins skip the exec handler

    interp: builtins skip the exec handler

    im sure that this is definitely intentional, but this is actually undesirable in my use case im developing hilbish and use this module to run sh of course, but used to only use this as a last option fallback instead of running my custom builtins or lua code. but now im considering actually using an exec handler for the runner but realized cd wasnt behaving how i expected it to be since it's this module's cd and not mine

    so is there a way for me to change how the builtin works from outside the package? i havent found anything in docs like that

  • interp: TestRunnerTerminalExec/Pseudo flaky on Mac

    interp: TestRunnerTerminalExec/Pseudo flaky on Mac

    --- FAIL: TestRunnerTerminalExec (0.00s)
        --- FAIL: TestRunnerTerminalExec/Pseudo (0.14s)
    ##[error]        terminal_test.go:129: signal: hangup
    FAIL
    FAIL	mvdan.cc/sh/v3/interp	4.548s
    

    @theclapp perhaps you could help me reproduce this again? Do you have any ideas?

    Worst case scenario, we can temporarily skip the pty tests on Mac.

  • interp: calling a program with >> on Windows does not append

    interp: calling a program with >> on Windows does not append

    For simple echo it's working, but:

    $ ls >> out
    ls: write error: Bad file descriptor
    

    ls is from Windows Git (C:\Program Files\Git\usr\bin\ls.exe).

    Replace in interp\module.go, fix it, but it's bad implementation:

    var DefaultOpen = ModuleOpen(func(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
    	if flag&os.O_APPEND != 0 {
    		flag = flag & (^os.O_APPEND)
    		f, err := os.OpenFile(path, flag, perm)
    		f.Seek(0, 2)
    		return f, err
    	}
    	return os.OpenFile(path, flag, perm)
    })
    
  • Use official Docker GitHub actions

    Use official Docker GitHub actions

    Use the following actions instead of manual Docker buildx setup:

    • https://github.com/docker/login-action
    • https://github.com/docker/setup-qemu-action
    • https://github.com/docker/build-push-action

    Notes:

    • It would be good to merge "Build and push" and "Build and push (alpine)" steps but I have no solution to propose for now;
    • there are some linting changes (using prettier).

    WIP because CI needs to be tested against upstream repo:

    • create a DOCKER_USER secret so that forks can test the CI (push);
    • I am using token and 2FA on DockerHub so the update description step could not be tested.

    Regarding the last step (Update DockerHub description), we may consider using this https://github.com/marketplace/actions/docker-hub-description.

  • Feature Req: Directive to make parser skip over lines.

    Feature Req: Directive to make parser skip over lines.

    Hi, first of all thanks for your work!

    in #680 you write:

    The only possible change I can see here is a way to make the parser skip over lines which have syntax errors,

    and I wanted to further motivate the idea:

    I have a script which can be sourced OR run. When sourced it just does a few simple things which work in zsh OR bash, so I'd like to have it work in both shells.

    Problem: To find the script location when sourced I need an assignment which violates bash (but works in zsh):

    scriptpath="${BASH_SOURCE[0]:-${(%):-%x}}" # this script. works in bash AND zsh
    

    And that makes shfmt deny formatting the whole file.

    Edit: Yes $0 would work outside of functions in zsh and that would be compatible with bash syntax, still the above one has some advantages, I assume there are other such crazies.

    -> An ignore directive would, imho, have some use cases.

  • syntax: remove backslash-newline-semicolon when it's unnecessary

    syntax: remove backslash-newline-semicolon when it's unnecessary

    Take this piece of shell:

    $ cat f.sh
    if
    	true \
    	; then
    	echo yes
    fi
    $ bash f.sh
    yes
    $ shfmt f.sh
    if
    	true \
    		;
    then
    	echo yes
    fi
    

    Note how it runs, but shfmt didn't improve it that much. It could do better, instead producing:

    $ cat f2.sh
    if
    	true
    then
    	echo yes
    fi
    $ bash f2.sh 
    yes
    $ shfmt f2.sh
    if
    	true
    then
    	echo yes
    fi
    
  • Joining of lines with options/arguments is inconsistent

    Joining of lines with options/arguments is inconsistent

    In my scripts, I have a few commands that follow this general pattern:

    env\
    	VAR1='1'\
    	VAR2='2'\
    	somecommand\
    	--big-flag-1='big_flag_value_1'\
    	--big-flag-2='big_flag_value_2'\
    	--big-flag-3='big_flag_value_3'\
    	-x\
    	-y\
    	-z\
    	file_with_a_very_long_name_1\
    	file_with_a_very_long_name_2\
    	file_with_a_very_long_name_3\
    	file_with_a_very_long_name_4\
    	;
    

    This is line-oriented to simplify diffing, sorting, etc. I would expect shfmt -p to either leave the lines as-is (maybe adding or removing a space before the final \), but the result is rather unexpected and weird:

    env VAR1='1' \
           VAR2='2' \
           somecommand --big-flag-1='big_flag_value_1' \
           --big-flag-2='big_flag_value_2' \
           --big-flag-3='big_flag_value_3' \
           -x -y -z file_with_a_very_long_name_1 file_with_a_very_long_name_2 file_with_a_very_long_name_3 file_with_a_very_long_name_4
    

    I get the addition of extra spaces, but then it also combines all of the single-letter flags and all non-option arguments into one unreadable line. But not the envs or the long-form flags.

    I feel like shfmt shouldn't interfere with the placement of arguments on separate lines at all, but if it should, it should also at the very least not produce these overlong lines of mixed short options and positional arguments.

  • cmd/shfmt: Files with non-shell extension excluded

    cmd/shfmt: Files with non-shell extension excluded

    Hi, I'm using shfmt -f . to list files I want to format. However, many files aren't found as they don't have a .sh/.bash extension, but are named something like mkosi.finalize and I can't change the name as it is expected by some consumer. The files have, however, a valid shebang.

    The files are excluded because of the following line:

    https://github.com/mvdan/sh/blob/279b806d8f2417af933a40a726ad8299395d9183/fileutil/file.go#L80

    Would be great to just omit it, and do the shebang check in any case.

  • syntax: allow printing redirections before all arguments

    syntax: allow printing redirections before all arguments

    Sometimes, to improve readability, I put logfile redirections at the beginning of the line:

    >>$LOGFILE echo "[label]" 1 2 3
    >>$LOGFILE some_command 4 5 6
    

    This is currently, as of shfmt v3.5.1, incorrectly reformatted as follows:

    echo >> $LOGFILE "[label]" 1 2 3
    some_command >> $LOGFILE 4 5 6
    

    Research

    Putting redirects at various places in-between arguments and at the end seems to have been supported from the very beginning of sh in 1971. Putting redirects at the beginning was not supported in UNIX V1, but emerged sometime later. I haven't found out when.

    UNIX V1 manual (1971-03-11) (and code):

    Each command is a sequence of non-blank command arguments separated by blanks. [...] Two characters [i.e., "<" or ">"] cause the immediately following string to be interpreted as a special argument to the shell itself, not passed to the command. An argument of the form <arg causes the file arg to be used as the standard input file of the given command; an argument of the form “>arg” causes file “arg” to be used as the standard output file for the given command.

    Bash documentation, section 3.6 (2022-11-13):

    The following redirection operators may precede or appear anywhere within a simple command or may follow a command. Redirections are processed in the order they appear, from left to right. [...]

    Bash documentation, section 3.7.1 (2022-11-13):

    When a simple command is executed, the shell performs the following expansions, assignments, and redirections, from left to right, in the following order: 1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing. [...]

  • cmd/shfmt: link to web demo or playground

    cmd/shfmt: link to web demo or playground

    shfmt is a great tool, and it would be fantastic if it was more widely adopted. Unfortunately many potential users don't have an easy way to run it: the only *nix machines they have access to are managed by others, and they don't have the expertise to set up something like a VM. To help those users out, I'm wondering if someone would be willing to host a playground like ShellCheck where users can simply paste their code and get a formatted version back in another part of the page.

Interpreter - The Official Interpreter for the Infant Lang written in Go

Infant Lang Interpreter Infant Lang Minimalistic Less Esoteric Programming Langu

Jan 10, 2022
Interactive Go interpreter and debugger with REPL, Eval, generics and Lisp-like macros

gomacro - interactive Go interpreter and debugger with generics and macros gomacro is an almost complete Go interpreter, implemented in pure Go. It of

Dec 30, 2022
Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter.

quickjs Go bindings to QuickJS: a fast, small, and embeddable ES2020 JavaScript interpreter. These bindings are a WIP and do not match full parity wit

Dec 28, 2022
Monkey programming language project from 'Writing An Interpreter In Go'and 'Writing A Compiler In Go' Books
Monkey programming language project from 'Writing An Interpreter In Go'and 'Writing A Compiler In Go' Books

Monkey Monkey programming language ?? project from "Writing An Interpreter In Go

Dec 16, 2021
Scriptable interpreter written in golang
Scriptable interpreter written in golang

Anko Anko is a scriptable interpreter written in Go. (Picture licensed under CC BY-SA 3.0, photo by Ocdp) Usage Example - Embedded package main impor

Dec 23, 2022
A POSIX-compliant AWK interpreter written in Go

GoAWK: an AWK interpreter written in Go AWK is a fascinating text-processing language, and somehow after reading the delightfully-terse The AWK Progra

Dec 31, 2022
A BASIC interpreter written in golang.
A BASIC interpreter written in golang.

05 PRINT "Index" 10 PRINT "GOBASIC!" 20 PRINT "Limitations" Arrays Line Numbers IF Statement DATA / READ Statements Builtin Functions Types 30 PRINT "

Dec 24, 2022
Prolog interpreter in Go

golog Prolog interpreter in Go with aspirations to be ISO compatible. See the full package documentation for usage details. Install with go get github

Nov 12, 2022
A simple virtual machine - compiler & interpreter - written in golang

go.vm Installation Build without Go Modules (Go before 1.11) Build with Go Modules (Go 1.11 or higher) Usage Opcodes Notes The compiler The interprete

Dec 17, 2022
A JavaScript interpreter in Go (golang)

otto -- import "github.com/robertkrimen/otto" Package otto is a JavaScript parser and interpreter written natively in Go. http://godoc.org/github.com/

Jan 2, 2023
Yaegi is Another Elegant Go Interpreter
Yaegi is Another Elegant Go Interpreter

Yaegi is Another Elegant Go Interpreter. It powers executable Go scripts and plugins, in embedded interpreters or interactive shells, on top of the Go

Dec 30, 2022
Lisp Interpreter

golisp Lisp Interpreter Usage $ golisp < foo.lisp Installation $ go get github.com/mattn/golisp/cmd/golisp Features Call Go functions. Print random in

Dec 15, 2022
Mini lisp interpreter written in Go.

Mini Go Lisp Mini lisp interpreter written in Go. It is implemented with reference to the d-tsuji/SDLisp repository written in Java. Support System Fu

Nov 25, 2022
Toy Lisp 1.5 interpreter

Lisp 1.5 To install: go get robpike.io/lisp. This is an implementation of the language defined, with sublime concision, in the first few pages of the

Jan 1, 2023
A interpreter of SweetLang, is writed in Go Programming language.

SweetLang ( Soon ) A interpreter of SweetLang, is writed in Go Programming language. SweetLang is made with clarity and simplicity we try to make its

Oct 31, 2021
interpreter for the basic language written in golang
interpreter for the basic language written in golang

jirachi interpreter for the basic language written in golang The plan supports the following functions: Arithmetic Operations (+, -, *, /, ^) Comparis

Sep 17, 2022
◻️ A Whitespace interpreter written in Go.

whitespace-go A Whitespace interpreter written in Go. Whitespace is a esoteric programming language made up entirely of spaces, tabs, and newlines. Th

May 18, 2022
🧠 A Brainfuck interpreter written in Go

brainfuck-go A Brainfuck interpreter written in Go. How Brainfuck works Brainfuck is an esoteric programming language designed to have the simplest co

Sep 30, 2022
A little brainfuck interpreter written in Go

Brainfuck_go_interpreter A little brainfuck interpreter written in Go from what I've done in coding game Usage $ go build brainfuck.go $ ./brainfuck P

Dec 13, 2021