syntax

package
v3.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 1, 2021 License: BSD-3-Clause Imports: 10 Imported by: 150

Documentation

Overview

Package syntax implements parsing and formatting of shell programs. It supports POSIX Shell, Bash, and mksh.

Example
package main

import (
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	r := strings.NewReader("{ foo; bar; }")
	f, err := syntax.NewParser().Parse(r, "")
	if err != nil {
		return
	}
	syntax.NewPrinter().Print(os.Stdout, f)
}
Output:

{
	foo
	bar
}

Index

Examples

Constants

View Source
const (
	RdrOut   = RedirOperator(rdrOut) + iota // >
	AppOut                                  // >>
	RdrIn                                   // <
	RdrInOut                                // <>
	DplIn                                   // <&
	DplOut                                  // >&
	ClbOut                                  // >|
	Hdoc                                    // <<
	DashHdoc                                // <<-
	WordHdoc                                // <<<
	RdrAll                                  // &>
	AppAll                                  // &>>
)
View Source
const (
	CmdIn  = ProcOperator(cmdIn) + iota // <(
	CmdOut                              // >(
)
View Source
const (
	GlobZeroOrOne  = GlobOperator(globQuest) + iota // ?(
	GlobZeroOrMore                                  // *(
	GlobOneOrMore                                   // +(
	GlobOne                                         // @(
	GlobExcept                                      // !(
)
View Source
const (
	AndStmt = BinCmdOperator(andAnd) + iota // &&
	OrStmt                                  // ||
	Pipe                                    // |
	PipeAll                                 // |&
)
View Source
const (
	Break       = CaseOperator(dblSemicolon) + iota // ;;
	Fallthrough                                     // ;&
	Resume                                          // ;;&
	ResumeKorn                                      // ;|
)
View Source
const (
	NamesPrefix      = ParNamesOperator(star) // *
	NamesPrefixWords = ParNamesOperator(at)   // @
)
View Source
const (
	AlternateUnset       = ParExpOperator(plus) + iota // +
	AlternateUnsetOrNull                               // :+
	DefaultUnset                                       // -
	DefaultUnsetOrNull                                 // :-
	ErrorUnset                                         // ?
	ErrorUnsetOrNull                                   // :?
	AssignUnset                                        // =
	AssignUnsetOrNull                                  // :=
	RemSmallSuffix                                     // %
	RemLargeSuffix                                     // %%
	RemSmallPrefix                                     // #
	RemLargePrefix                                     // ##
	UpperFirst                                         // ^
	UpperAll                                           // ^^
	LowerFirst                                         // ,
	LowerAll                                           // ,,
	OtherParamOps                                      // @
)
View Source
const (
	Not         = UnAritOperator(exclMark) + iota // !
	BitNegation                                   // ~
	Inc                                           // ++
	Dec                                           // --
	Plus        = UnAritOperator(plus)            // +
	Minus       = UnAritOperator(minus)           // -
)
View Source
const (
	Add = BinAritOperator(plus)   // +
	Sub = BinAritOperator(minus)  // -
	Mul = BinAritOperator(star)   // *
	Quo = BinAritOperator(slash)  // /
	Rem = BinAritOperator(perc)   // %
	Pow = BinAritOperator(power)  // **
	Eql = BinAritOperator(equal)  // ==
	Gtr = BinAritOperator(rdrOut) // >
	Lss = BinAritOperator(rdrIn)  // <
	Neq = BinAritOperator(nequal) // !=
	Leq = BinAritOperator(lequal) // <=
	Geq = BinAritOperator(gequal) // >=
	And = BinAritOperator(and)    // &
	Or  = BinAritOperator(or)     // |
	Xor = BinAritOperator(caret)  // ^
	Shr = BinAritOperator(appOut) // >>
	Shl = BinAritOperator(hdoc)   // <<

	AndArit   = BinAritOperator(andAnd) // &&
	OrArit    = BinAritOperator(orOr)   // ||
	Comma     = BinAritOperator(comma)  // ,
	TernQuest = BinAritOperator(quest)  // ?
	TernColon = BinAritOperator(colon)  // :

	Assgn    = BinAritOperator(assgn)    // =
	AddAssgn = BinAritOperator(addAssgn) // +=
	SubAssgn = BinAritOperator(subAssgn) // -=
	MulAssgn = BinAritOperator(mulAssgn) // *=
	QuoAssgn = BinAritOperator(quoAssgn) // /=
	RemAssgn = BinAritOperator(remAssgn) // %=
	AndAssgn = BinAritOperator(andAssgn) // &=
	OrAssgn  = BinAritOperator(orAssgn)  // |=
	XorAssgn = BinAritOperator(xorAssgn) // ^=
	ShlAssgn = BinAritOperator(shlAssgn) // <<=
	ShrAssgn = BinAritOperator(shrAssgn) // >>=
)
View Source
const (
	TsExists  = UnTestOperator(tsExists) + iota // -e
	TsRegFile                                   // -f
	TsDirect                                    // -d
	TsCharSp                                    // -c
	TsBlckSp                                    // -b
	TsNmPipe                                    // -p
	TsSocket                                    // -S
	TsSmbLink                                   // -L
	TsSticky                                    // -k
	TsGIDSet                                    // -g
	TsUIDSet                                    // -u
	TsGrpOwn                                    // -G
	TsUsrOwn                                    // -O
	TsModif                                     // -N
	TsRead                                      // -r
	TsWrite                                     // -w
	TsExec                                      // -x
	TsNoEmpty                                   // -s
	TsFdTerm                                    // -t
	TsEmpStr                                    // -z
	TsNempStr                                   // -n
	TsOptSet                                    // -o
	TsVarSet                                    // -v
	TsRefVar                                    // -R
	TsNot     = UnTestOperator(exclMark)        // !
)
View Source
const (
	TsReMatch    = BinTestOperator(tsReMatch) + iota // =~
	TsNewer                                          // -nt
	TsOlder                                          // -ot
	TsDevIno                                         // -ef
	TsEql                                            // -eq
	TsNeq                                            // -ne
	TsLeq                                            // -le
	TsGeq                                            // -ge
	TsLss                                            // -lt
	TsGtr                                            // -gt
	AndTest      = BinTestOperator(andAnd)           // &&
	OrTest       = BinTestOperator(orOr)             // ||
	TsMatchShort = BinTestOperator(assgn)            // =
	TsMatch      = BinTestOperator(equal)            // ==
	TsNoMatch    = BinTestOperator(nequal)           // !=
	TsBefore     = BinTestOperator(rdrIn)            // <
	TsAfter      = BinTestOperator(rdrOut)           // >
)

Variables

This section is empty.

Functions

func DebugPrint

func DebugPrint(w io.Writer, node Node) error

DebugPrint prints the provided syntax tree, spanning multiple lines and with indentation. Can be useful to investigate the content of a syntax tree.

Example
package main

import (
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	in := strings.NewReader(`echo 'foo'`)
	f, err := syntax.NewParser().Parse(in, "")
	if err != nil {
		return
	}
	syntax.DebugPrint(os.Stdout, f)
}
Output:

*syntax.File {
.  Name: ""
.  Stmts: []*syntax.Stmt (len = 1) {
.  .  0: *syntax.Stmt {
.  .  .  Comments: []syntax.Comment (len = 0) {}
.  .  .  Cmd: *syntax.CallExpr {
.  .  .  .  Assigns: []*syntax.Assign (len = 0) {}
.  .  .  .  Args: []*syntax.Word (len = 2) {
.  .  .  .  .  0: *syntax.Word {
.  .  .  .  .  .  Parts: []syntax.WordPart (len = 1) {
.  .  .  .  .  .  .  0: *syntax.Lit {
.  .  .  .  .  .  .  .  ValuePos: 1:1
.  .  .  .  .  .  .  .  ValueEnd: 1:5
.  .  .  .  .  .  .  .  Value: "echo"
.  .  .  .  .  .  .  }
.  .  .  .  .  .  }
.  .  .  .  .  }
.  .  .  .  .  1: *syntax.Word {
.  .  .  .  .  .  Parts: []syntax.WordPart (len = 1) {
.  .  .  .  .  .  .  0: *syntax.SglQuoted {
.  .  .  .  .  .  .  .  Left: 1:6
.  .  .  .  .  .  .  .  Right: 1:10
.  .  .  .  .  .  .  .  Dollar: false
.  .  .  .  .  .  .  .  Value: "foo"
.  .  .  .  .  .  .  }
.  .  .  .  .  .  }
.  .  .  .  .  }
.  .  .  .  }
.  .  .  }
.  .  .  Position: 1:1
.  .  .  Semicolon: 0:0
.  .  .  Negated: false
.  .  .  Background: false
.  .  .  Coprocess: false
.  .  .  Redirs: []*syntax.Redirect (len = 0) {}
.  .  }
.  }
.  Last: []syntax.Comment (len = 0) {}
}

func IsIncomplete

func IsIncomplete(err error) bool

IsIncomplete reports whether a Parser error could have been avoided with extra input bytes. For example, if an io.EOF was encountered while there was an unclosed quote or parenthesis.

func IsKeyword added in v3.3.0

func IsKeyword(word string) bool

IsKeyword returns true if the given word is part of the language keywords.

func Quote added in v3.4.0

func Quote(s string, lang LangVariant) (string, error)

Quote returns a quoted version of the input string, so that the quoted version is expanded or interpreted as the original string in the given language variant.

Quoting is necessary when using arbitrary literal strings as words in a shell script or command. Without quoting, one can run into syntax errors, as well as the possibility of running unintended code.

An error is returned when a string cannot be quoted for a variant. For instance, POSIX lacks escape sequences for non-printable characters, and no language variant can represent a string containing null bytes. In such cases, the returned error type will be *QuoteError.

The quoting strategy is chosen on a best-effort basis, to minimize the amount of extra bytes necessary.

Some strings do not require any quoting and are returned unchanged. Those strings can be directly surrounded in single quotes as well.

Example
package main

import (
	"fmt"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	for _, s := range []string{
		"foo",
		"bar $baz",
		`"won't"`,
		"~/home",
		"#1304",
		"name=value",
		"for",
		"glob-*",
		"invalid-\xe2'",
		"nonprint-\x0b\x1b",
	} {
		// We assume Bash syntax here.
		// For general shell syntax quoting, use syntax.LangPOSIX.
		quoted, err := syntax.Quote(s, syntax.LangBash)
		if err != nil {
			fmt.Printf("%q cannot be quoted: %v\n", s, err)
		} else {
			fmt.Printf("Quote(%17q): %s\n", s, quoted)
		}
	}
}
Output:

Quote(            "foo"): foo
Quote(       "bar $baz"): 'bar $baz'
Quote(      "\"won't\""): "\"won't\""
Quote(         "~/home"): '~/home'
Quote(          "#1304"): '#1304'
Quote(     "name=value"): 'name=value'
Quote(            "for"): 'for'
Quote(         "glob-*"): 'glob-*'
Quote(  "invalid-\xe2'"): $'invalid-\xe2\''
Quote("nonprint-\v\x1b"): $'nonprint-\v\x1b'

func Simplify

func Simplify(n Node) bool

Simplify modifies a node to remove redundant pieces of syntax, and returns whether any changes were made.

The changes currently applied are:

Remove clearly useless parentheses       $(( (expr) ))
Remove dollars from vars in exprs        (($var))
Remove duplicate subshells               $( (stmts) )
Remove redundant quotes                  [[ "$var" == str ]]
Merge negations with unary operators     [[ ! -n $var ]]
Use single quotes to shorten literals    "\$foo"

func SplitBraces

func SplitBraces(word *Word) bool

SplitBraces parses brace expansions within a word's literal parts. If any valid brace expansions are found, they are replaced with BraceExp nodes, and the function returns true. Otherwise, the word is left untouched and the function returns false.

For example, a literal word "foo{bar,baz}" will result in a word containing the literal "foo", and a brace expansion with the elements "bar" and "baz".

It does not return an error; malformed brace expansions are simply skipped. For example, the literal word "a{b" is left unchanged.

func ValidName

func ValidName(val string) bool

ValidName returns whether val is a valid name as per the POSIX spec.

func Walk

func Walk(node Node, f func(Node) bool)

Walk traverses a syntax tree in depth-first order: It starts by calling f(node); node must not be nil. If f returns true, Walk invokes f recursively for each of the non-nil children of node, followed by f(nil).

Example
package main

import (
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	in := strings.NewReader(`echo $foo "and $bar"`)
	f, err := syntax.NewParser().Parse(in, "")
	if err != nil {
		return
	}
	syntax.Walk(f, func(node syntax.Node) bool {
		switch x := node.(type) {
		case *syntax.ParamExp:
			x.Param.Value = strings.ToUpper(x.Param.Value)
		}
		return true
	})
	syntax.NewPrinter().Print(os.Stdout, f)
}
Output:

echo $FOO "and $BAR"

Types

type ArithmCmd

type ArithmCmd struct {
	Left, Right Pos
	Unsigned    bool // mksh's ((# expr))

	X ArithmExpr
}

ArithmCmd represents an arithmetic command.

This node will only appear in LangBash and LangMirBSDKorn.

func (*ArithmCmd) End

func (a *ArithmCmd) End() Pos

func (*ArithmCmd) Pos

func (a *ArithmCmd) Pos() Pos

type ArithmExp

type ArithmExp struct {
	Left, Right Pos
	Bracket     bool // deprecated $[expr] form
	Unsigned    bool // mksh's $((# expr))

	X ArithmExpr
}

ArithmExp represents an arithmetic expansion.

func (*ArithmExp) End

func (a *ArithmExp) End() Pos

func (*ArithmExp) Pos

func (a *ArithmExp) Pos() Pos

type ArithmExpr

type ArithmExpr interface {
	Node
	// contains filtered or unexported methods
}

ArithmExpr represents all nodes that form arithmetic expressions.

These are *BinaryArithm, *UnaryArithm, *ParenArithm, and *Word.

type ArrayElem

type ArrayElem struct {
	Index    ArithmExpr
	Value    *Word
	Comments []Comment
}

ArrayElem represents a Bash array element.

Index can be nil; for example, declare -a x=(value). Value can be nil; for example, declare -A x=([index]=). Finally, neither can be nil; for example, declare -A x=([index]=value)

func (*ArrayElem) End

func (a *ArrayElem) End() Pos

func (*ArrayElem) Pos

func (a *ArrayElem) Pos() Pos

type ArrayExpr

type ArrayExpr struct {
	Lparen, Rparen Pos

	Elems []*ArrayElem
	Last  []Comment
}

ArrayExpr represents a Bash array expression.

This node will only appear with LangBash.

func (*ArrayExpr) End

func (a *ArrayExpr) End() Pos

func (*ArrayExpr) Pos

func (a *ArrayExpr) Pos() Pos

type Assign

type Assign struct {
	Append bool       // +=
	Naked  bool       // without '='
	Name   *Lit       // must be a valid name
	Index  ArithmExpr // [i], ["k"]
	Value  *Word      // =val
	Array  *ArrayExpr // =(arr)
}

Assign represents an assignment to a variable.

Here and elsewhere, Index can mean either an index expression into an indexed array, or a string key into an associative array.

If Index is non-nil, the value will be a word and not an array as nested arrays are not allowed.

If Naked is true and Name is nil, the assignment is part of a DeclClause and the argument (in the Value field) will be evaluated at run-time. This includes parameter expansions, which may expand to assignments or options.

func (*Assign) End

func (a *Assign) End() Pos

func (*Assign) Pos

func (a *Assign) Pos() Pos

type BinAritOperator

type BinAritOperator token

func (BinAritOperator) String

func (o BinAritOperator) String() string

type BinCmdOperator

type BinCmdOperator token

func (BinCmdOperator) String

func (o BinCmdOperator) String() string

type BinTestOperator

type BinTestOperator token

func (BinTestOperator) String

func (o BinTestOperator) String() string

type BinaryArithm

type BinaryArithm struct {
	OpPos Pos
	Op    BinAritOperator
	X, Y  ArithmExpr
}

BinaryArithm represents a binary arithmetic expression.

If Op is any assign operator, X will be a word with a single *Lit whose value is a valid name.

Ternary operators like "a ? b : c" are fit into this structure. Thus, if Op==TernQuest, Y will be a *BinaryArithm with Op==TernColon. Op can only be TernColon in that scenario.

func (*BinaryArithm) End

func (b *BinaryArithm) End() Pos

func (*BinaryArithm) Pos

func (b *BinaryArithm) Pos() Pos

type BinaryCmd

type BinaryCmd struct {
	OpPos Pos
	Op    BinCmdOperator
	X, Y  *Stmt
}

BinaryCmd represents a binary expression between two statements.

func (*BinaryCmd) End

func (b *BinaryCmd) End() Pos

func (*BinaryCmd) Pos

func (b *BinaryCmd) Pos() Pos

type BinaryTest

type BinaryTest struct {
	OpPos Pos
	Op    BinTestOperator
	X, Y  TestExpr
}

BinaryTest represents a binary test expression.

func (*BinaryTest) End

func (b *BinaryTest) End() Pos

func (*BinaryTest) Pos

func (b *BinaryTest) Pos() Pos

type Block

type Block struct {
	Lbrace, Rbrace Pos

	Stmts []*Stmt
	Last  []Comment
}

Block represents a series of commands that should be executed in a nested scope. It is essentially a list of statements within curly braces.

func (*Block) End

func (b *Block) End() Pos

func (*Block) Pos

func (b *Block) Pos() Pos

type BraceExp

type BraceExp struct {
	Sequence bool // {x..y[..incr]} instead of {x,y[,...]}
	Elems    []*Word
}

BraceExp represents a Bash brace expression, such as "{a,f}" or "{1..10}".

This node will only appear as a result of SplitBraces.

func (*BraceExp) End

func (b *BraceExp) End() Pos

func (*BraceExp) Pos

func (b *BraceExp) Pos() Pos

type CStyleLoop

type CStyleLoop struct {
	Lparen, Rparen Pos
	// Init, Cond, Post can each be nil, if the for loop construct omits it.
	Init, Cond, Post ArithmExpr
}

CStyleLoop represents the behaviour of a for clause similar to the C language.

This node will only appear with LangBash.

func (*CStyleLoop) End

func (c *CStyleLoop) End() Pos

func (*CStyleLoop) Pos

func (c *CStyleLoop) Pos() Pos

type CallExpr

type CallExpr struct {
	Assigns []*Assign // a=x b=y args
	Args    []*Word
}

CallExpr represents a command execution or function call, otherwise known as a "simple command".

If Args is empty, Assigns apply to the shell environment. Otherwise, they are variables that cannot be arrays and which only apply to the call.

func (*CallExpr) End

func (c *CallExpr) End() Pos

func (*CallExpr) Pos

func (c *CallExpr) Pos() Pos

type CaseClause

type CaseClause struct {
	Case, In, Esac Pos
	Braces         bool // deprecated mksh form with braces instead of in/esac

	Word  *Word
	Items []*CaseItem
	Last  []Comment
}

CaseClause represents a case (switch) clause.

func (*CaseClause) End

func (c *CaseClause) End() Pos

func (*CaseClause) Pos

func (c *CaseClause) Pos() Pos

type CaseItem

type CaseItem struct {
	Op       CaseOperator
	OpPos    Pos // unset if it was finished by "esac"
	Comments []Comment
	Patterns []*Word

	Stmts []*Stmt
	Last  []Comment
}

CaseItem represents a pattern list (case) within a CaseClause.

func (*CaseItem) End

func (c *CaseItem) End() Pos

func (*CaseItem) Pos

func (c *CaseItem) Pos() Pos

type CaseOperator

type CaseOperator token

func (CaseOperator) String

func (o CaseOperator) String() string

type CmdSubst

type CmdSubst struct {
	Left, Right Pos

	Stmts []*Stmt
	Last  []Comment

	Backquotes bool // deprecated `foo`
	TempFile   bool // mksh's ${ foo;}
	ReplyVar   bool // mksh's ${|foo;}
}

CmdSubst represents a command substitution.

func (*CmdSubst) End

func (c *CmdSubst) End() Pos

func (*CmdSubst) Pos

func (c *CmdSubst) Pos() Pos

type Command

type Command interface {
	Node
	// contains filtered or unexported methods
}

Command represents all nodes that are simple or compound commands, including function declarations.

These are *CallExpr, *IfClause, *WhileClause, *ForClause, *CaseClause, *Block, *Subshell, *BinaryCmd, *FuncDecl, *ArithmCmd, *TestClause, *DeclClause, *LetClause, *TimeClause, and *CoprocClause.

Example
package main

import (
	"fmt"
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	r := strings.NewReader("echo foo; if x; then y; fi; foo | bar")
	f, err := syntax.NewParser().Parse(r, "")
	if err != nil {
		return
	}

	printer := syntax.NewPrinter()
	for i, stmt := range f.Stmts {
		fmt.Printf("Cmd %d: %-20T - ", i, stmt.Cmd)
		printer.Print(os.Stdout, stmt.Cmd)
		fmt.Println()
	}

}
Output:

Cmd 0: *syntax.CallExpr     - echo foo
Cmd 1: *syntax.IfClause     - if x; then y; fi
Cmd 2: *syntax.BinaryCmd    - foo | bar

type Comment

type Comment struct {
	Hash Pos
	Text string
}

Comment represents a single comment on a single line.

func (*Comment) End

func (c *Comment) End() Pos

func (*Comment) Pos

func (c *Comment) Pos() Pos

type CoprocClause

type CoprocClause struct {
	Coproc Pos
	Name   *Word
	Stmt   *Stmt
}

CoprocClause represents a Bash coproc clause.

This node will only appear with LangBash.

func (*CoprocClause) End

func (c *CoprocClause) End() Pos

func (*CoprocClause) Pos

func (c *CoprocClause) Pos() Pos

type DblQuoted

type DblQuoted struct {
	Left, Right Pos
	Dollar      bool // $""
	Parts       []WordPart
}

DblQuoted represents a list of nodes within double quotes.

func (*DblQuoted) End

func (q *DblQuoted) End() Pos

func (*DblQuoted) Pos

func (q *DblQuoted) Pos() Pos

type DeclClause

type DeclClause struct {
	// Variant is one of "declare", "local", "export", "readonly",
	// "typeset", or "nameref".
	Variant *Lit
	Args    []*Assign
}

DeclClause represents a Bash declare clause.

Args can contain a mix of regular and naked assignments. The naked assignments can represent either options or variable names.

This node will only appear with LangBash.

func (*DeclClause) End

func (d *DeclClause) End() Pos

func (*DeclClause) Pos

func (d *DeclClause) Pos() Pos

type Expansion

type Expansion struct {
	Op   ParExpOperator
	Word *Word
}

Expansion represents string manipulation in a ParamExp other than those covered by Replace.

type ExtGlob

type ExtGlob struct {
	OpPos   Pos
	Op      GlobOperator
	Pattern *Lit
}

ExtGlob represents a Bash extended globbing expression. Note that these are parsed independently of whether shopt has been called or not.

This node will only appear in LangBash and LangMirBSDKorn.

func (*ExtGlob) End

func (e *ExtGlob) End() Pos

func (*ExtGlob) Pos

func (e *ExtGlob) Pos() Pos

type File

type File struct {
	Name string

	Stmts []*Stmt
	Last  []Comment
}

File represents a shell source file.

func (*File) End

func (f *File) End() Pos

func (*File) Pos

func (f *File) Pos() Pos

type ForClause

type ForClause struct {
	ForPos, DoPos, DonePos Pos
	Select                 bool
	Braces                 bool // deprecated form with { } instead of do/done
	Loop                   Loop

	Do     []*Stmt
	DoLast []Comment
}

ForClause represents a for or a select clause. The latter is only present in Bash.

func (*ForClause) End

func (f *ForClause) End() Pos

func (*ForClause) Pos

func (f *ForClause) Pos() Pos

type FuncDecl

type FuncDecl struct {
	Position Pos
	RsrvWord bool // non-posix "function f" style
	Parens   bool // with () parentheses, only meaningful with RsrvWord=true
	Name     *Lit
	Body     *Stmt
}

FuncDecl represents the declaration of a function.

func (*FuncDecl) End

func (f *FuncDecl) End() Pos

func (*FuncDecl) Pos

func (f *FuncDecl) Pos() Pos

type GlobOperator

type GlobOperator token

func (GlobOperator) String

func (o GlobOperator) String() string

type IfClause

type IfClause struct {
	Position Pos // position of the starting "if", "elif", or "else" token
	ThenPos  Pos // position of "then", empty if this is an "else"
	FiPos    Pos // position of "fi", shared with .Else if non-nil

	Cond     []*Stmt
	CondLast []Comment
	Then     []*Stmt
	ThenLast []Comment

	Else *IfClause // if non-nil, an "elif" or an "else"

	Last []Comment // comments on the first "elif", "else", or "fi"
}

IfClause represents an if statement.

func (*IfClause) End

func (c *IfClause) End() Pos

func (*IfClause) Pos

func (c *IfClause) Pos() Pos

type LangError

type LangError struct {
	Filename string
	Pos
	Feature string
	Langs   []LangVariant
}

LangError is returned when the parser encounters code that is only valid in other shell language variants. The error includes what feature is not present in the current language variant, and what languages support it.

func (LangError) Error

func (e LangError) Error() string

type LangVariant

type LangVariant int

LangVariant describes a shell language variant to use when tokenizing and parsing shell code. The zero value is Bash.

const (
	// LangBash corresponds to the GNU Bash language, as described in its
	// manual at https://www.gnu.org/software/bash/manual/bash.html.
	//
	// We currently follow Bash version 5.1.
	//
	// Its string representation is "bash".
	LangBash LangVariant = iota

	// LangPOSIX corresponds to the POSIX Shell language, as described at
	// https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html.
	//
	// Its string representation is "posix" or "sh".
	LangPOSIX

	// LangMirBSDKorn corresponds to the MirBSD Korn Shell, also known as
	// mksh, as described at http://www.mirbsd.org/htman/i386/man1/mksh.htm.
	// Note that it shares some features with Bash, due to the the shared
	// ancestry that is ksh.
	//
	// We currently follow mksh version 59.
	//
	// Its string representation is "mksh".
	LangMirBSDKorn

	// LangBats corresponds to the Bash Automated Testing System language,
	// as described at https://github.com/bats-core/bats-core. Note that
	// it's just a small extension of the Bash language.
	//
	// Its string representation is "bats".
	LangBats
)

func (*LangVariant) Set added in v3.2.0

func (l *LangVariant) Set(s string) error

func (LangVariant) String

func (l LangVariant) String() string

type LetClause

type LetClause struct {
	Let   Pos
	Exprs []ArithmExpr
}

LetClause represents a Bash let clause.

This node will only appear in LangBash and LangMirBSDKorn.

func (*LetClause) End

func (l *LetClause) End() Pos

func (*LetClause) Pos

func (l *LetClause) Pos() Pos

type Lit

type Lit struct {
	ValuePos, ValueEnd Pos
	Value              string
}

Lit represents a string literal.

Note that a parsed string literal may not appear as-is in the original source code, as it is possible to split literals by escaping newlines. The splitting is lost, but the end position is not.

func (*Lit) End

func (l *Lit) End() Pos

func (*Lit) Pos

func (l *Lit) Pos() Pos

type Loop

type Loop interface {
	Node
	// contains filtered or unexported methods
}

Loop holds either *WordIter or *CStyleLoop.

type Node

type Node interface {
	// Pos returns the position of the first character of the node. Comments
	// are ignored, except if the node is a *File.
	Pos() Pos
	// End returns the position of the character immediately after the node.
	// If the character is a newline, the line number won't cross into the
	// next line. Comments are ignored, except if the node is a *File.
	End() Pos
}

Node represents a syntax tree node.

type ParExpOperator

type ParExpOperator token

func (ParExpOperator) String

func (o ParExpOperator) String() string

type ParNamesOperator

type ParNamesOperator token

func (ParNamesOperator) String

func (o ParNamesOperator) String() string

type ParamExp

type ParamExp struct {
	Dollar, Rbrace Pos

	Short  bool // $a instead of ${a}
	Excl   bool // ${!a}
	Length bool // ${#a}
	Width  bool // ${%a}
	Param  *Lit
	Index  ArithmExpr       // ${a[i]}, ${a["k"]}
	Slice  *Slice           // ${a:x:y}
	Repl   *Replace         // ${a/x/y}
	Names  ParNamesOperator // ${!prefix*} or ${!prefix@}
	Exp    *Expansion       // ${a:-b}, ${a#b}, etc
}

ParamExp represents a parameter expansion.

func (*ParamExp) End

func (p *ParamExp) End() Pos

func (*ParamExp) Pos

func (p *ParamExp) Pos() Pos

type ParenArithm

type ParenArithm struct {
	Lparen, Rparen Pos

	X ArithmExpr
}

ParenArithm represents an arithmetic expression within parentheses.

func (*ParenArithm) End

func (p *ParenArithm) End() Pos

func (*ParenArithm) Pos

func (p *ParenArithm) Pos() Pos

type ParenTest

type ParenTest struct {
	Lparen, Rparen Pos

	X TestExpr
}

ParenTest represents a test expression within parentheses.

func (*ParenTest) End

func (p *ParenTest) End() Pos

func (*ParenTest) Pos

func (p *ParenTest) Pos() Pos

type ParseError

type ParseError struct {
	Filename string
	Pos
	Text string

	Incomplete bool
}

ParseError represents an error found when parsing a source file, from which the parser cannot recover.

func (ParseError) Error

func (e ParseError) Error() string

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser holds the internal state of the parsing mechanism of a program.

func NewParser

func NewParser(options ...ParserOption) *Parser

NewParser allocates a new Parser and applies any number of options.

Example (Options)
package main

import (
	"fmt"
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	src := "for ((i = 0; i < 5; i++)); do echo $i >f; done"

	// LangBash is the default
	r := strings.NewReader(src)
	f, err := syntax.NewParser().Parse(r, "")
	fmt.Println(err)

	// Parser errors with LangPOSIX
	r = strings.NewReader(src)
	_, err = syntax.NewParser(syntax.Variant(syntax.LangPOSIX)).Parse(r, "")
	fmt.Println(err)

	syntax.NewPrinter().Print(os.Stdout, f)
	syntax.NewPrinter(syntax.SpaceRedirects(true)).Print(os.Stdout, f)

}
Output:

<nil>
1:5: c-style fors are a bash feature
for ((i = 0; i < 5; i++)); do echo $i >f; done
for ((i = 0; i < 5; i++)); do echo $i > f; done

func (*Parser) Arithmetic

func (p *Parser) Arithmetic(r io.Reader) (ArithmExpr, error)

Arithmetic parses a single arithmetic expression. That is, as if the input were within the $(( and )) tokens.

func (*Parser) Document

func (p *Parser) Document(r io.Reader) (*Word, error)

Document parses a single here-document word. That is, it parses the input as if they were lines following a <<EOF redirection.

In practice, this is the same as parsing the input as if it were within double quotes, but without having to escape all double quote characters. Similarly, the here-document word parsed here cannot be ended by any delimiter other than reaching the end of the input.

func (*Parser) Incomplete

func (p *Parser) Incomplete() bool

Incomplete reports whether the parser is waiting to read more bytes because it needs to finish properly parsing a statement.

It is only safe to call while the parser is blocked on a read. For an example use case, see the documentation for Parser.Interactive.

func (*Parser) Interactive

func (p *Parser) Interactive(r io.Reader, fn func([]*Stmt) bool) error

Interactive implements what is necessary to parse statements in an interactive shell. The parser will call the given function under two circumstances outlined below.

If a line containing any number of statements is parsed, the function will be called with said statements.

If a line ending in an incomplete statement is parsed, the function will be called with any fully parsed statents, and Parser.Incomplete will return true.

One can imagine a simple interactive shell implementation as follows:

fmt.Fprintf(os.Stdout, "$ ")
parser.Interactive(os.Stdin, func(stmts []*syntax.Stmt) bool {
        if parser.Incomplete() {
                fmt.Fprintf(os.Stdout, "> ")
                return true
        }
        run(stmts)
        fmt.Fprintf(os.Stdout, "$ ")
        return true
}

If the callback function returns false, parsing is stopped and the function is not called again.

func (*Parser) Parse

func (p *Parser) Parse(r io.Reader, name string) (*File, error)

Parse reads and parses a shell program with an optional name. It returns the parsed program if no issues were encountered. Otherwise, an error is returned. Reads from r are buffered.

Parse can be called more than once, but not concurrently. That is, a Parser can be reused once it is done working.

func (*Parser) Stmts

func (p *Parser) Stmts(r io.Reader, fn func(*Stmt) bool) error

Stmts reads and parses statements one at a time, calling a function each time one is parsed. If the function returns false, parsing is stopped and the function is not called again.

func (*Parser) Words

func (p *Parser) Words(r io.Reader, fn func(*Word) bool) error

Words reads and parses words one at a time, calling a function each time one is parsed. If the function returns false, parsing is stopped and the function is not called again.

Newlines are skipped, meaning that multi-line input will work fine. If the parser encounters a token that isn't a word, such as a semicolon, an error will be returned.

Note that the lexer doesn't currently tokenize spaces, so it may need to read a non-space byte such as a newline or a letter before finishing the parsing of a word. This will be fixed in the future.

type ParserOption

type ParserOption func(*Parser)

ParserOption is a function which can be passed to NewParser to alter its behaviour. To apply option to existing Parser call it directly, for example KeepComments(true)(parser).

func KeepComments

func KeepComments(enabled bool) ParserOption

KeepComments makes the parser parse comments and attach them to nodes, as opposed to discarding them.

func StopAt

func StopAt(word string) ParserOption

StopAt configures the lexer to stop at an arbitrary word, treating it as if it were the end of the input. It can contain any characters except whitespace, and cannot be over four bytes in size.

This can be useful to embed shell code within another language, as one can use a special word to mark the delimiters between the two.

As a word, it will only apply when following whitespace or a separating token. For example, StopAt("$$") will act on the inputs "foo $$" and "foo;$$", but not on "foo '$$'".

The match is done by prefix, so the example above will also act on "foo $$bar".

func Variant

func Variant(l LangVariant) ParserOption

Variant changes the shell language variant that the parser will accept.

The passed language variant must be one of the constant values defined in this package.

type Pos

type Pos struct {
	// contains filtered or unexported fields
}

Pos is a position within a shell source file.

func (Pos) After

func (p Pos) After(p2 Pos) bool

After reports whether the position p is after p2. It is a more expressive version of p.Offset() > p2.Offset().

func (Pos) Col

func (p Pos) Col() uint

Col returns the column number of the position, starting at 1. It counts in bytes.

Col is protected against overflows; if an input line has too many columns, extra columns will have a column number of 0, rendered as "?".

func (Pos) IsValid

func (p Pos) IsValid() bool

IsValid reports whether the position is valid. All positions in nodes returned by Parse are valid.

func (Pos) Line

func (p Pos) Line() uint

Line returns the line number of the position, starting at 1.

Line is protected against overflows; if an input has too many lines, extra lines will have a line number of 0, rendered as "?".

func (Pos) Offset

func (p Pos) Offset() uint

Offset returns the byte offset of the position in the original source file. Byte offsets start at 0.

func (Pos) String

func (p Pos) String() string

type Printer

type Printer struct {
	// contains filtered or unexported fields
}

Printer holds the internal state of the printing mechanism of a program.

func NewPrinter

func NewPrinter(opts ...PrinterOption) *Printer

NewPrinter allocates a new Printer and applies any number of options.

func (*Printer) Print

func (p *Printer) Print(w io.Writer, node Node) error

Print "pretty-prints" the given syntax tree node to the given writer. Writes to w are buffered.

The node types supported at the moment are *File, *Stmt, *Word, any Command node, and any WordPart node. A trailing newline will only be printed when a *File is used.

type PrinterOption

type PrinterOption func(*Printer)

PrinterOption is a function which can be passed to NewPrinter to alter its behaviour. To apply option to existing Printer call it directly, for example KeepPadding(true)(printer).

func BinaryNextLine

func BinaryNextLine(enabled bool) PrinterOption

BinaryNextLine will make binary operators appear on the next line when a binary command, such as a pipe, spans multiple lines. A backslash will be used.

func FunctionNextLine added in v3.1.0

func FunctionNextLine(enabled bool) PrinterOption

FunctionNextLine will place a function's opening braces on the next line.

func Indent

func Indent(spaces uint) PrinterOption

Indent sets the number of spaces used for indentation. If set to 0, tabs will be used instead.

func KeepPadding

func KeepPadding(enabled bool) PrinterOption

KeepPadding will keep most nodes and tokens in the same column that they were in the original source. This allows the user to decide how to align and pad their code with spaces.

Note that this feature is best-effort and will only keep the alignment stable, so it may need some human help the first time it is run.

func Minify

func Minify(enabled bool) PrinterOption

Minify will print programs in a way to save the most bytes possible. For example, indentation and comments are skipped, and extra whitespace is avoided when possible.

func SingleLine added in v3.3.0

func SingleLine(enabled bool) PrinterOption

SingleLine will attempt to print programs in one line. For example, lists of commands or nested blocks do not use newlines in this mode. Note that some newlines must still appear, such as those following comments or around here-documents.

Print's trailing newline when given a *File is not affected by this option.

func SpaceRedirects

func SpaceRedirects(enabled bool) PrinterOption

SpaceRedirects will put a space after most redirection operators. The exceptions are '>&', '<&', '>(', and '<('.

func SwitchCaseIndent

func SwitchCaseIndent(enabled bool) PrinterOption

SwitchCaseIndent will make switch cases be indented. As such, switch case bodies will be two levels deeper than the switch itself.

type ProcOperator

type ProcOperator token

func (ProcOperator) String

func (o ProcOperator) String() string

type ProcSubst

type ProcSubst struct {
	OpPos, Rparen Pos
	Op            ProcOperator

	Stmts []*Stmt
	Last  []Comment
}

ProcSubst represents a Bash process substitution.

This node will only appear with LangBash.

func (*ProcSubst) End

func (s *ProcSubst) End() Pos

func (*ProcSubst) Pos

func (s *ProcSubst) Pos() Pos

type QuoteError added in v3.4.0

type QuoteError struct {
	ByteOffset int
	Message    string
}

func (QuoteError) Error added in v3.4.0

func (e QuoteError) Error() string

type RedirOperator

type RedirOperator token

func (RedirOperator) String

func (o RedirOperator) String() string

type Redirect

type Redirect struct {
	OpPos Pos
	Op    RedirOperator
	N     *Lit  // fd>, or {varname}> in Bash
	Word  *Word // >word
	Hdoc  *Word // here-document body
}

Redirect represents an input/output redirection.

func (*Redirect) End

func (r *Redirect) End() Pos

func (*Redirect) Pos

func (r *Redirect) Pos() Pos

type Replace

type Replace struct {
	All        bool
	Orig, With *Word
}

Replace represents a search and replace expression inside a ParamExp.

type SglQuoted

type SglQuoted struct {
	Left, Right Pos
	Dollar      bool // $”
	Value       string
}

SglQuoted represents a string within single quotes.

func (*SglQuoted) End

func (q *SglQuoted) End() Pos

func (*SglQuoted) Pos

func (q *SglQuoted) Pos() Pos

type Slice

type Slice struct {
	Offset, Length ArithmExpr
}

Slice represents a character slicing expression inside a ParamExp.

This node will only appear in LangBash and LangMirBSDKorn.

type Stmt

type Stmt struct {
	Comments   []Comment
	Cmd        Command
	Position   Pos
	Semicolon  Pos  // position of ';', '&', or '|&', if any
	Negated    bool // ! stmt
	Background bool // stmt &
	Coprocess  bool // mksh's |&

	Redirs []*Redirect // stmt >a <b
}

Stmt represents a statement, also known as a "complete command". It is compromised of a command and other components that may come before or after it.

func (*Stmt) End

func (s *Stmt) End() Pos

func (*Stmt) Pos

func (s *Stmt) Pos() Pos

type Subshell

type Subshell struct {
	Lparen, Rparen Pos

	Stmts []*Stmt
	Last  []Comment
}

Subshell represents a series of commands that should be executed in a nested shell environment.

func (*Subshell) End

func (s *Subshell) End() Pos

func (*Subshell) Pos

func (s *Subshell) Pos() Pos

type TestClause

type TestClause struct {
	Left, Right Pos

	X TestExpr
}

TestClause represents a Bash extended test clause.

This node will only appear in LangBash and LangMirBSDKorn.

func (*TestClause) End

func (t *TestClause) End() Pos

func (*TestClause) Pos

func (t *TestClause) Pos() Pos

type TestDecl added in v3.2.0

type TestDecl struct {
	Position    Pos
	Description *Word
	Body        *Stmt
}

TestDecl represents the declaration of a Bats test function.

func (*TestDecl) End added in v3.2.0

func (f *TestDecl) End() Pos

func (*TestDecl) Pos added in v3.2.0

func (f *TestDecl) Pos() Pos

type TestExpr

type TestExpr interface {
	Node
	// contains filtered or unexported methods
}

TestExpr represents all nodes that form test expressions.

These are *BinaryTest, *UnaryTest, *ParenTest, and *Word.

type TimeClause

type TimeClause struct {
	Time        Pos
	PosixFormat bool
	Stmt        *Stmt
}

TimeClause represents a Bash time clause. PosixFormat corresponds to the -p flag.

This node will only appear in LangBash and LangMirBSDKorn.

func (*TimeClause) End

func (c *TimeClause) End() Pos

func (*TimeClause) Pos

func (c *TimeClause) Pos() Pos

type UnAritOperator

type UnAritOperator token

func (UnAritOperator) String

func (o UnAritOperator) String() string

type UnTestOperator

type UnTestOperator token

func (UnTestOperator) String

func (o UnTestOperator) String() string

type UnaryArithm

type UnaryArithm struct {
	OpPos Pos
	Op    UnAritOperator
	Post  bool
	X     ArithmExpr
}

UnaryArithm represents an unary arithmetic expression. The unary opearator may come before or after the sub-expression.

If Op is Inc or Dec, X will be a word with a single *Lit whose value is a valid name.

func (*UnaryArithm) End

func (u *UnaryArithm) End() Pos

func (*UnaryArithm) Pos

func (u *UnaryArithm) Pos() Pos

type UnaryTest

type UnaryTest struct {
	OpPos Pos
	Op    UnTestOperator
	X     TestExpr
}

UnaryTest represents a unary test expression. The unary opearator may come before or after the sub-expression.

func (*UnaryTest) End

func (u *UnaryTest) End() Pos

func (*UnaryTest) Pos

func (u *UnaryTest) Pos() Pos

type WhileClause

type WhileClause struct {
	WhilePos, DoPos, DonePos Pos
	Until                    bool

	Cond     []*Stmt
	CondLast []Comment
	Do       []*Stmt
	DoLast   []Comment
}

WhileClause represents a while or an until clause.

func (*WhileClause) End

func (w *WhileClause) End() Pos

func (*WhileClause) Pos

func (w *WhileClause) Pos() Pos

type Word

type Word struct {
	Parts []WordPart
}

Word represents a shell word, containing one or more word parts contiguous to each other. The word is delimeted by word boundaries, such as spaces, newlines, semicolons, or parentheses.

Example
package main

import (
	"fmt"
	"os"
	"strings"

	"mvdan.cc/sh/v3/syntax"
)

func main() {
	r := strings.NewReader("echo foo${bar}'baz'")
	f, err := syntax.NewParser().Parse(r, "")
	if err != nil {
		return
	}

	printer := syntax.NewPrinter()
	args := f.Stmts[0].Cmd.(*syntax.CallExpr).Args
	for i, word := range args {
		fmt.Printf("Word number %d:\n", i)
		for _, part := range word.Parts {
			fmt.Printf("%-20T - ", part)
			printer.Print(os.Stdout, part)
			fmt.Println()
		}
		fmt.Println()
	}

}
Output:

Word number 0:
*syntax.Lit          - echo

Word number 1:
*syntax.Lit          - foo
*syntax.ParamExp     - ${bar}
*syntax.SglQuoted    - 'baz'

func (*Word) End

func (w *Word) End() Pos

func (*Word) Lit

func (w *Word) Lit() string

Lit returns the word as a literal value, if the word consists of *Lit nodes only. An empty string is returned otherwise. Words with multiple literals, which can appear in some edge cases, are handled properly.

For example, the word "foo" will return "foo", but the word "foo${bar}" will return "".

func (*Word) Pos

func (w *Word) Pos() Pos

type WordIter

type WordIter struct {
	Name  *Lit
	InPos Pos // position of "in"
	Items []*Word
}

WordIter represents the iteration of a variable over a series of words in a for clause. If InPos is an invalid position, the "in" token was missing, so the iteration is over the shell's positional parameters.

func (*WordIter) End

func (w *WordIter) End() Pos

func (*WordIter) Pos

func (w *WordIter) Pos() Pos

type WordPart

type WordPart interface {
	Node
	// contains filtered or unexported methods
}

WordPart represents all nodes that can form part of a word.

These are *Lit, *SglQuoted, *DblQuoted, *ParamExp, *CmdSubst, *ArithmExp, *ProcSubst, and *ExtGlob.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL