|
@@ -2,6 +2,7 @@ package shell
|
|
|
|
|
|
import (
|
|
import (
|
|
"bytes"
|
|
"bytes"
|
|
|
|
+ "fmt"
|
|
"strings"
|
|
"strings"
|
|
"text/scanner"
|
|
"text/scanner"
|
|
"unicode"
|
|
"unicode"
|
|
@@ -17,7 +18,9 @@ import (
|
|
// It doesn't support all flavors of ${xx:...} formats but new ones can
|
|
// It doesn't support all flavors of ${xx:...} formats but new ones can
|
|
// be added by adding code to the "special ${} format processing" section
|
|
// be added by adding code to the "special ${} format processing" section
|
|
type Lex struct {
|
|
type Lex struct {
|
|
- escapeToken rune
|
|
|
|
|
|
+ escapeToken rune
|
|
|
|
+ RawQuotes bool
|
|
|
|
+ SkipUnsetEnv bool
|
|
}
|
|
}
|
|
|
|
|
|
// NewLex creates a new Lex which uses escapeToken to escape quotes.
|
|
// NewLex creates a new Lex which uses escapeToken to escape quotes.
|
|
@@ -58,17 +61,21 @@ func (s *Lex) ProcessWordsWithMap(word string, env map[string]string) ([]string,
|
|
|
|
|
|
func (s *Lex) process(word string, env map[string]string) (string, []string, error) {
|
|
func (s *Lex) process(word string, env map[string]string) (string, []string, error) {
|
|
sw := &shellWord{
|
|
sw := &shellWord{
|
|
- envs: env,
|
|
|
|
- escapeToken: s.escapeToken,
|
|
|
|
|
|
+ envs: env,
|
|
|
|
+ escapeToken: s.escapeToken,
|
|
|
|
+ skipUnsetEnv: s.SkipUnsetEnv,
|
|
|
|
+ rawQuotes: s.RawQuotes,
|
|
}
|
|
}
|
|
sw.scanner.Init(strings.NewReader(word))
|
|
sw.scanner.Init(strings.NewReader(word))
|
|
return sw.process(word)
|
|
return sw.process(word)
|
|
}
|
|
}
|
|
|
|
|
|
type shellWord struct {
|
|
type shellWord struct {
|
|
- scanner scanner.Scanner
|
|
|
|
- envs map[string]string
|
|
|
|
- escapeToken rune
|
|
|
|
|
|
+ scanner scanner.Scanner
|
|
|
|
+ envs map[string]string
|
|
|
|
+ escapeToken rune
|
|
|
|
+ rawQuotes bool
|
|
|
|
+ skipUnsetEnv bool
|
|
}
|
|
}
|
|
|
|
|
|
func (sw *shellWord) process(source string) (string, []string, error) {
|
|
func (sw *shellWord) process(source string) (string, []string, error) {
|
|
@@ -103,10 +110,8 @@ func (w *wordsStruct) addRawChar(ch rune) {
|
|
}
|
|
}
|
|
|
|
|
|
func (w *wordsStruct) addString(str string) {
|
|
func (w *wordsStruct) addString(str string) {
|
|
- var scan scanner.Scanner
|
|
|
|
- scan.Init(strings.NewReader(str))
|
|
|
|
- for scan.Peek() != scanner.EOF {
|
|
|
|
- w.addChar(scan.Next())
|
|
|
|
|
|
+ for _, ch := range str {
|
|
|
|
+ w.addChar(ch)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -196,14 +201,20 @@ func (sw *shellWord) processSingleQuote() (string, error) {
|
|
|
|
|
|
var result bytes.Buffer
|
|
var result bytes.Buffer
|
|
|
|
|
|
- sw.scanner.Next()
|
|
|
|
|
|
+ ch := sw.scanner.Next()
|
|
|
|
+ if sw.rawQuotes {
|
|
|
|
+ result.WriteRune(ch)
|
|
|
|
+ }
|
|
|
|
|
|
for {
|
|
for {
|
|
- ch := sw.scanner.Next()
|
|
|
|
|
|
+ ch = sw.scanner.Next()
|
|
switch ch {
|
|
switch ch {
|
|
case scanner.EOF:
|
|
case scanner.EOF:
|
|
return "", errors.New("unexpected end of statement while looking for matching single-quote")
|
|
return "", errors.New("unexpected end of statement while looking for matching single-quote")
|
|
case '\'':
|
|
case '\'':
|
|
|
|
+ if sw.rawQuotes {
|
|
|
|
+ result.WriteRune(ch)
|
|
|
|
+ }
|
|
return result.String(), nil
|
|
return result.String(), nil
|
|
}
|
|
}
|
|
result.WriteRune(ch)
|
|
result.WriteRune(ch)
|
|
@@ -225,14 +236,20 @@ func (sw *shellWord) processDoubleQuote() (string, error) {
|
|
|
|
|
|
var result bytes.Buffer
|
|
var result bytes.Buffer
|
|
|
|
|
|
- sw.scanner.Next()
|
|
|
|
|
|
+ ch := sw.scanner.Next()
|
|
|
|
+ if sw.rawQuotes {
|
|
|
|
+ result.WriteRune(ch)
|
|
|
|
+ }
|
|
|
|
|
|
for {
|
|
for {
|
|
switch sw.scanner.Peek() {
|
|
switch sw.scanner.Peek() {
|
|
case scanner.EOF:
|
|
case scanner.EOF:
|
|
return "", errors.New("unexpected end of statement while looking for matching double-quote")
|
|
return "", errors.New("unexpected end of statement while looking for matching double-quote")
|
|
case '"':
|
|
case '"':
|
|
- sw.scanner.Next()
|
|
|
|
|
|
+ ch := sw.scanner.Next()
|
|
|
|
+ if sw.rawQuotes {
|
|
|
|
+ result.WriteRune(ch)
|
|
|
|
+ }
|
|
return result.String(), nil
|
|
return result.String(), nil
|
|
case '$':
|
|
case '$':
|
|
value, err := sw.processDollar()
|
|
value, err := sw.processDollar()
|
|
@@ -269,7 +286,11 @@ func (sw *shellWord) processDollar() (string, error) {
|
|
if name == "" {
|
|
if name == "" {
|
|
return "$", nil
|
|
return "$", nil
|
|
}
|
|
}
|
|
- return sw.getEnv(name), nil
|
|
|
|
|
|
+ value, found := sw.getEnv(name)
|
|
|
|
+ if !found && sw.skipUnsetEnv {
|
|
|
|
+ return "$" + name, nil
|
|
|
|
+ }
|
|
|
|
+ return value, nil
|
|
}
|
|
}
|
|
|
|
|
|
sw.scanner.Next()
|
|
sw.scanner.Next()
|
|
@@ -285,7 +306,11 @@ func (sw *shellWord) processDollar() (string, error) {
|
|
switch ch {
|
|
switch ch {
|
|
case '}':
|
|
case '}':
|
|
// Normal ${xx} case
|
|
// Normal ${xx} case
|
|
- return sw.getEnv(name), nil
|
|
|
|
|
|
+ value, found := sw.getEnv(name)
|
|
|
|
+ if !found && sw.skipUnsetEnv {
|
|
|
|
+ return fmt.Sprintf("${%s}", name), nil
|
|
|
|
+ }
|
|
|
|
+ return value, nil
|
|
case ':':
|
|
case ':':
|
|
// Special ${xx:...} format processing
|
|
// Special ${xx:...} format processing
|
|
// Yes it allows for recursive $'s in the ... spot
|
|
// Yes it allows for recursive $'s in the ... spot
|
|
@@ -301,19 +326,26 @@ func (sw *shellWord) processDollar() (string, error) {
|
|
|
|
|
|
// Grab the current value of the variable in question so we
|
|
// Grab the current value of the variable in question so we
|
|
// can use to to determine what to do based on the modifier
|
|
// can use to to determine what to do based on the modifier
|
|
- newValue := sw.getEnv(name)
|
|
|
|
|
|
+ newValue, found := sw.getEnv(name)
|
|
|
|
|
|
switch modifier {
|
|
switch modifier {
|
|
case '+':
|
|
case '+':
|
|
if newValue != "" {
|
|
if newValue != "" {
|
|
newValue = word
|
|
newValue = word
|
|
}
|
|
}
|
|
|
|
+ if !found && sw.skipUnsetEnv {
|
|
|
|
+ return fmt.Sprintf("${%s:%s%s}", name, string(modifier), word), nil
|
|
|
|
+ }
|
|
return newValue, nil
|
|
return newValue, nil
|
|
|
|
|
|
case '-':
|
|
case '-':
|
|
if newValue == "" {
|
|
if newValue == "" {
|
|
newValue = word
|
|
newValue = word
|
|
}
|
|
}
|
|
|
|
+ if !found && sw.skipUnsetEnv {
|
|
|
|
+ return fmt.Sprintf("${%s:%s%s}", name, string(modifier), word), nil
|
|
|
|
+ }
|
|
|
|
+
|
|
return newValue, nil
|
|
return newValue, nil
|
|
|
|
|
|
default:
|
|
default:
|
|
@@ -364,13 +396,13 @@ func isSpecialParam(char rune) bool {
|
|
return false
|
|
return false
|
|
}
|
|
}
|
|
|
|
|
|
-func (sw *shellWord) getEnv(name string) string {
|
|
|
|
|
|
+func (sw *shellWord) getEnv(name string) (string, bool) {
|
|
for key, value := range sw.envs {
|
|
for key, value := range sw.envs {
|
|
if EqualEnvKeys(name, key) {
|
|
if EqualEnvKeys(name, key) {
|
|
- return value
|
|
|
|
|
|
+ return value, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return ""
|
|
|
|
|
|
+ return "", false
|
|
}
|
|
}
|
|
|
|
|
|
func BuildEnvs(env []string) map[string]string {
|
|
func BuildEnvs(env []string) map[string]string {
|