Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
144 lines
3.3 KiB
144 lines
3.3 KiB
package shellquote |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"strings" |
|
"unicode/utf8" |
|
) |
|
|
|
var ( |
|
UnterminatedSingleQuoteError = errors.New("Unterminated single-quoted string") |
|
UnterminatedDoubleQuoteError = errors.New("Unterminated double-quoted string") |
|
UnterminatedEscapeError = errors.New("Unterminated backslash-escape") |
|
) |
|
|
|
var ( |
|
splitChars = " \n\t" |
|
singleChar = '\'' |
|
doubleChar = '"' |
|
escapeChar = '\\' |
|
doubleEscapeChars = "$`\"\n\\" |
|
) |
|
|
|
// Split splits a string according to /bin/sh's word-splitting rules. It |
|
// supports backslash-escapes, single-quotes, and double-quotes. Notably it does |
|
// not support the $'' style of quoting. It also doesn't attempt to perform any |
|
// other sort of expansion, including brace expansion, shell expansion, or |
|
// pathname expansion. |
|
// |
|
// If the given input has an unterminated quoted string or ends in a |
|
// backslash-escape, one of UnterminatedSingleQuoteError, |
|
// UnterminatedDoubleQuoteError, or UnterminatedEscapeError is returned. |
|
func Split(input string) (words []string, err error) { |
|
var buf bytes.Buffer |
|
words = make([]string, 0) |
|
|
|
for len(input) > 0 { |
|
// skip any splitChars at the start |
|
c, l := utf8.DecodeRuneInString(input) |
|
if strings.ContainsRune(splitChars, c) { |
|
input = input[l:] |
|
continue |
|
} |
|
|
|
var word string |
|
word, input, err = splitWord(input, &buf) |
|
if err != nil { |
|
return |
|
} |
|
words = append(words, word) |
|
} |
|
return |
|
} |
|
|
|
func splitWord(input string, buf *bytes.Buffer) (word string, remainder string, err error) { |
|
buf.Reset() |
|
|
|
raw: |
|
{ |
|
cur := input |
|
for len(cur) > 0 { |
|
c, l := utf8.DecodeRuneInString(cur) |
|
cur = cur[l:] |
|
if c == singleChar { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l]) |
|
input = cur |
|
goto single |
|
} else if c == doubleChar { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l]) |
|
input = cur |
|
goto double |
|
} else if c == escapeChar { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l]) |
|
input = cur |
|
goto escape |
|
} else if strings.ContainsRune(splitChars, c) { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l]) |
|
return buf.String(), cur, nil |
|
} |
|
} |
|
if len(input) > 0 { |
|
buf.WriteString(input) |
|
input = "" |
|
} |
|
goto done |
|
} |
|
|
|
escape: |
|
{ |
|
if len(input) == 0 { |
|
return "", "", UnterminatedEscapeError |
|
} |
|
c, l := utf8.DecodeRuneInString(input) |
|
if c == '\n' { |
|
// a backslash-escaped newline is elided from the output entirely |
|
} else { |
|
buf.WriteString(input[:l]) |
|
} |
|
input = input[l:] |
|
} |
|
goto raw |
|
|
|
single: |
|
{ |
|
i := strings.IndexRune(input, singleChar) |
|
if i == -1 { |
|
return "", "", UnterminatedSingleQuoteError |
|
} |
|
buf.WriteString(input[0:i]) |
|
input = input[i+1:] |
|
goto raw |
|
} |
|
|
|
double: |
|
{ |
|
cur := input |
|
for len(cur) > 0 { |
|
c, l := utf8.DecodeRuneInString(cur) |
|
cur = cur[l:] |
|
if c == doubleChar { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l]) |
|
input = cur |
|
goto raw |
|
} else if c == escapeChar { |
|
// bash only supports certain escapes in double-quoted strings |
|
c2, l2 := utf8.DecodeRuneInString(cur) |
|
cur = cur[l2:] |
|
if strings.ContainsRune(doubleEscapeChars, c2) { |
|
buf.WriteString(input[0 : len(input)-len(cur)-l-l2]) |
|
if c2 == '\n' { |
|
// newline is special, skip the backslash entirely |
|
} else { |
|
buf.WriteRune(c2) |
|
} |
|
input = cur |
|
} |
|
} |
|
} |
|
return "", "", UnterminatedDoubleQuoteError |
|
} |
|
|
|
done: |
|
return buf.String(), input, nil |
|
}
|
|
|