2018-01-03 19:12:40 +02:00
|
|
|
// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
|
|
|
|
// See LICENSE for licensing information
|
|
|
|
|
|
|
|
package interp
|
|
|
|
|
|
|
|
import (
|
2018-12-15 19:44:17 +02:00
|
|
|
"os"
|
2018-01-03 19:12:40 +02:00
|
|
|
"runtime"
|
2018-12-15 19:44:17 +02:00
|
|
|
"strconv"
|
2018-01-03 19:12:40 +02:00
|
|
|
"strings"
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
"mvdan.cc/sh/expand"
|
2018-01-03 19:12:40 +02:00
|
|
|
"mvdan.cc/sh/syntax"
|
|
|
|
)
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
type overlayEnviron struct {
|
|
|
|
parent expand.Environ
|
|
|
|
values map[string]expand.Variable
|
2018-04-28 20:35:15 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (o overlayEnviron) Get(name string) expand.Variable {
|
|
|
|
if vr, ok := o.values[name]; ok {
|
|
|
|
return vr
|
|
|
|
}
|
|
|
|
return o.parent.Get(name)
|
2018-04-28 20:35:15 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (o overlayEnviron) Set(name string, vr expand.Variable) {
|
|
|
|
o.values[name] = vr
|
2018-04-28 20:35:15 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (o overlayEnviron) Each(f func(name string, vr expand.Variable) bool) {
|
|
|
|
o.parent.Each(f)
|
|
|
|
for name, vr := range o.values {
|
|
|
|
if !f(name, vr) {
|
2018-04-28 20:35:15 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func execEnv(env expand.Environ) []string {
|
|
|
|
list := make([]string, 0, 32)
|
|
|
|
env.Each(func(name string, vr expand.Variable) bool {
|
|
|
|
if vr.Exported {
|
|
|
|
list = append(list, name+"="+vr.String())
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
2018-04-28 20:35:15 +02:00
|
|
|
return list
|
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) lookupVar(name string) expand.Variable {
|
|
|
|
if name == "" {
|
|
|
|
panic("variable name must not be empty")
|
|
|
|
}
|
|
|
|
var value interface{}
|
|
|
|
switch name {
|
|
|
|
case "#":
|
|
|
|
value = strconv.Itoa(len(r.Params))
|
|
|
|
case "@", "*":
|
|
|
|
value = r.Params
|
|
|
|
case "?":
|
|
|
|
value = strconv.Itoa(r.exit)
|
|
|
|
case "$":
|
|
|
|
value = strconv.Itoa(os.Getpid())
|
|
|
|
case "PPID":
|
|
|
|
value = strconv.Itoa(os.Getppid())
|
|
|
|
case "DIRSTACK":
|
|
|
|
value = r.dirStack
|
|
|
|
case "0":
|
|
|
|
if r.filename != "" {
|
|
|
|
value = r.filename
|
|
|
|
} else {
|
|
|
|
value = "gosh"
|
2018-04-28 20:35:15 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
case "1", "2", "3", "4", "5", "6", "7", "8", "9":
|
|
|
|
i := int(name[0] - '1')
|
|
|
|
if i < len(r.Params) {
|
|
|
|
value = r.Params[i]
|
|
|
|
} else {
|
|
|
|
value = ""
|
2018-04-28 20:35:15 +02:00
|
|
|
}
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if value != nil {
|
|
|
|
return expand.Variable{Value: value}
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if value, e := r.cmdVars[name]; e {
|
|
|
|
return expand.Variable{Value: value}
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if vr, e := r.funcVars[name]; e {
|
2018-12-15 19:44:17 +02:00
|
|
|
vr.Local = true
|
|
|
|
return vr
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if vr, e := r.Vars[name]; e {
|
2018-12-15 19:44:17 +02:00
|
|
|
return vr
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if vr := r.Env.Get(name); vr.IsSet() {
|
|
|
|
return vr
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
upper := strings.ToUpper(name)
|
2018-12-15 19:44:17 +02:00
|
|
|
if vr := r.Env.Get(upper); vr.IsSet() {
|
|
|
|
return vr
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
}
|
2018-04-28 20:35:15 +02:00
|
|
|
if r.opts[optNoUnset] {
|
2018-01-03 19:12:40 +02:00
|
|
|
r.errf("%s: unbound variable\n", name)
|
2018-09-01 16:00:49 +02:00
|
|
|
r.setErr(ShellExitStatus(1))
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
return expand.Variable{}
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) envGet(name string) string {
|
|
|
|
return r.lookupVar(name).String()
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) delVar(name string) {
|
2018-12-15 19:44:17 +02:00
|
|
|
vr := r.lookupVar(name)
|
|
|
|
if vr.ReadOnly {
|
2018-01-03 19:12:40 +02:00
|
|
|
r.errf("%s: readonly variable\n", name)
|
|
|
|
r.exit = 1
|
|
|
|
return
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if vr.Local {
|
|
|
|
// don't overwrite a non-local var with the same name
|
|
|
|
r.funcVars[name] = expand.Variable{}
|
|
|
|
} else {
|
|
|
|
r.Vars[name] = expand.Variable{} // to not query r.Env
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) setVarString(name, value string) {
|
|
|
|
r.setVar(name, nil, expand.Variable{Value: value})
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) setVarInternal(name string, vr expand.Variable) {
|
|
|
|
if _, ok := vr.Value.(string); ok {
|
2018-04-28 20:35:15 +02:00
|
|
|
if r.opts[optAllExport] {
|
2018-01-03 19:12:40 +02:00
|
|
|
vr.Exported = true
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vr.Exported = false
|
|
|
|
}
|
|
|
|
if vr.Local {
|
|
|
|
if r.funcVars == nil {
|
2018-12-15 19:44:17 +02:00
|
|
|
r.funcVars = make(map[string]expand.Variable)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
r.funcVars[name] = vr
|
|
|
|
} else {
|
|
|
|
r.Vars[name] = vr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable) {
|
|
|
|
cur := r.lookupVar(name)
|
2018-01-03 19:12:40 +02:00
|
|
|
if cur.ReadOnly {
|
|
|
|
r.errf("%s: readonly variable\n", name)
|
|
|
|
r.exit = 1
|
|
|
|
return
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if name2, var2 := cur.Resolve(r.Env); name2 != "" {
|
|
|
|
name = name2
|
|
|
|
cur = var2
|
|
|
|
vr.NameRef = false
|
|
|
|
cur.NameRef = false
|
|
|
|
}
|
|
|
|
_, isIndexArray := cur.Value.([]string)
|
|
|
|
_, isAssocArray := cur.Value.(map[string]string)
|
2018-01-03 19:12:40 +02:00
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
if _, ok := vr.Value.(string); ok && index == nil {
|
2018-01-03 19:12:40 +02:00
|
|
|
// When assigning a string to an array, fall back to the
|
|
|
|
// zero value for the index.
|
|
|
|
if isIndexArray {
|
|
|
|
index = &syntax.Word{Parts: []syntax.WordPart{
|
|
|
|
&syntax.Lit{Value: "0"},
|
|
|
|
}}
|
|
|
|
} else if isAssocArray {
|
|
|
|
index = &syntax.Word{Parts: []syntax.WordPart{
|
|
|
|
&syntax.DblQuoted{},
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if index == nil {
|
|
|
|
r.setVarInternal(name, vr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
// from the syntax package, we know that value must be a string if index
|
|
|
|
// is non-nil; nested arrays are forbidden.
|
|
|
|
valStr := vr.Value.(string)
|
2018-01-03 19:12:40 +02:00
|
|
|
|
|
|
|
// if the existing variable is already an AssocArray, try our best
|
|
|
|
// to convert the key to a string
|
2018-02-12 02:02:22 +02:00
|
|
|
if isAssocArray {
|
2018-12-15 19:44:17 +02:00
|
|
|
amap := cur.Value.(map[string]string)
|
2018-01-03 19:12:40 +02:00
|
|
|
w, ok := index.(*syntax.Word)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
k := r.literal(w)
|
2018-01-03 19:12:40 +02:00
|
|
|
amap[k] = valStr
|
|
|
|
cur.Value = amap
|
|
|
|
r.setVarInternal(name, cur)
|
|
|
|
return
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
var list []string
|
2018-01-03 19:12:40 +02:00
|
|
|
switch x := cur.Value.(type) {
|
2018-12-15 19:44:17 +02:00
|
|
|
case string:
|
|
|
|
list = append(list, x)
|
|
|
|
case []string:
|
2018-01-03 19:12:40 +02:00
|
|
|
list = x
|
2018-12-15 19:44:17 +02:00
|
|
|
case map[string]string: // done above
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
k := r.arithm(index)
|
2018-01-03 19:12:40 +02:00
|
|
|
for len(list) < k+1 {
|
|
|
|
list = append(list, "")
|
|
|
|
}
|
|
|
|
list[k] = valStr
|
|
|
|
cur.Value = list
|
|
|
|
r.setVarInternal(name, cur)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Runner) setFunc(name string, body *syntax.Stmt) {
|
|
|
|
if r.Funcs == nil {
|
|
|
|
r.Funcs = make(map[string]*syntax.Stmt, 4)
|
|
|
|
}
|
|
|
|
r.Funcs[name] = body
|
|
|
|
}
|
|
|
|
|
|
|
|
func stringIndex(index syntax.ArithmExpr) bool {
|
|
|
|
w, ok := index.(*syntax.Word)
|
|
|
|
if !ok || len(w.Parts) != 1 {
|
|
|
|
return false
|
|
|
|
}
|
2018-01-21 13:39:15 +02:00
|
|
|
switch w.Parts[0].(type) {
|
|
|
|
case *syntax.DblQuoted, *syntax.SglQuoted:
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
|
2018-12-15 19:44:17 +02:00
|
|
|
func (r *Runner) assignVal(as *syntax.Assign, valType string) interface{} {
|
|
|
|
prev := r.lookupVar(as.Name.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
if as.Naked {
|
|
|
|
return prev.Value
|
|
|
|
}
|
|
|
|
if as.Value != nil {
|
2018-12-15 19:44:17 +02:00
|
|
|
s := r.literal(as.Value)
|
|
|
|
if !as.Append || !prev.IsSet() {
|
|
|
|
return s
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
switch x := prev.Value.(type) {
|
2018-12-15 19:44:17 +02:00
|
|
|
case string:
|
|
|
|
return x + s
|
|
|
|
case []string:
|
2018-01-03 19:12:40 +02:00
|
|
|
if len(x) == 0 {
|
|
|
|
x = append(x, "")
|
|
|
|
}
|
|
|
|
x[0] += s
|
|
|
|
return x
|
2018-12-15 19:44:17 +02:00
|
|
|
case map[string]string:
|
2018-01-03 19:12:40 +02:00
|
|
|
// TODO
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
return s
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
if as.Array == nil {
|
2018-12-15 19:44:17 +02:00
|
|
|
// don't return nil, as that's an unset variable
|
|
|
|
return ""
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
elems := as.Array.Elems
|
|
|
|
if valType == "" {
|
|
|
|
if len(elems) == 0 || !stringIndex(elems[0].Index) {
|
|
|
|
valType = "-a" // indexed
|
|
|
|
} else {
|
|
|
|
valType = "-A" // associative
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if valType == "-A" {
|
|
|
|
// associative array
|
2018-12-15 19:44:17 +02:00
|
|
|
amap := make(map[string]string, len(elems))
|
2018-01-03 19:12:40 +02:00
|
|
|
for _, elem := range elems {
|
2018-12-15 19:44:17 +02:00
|
|
|
k := r.literal(elem.Index.(*syntax.Word))
|
|
|
|
amap[k] = r.literal(elem.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if !as.Append || !prev.IsSet() {
|
2018-01-03 19:12:40 +02:00
|
|
|
return amap
|
|
|
|
}
|
|
|
|
// TODO
|
|
|
|
return amap
|
|
|
|
}
|
|
|
|
// indexed array
|
|
|
|
maxIndex := len(elems) - 1
|
|
|
|
indexes := make([]int, len(elems))
|
|
|
|
for i, elem := range elems {
|
|
|
|
if elem.Index == nil {
|
|
|
|
indexes[i] = i
|
|
|
|
continue
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
k := r.arithm(elem.Index)
|
2018-01-03 19:12:40 +02:00
|
|
|
indexes[i] = k
|
|
|
|
if k > maxIndex {
|
|
|
|
maxIndex = k
|
|
|
|
}
|
|
|
|
}
|
|
|
|
strs := make([]string, maxIndex+1)
|
|
|
|
for i, elem := range elems {
|
2018-12-15 19:44:17 +02:00
|
|
|
strs[indexes[i]] = r.literal(elem.Value)
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
if !as.Append || !prev.IsSet() {
|
|
|
|
return strs
|
2018-01-03 19:12:40 +02:00
|
|
|
}
|
|
|
|
switch x := prev.Value.(type) {
|
2018-12-15 19:44:17 +02:00
|
|
|
case string:
|
|
|
|
return append([]string{x}, strs...)
|
|
|
|
case []string:
|
2018-01-03 19:12:40 +02:00
|
|
|
return append(x, strs...)
|
2018-12-15 19:44:17 +02:00
|
|
|
case map[string]string:
|
2018-01-03 19:12:40 +02:00
|
|
|
// TODO
|
|
|
|
}
|
2018-12-15 19:44:17 +02:00
|
|
|
return strs
|
2018-01-21 13:39:15 +02:00
|
|
|
}
|