aeb17182b4
Signed-off-by: Emile Vauge <emile@vauge.com>
145 lines
2.3 KiB
Go
145 lines
2.3 KiB
Go
package shellwords
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"regexp"
|
|
)
|
|
|
|
var (
|
|
ParseEnv bool = false
|
|
ParseBacktick bool = false
|
|
)
|
|
|
|
var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
|
|
|
|
func isSpace(r rune) bool {
|
|
switch r {
|
|
case ' ', '\t', '\r', '\n':
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func replaceEnv(s string) string {
|
|
return envRe.ReplaceAllStringFunc(s, func(s string) string {
|
|
s = s[1:]
|
|
if s[0] == '{' {
|
|
s = s[1 : len(s)-1]
|
|
}
|
|
return os.Getenv(s)
|
|
})
|
|
}
|
|
|
|
type Parser struct {
|
|
ParseEnv bool
|
|
ParseBacktick bool
|
|
Position int
|
|
}
|
|
|
|
func NewParser() *Parser {
|
|
return &Parser{ParseEnv, ParseBacktick, 0}
|
|
}
|
|
|
|
func (p *Parser) Parse(line string) ([]string, error) {
|
|
args := []string{}
|
|
buf := ""
|
|
var escaped, doubleQuoted, singleQuoted, backQuote bool
|
|
backtick := ""
|
|
|
|
pos := -1
|
|
got := false
|
|
|
|
loop:
|
|
for i, r := range line {
|
|
if escaped {
|
|
buf += string(r)
|
|
escaped = false
|
|
continue
|
|
}
|
|
|
|
if r == '\\' {
|
|
if singleQuoted {
|
|
buf += string(r)
|
|
} else {
|
|
escaped = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
if isSpace(r) {
|
|
if singleQuoted || doubleQuoted || backQuote {
|
|
buf += string(r)
|
|
backtick += string(r)
|
|
} else if got {
|
|
if p.ParseEnv {
|
|
buf = replaceEnv(buf)
|
|
}
|
|
args = append(args, buf)
|
|
buf = ""
|
|
got = false
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case '`':
|
|
if !singleQuoted && !doubleQuoted {
|
|
if p.ParseBacktick {
|
|
if backQuote {
|
|
out, err := shellRun(backtick)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
buf = out
|
|
}
|
|
backtick = ""
|
|
backQuote = !backQuote
|
|
continue
|
|
}
|
|
backtick = ""
|
|
backQuote = !backQuote
|
|
}
|
|
case '"':
|
|
if !singleQuoted {
|
|
doubleQuoted = !doubleQuoted
|
|
continue
|
|
}
|
|
case '\'':
|
|
if !doubleQuoted {
|
|
singleQuoted = !singleQuoted
|
|
continue
|
|
}
|
|
case ';', '&', '|', '<', '>':
|
|
if !(escaped || singleQuoted || doubleQuoted || backQuote) {
|
|
pos = i
|
|
break loop
|
|
}
|
|
}
|
|
|
|
got = true
|
|
buf += string(r)
|
|
if backQuote {
|
|
backtick += string(r)
|
|
}
|
|
}
|
|
|
|
if got {
|
|
if p.ParseEnv {
|
|
buf = replaceEnv(buf)
|
|
}
|
|
args = append(args, buf)
|
|
}
|
|
|
|
if escaped || singleQuoted || doubleQuoted || backQuote {
|
|
return nil, errors.New("invalid command line string")
|
|
}
|
|
|
|
p.Position = pos
|
|
|
|
return args, nil
|
|
}
|
|
|
|
func Parse(line string) ([]string, error) {
|
|
return NewParser().Parse(line)
|
|
}
|