10 var envRe = regexp.MustCompile(`\$({[a-zA-Z0-9_]+}|[a-zA-Z0-9_]+)`)
12 func isSpace(r rune) bool {
14 case ' ', '\t', '\r', '\n':
20 func replaceEnv(s string) string {
21 return envRe.ReplaceAllStringFunc(s, func(s string) string {
34 func NewParser() *Parser {
38 func (p *Parser) Parse(line string) ([]string, error) {
41 var escaped, doubleQuoted, singleQuoted, backQuote, dollarQuote bool
48 for i, r := range line {
65 if singleQuoted || doubleQuoted || backQuote || dollarQuote {
70 args = append(args, buf)
79 if !singleQuoted && !doubleQuoted && !dollarQuote {
81 out, err := shellRun(backtick)
88 backQuote = !backQuote
91 backQuote = !backQuote
94 if !singleQuoted && !doubleQuoted && !backQuote {
96 out, err := shellRun(backtick)
103 dollarQuote = !dollarQuote
106 dollarQuote = !dollarQuote
109 if !singleQuoted && !doubleQuoted && !backQuote {
110 if !dollarQuote && len(buf) > 0 && buf == "$" {
115 return nil, errors.New("invalid command line string")
119 if !singleQuoted && !dollarQuote {
120 doubleQuoted = !doubleQuoted
124 if !doubleQuoted && !dollarQuote {
125 singleQuoted = !singleQuoted
128 case ';', '&', '|', '<', '>':
129 if !(escaped || singleQuoted || doubleQuoted || backQuote) {
137 if backQuote || dollarQuote {
138 backtick += string(r)
142 buf = replaceEnv(buf)
143 args = append(args, buf)
145 if escaped || singleQuoted || doubleQuoted || backQuote || dollarQuote {
146 return nil, errors.New("invalid command line string")
154 func Split(line string) ([]string, error) {
155 return NewParser().Parse(line)
158 func Join(args ...string) string {
160 for i, w := range args {
165 buf.WriteString("''")
169 for _, b := range w {
171 case ' ', '\t', '\r', '\n':
173 buf.WriteString(string(b))
175 buf.WriteString(string(b))