1// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc> 2// See LICENSE for licensing information 3 4package interp 5 6import ( 7 "os" 8 "runtime" 9 "strconv" 10 "strings" 11 12 "mvdan.cc/sh/v3/expand" 13 "mvdan.cc/sh/v3/syntax" 14) 15 16type overlayEnviron struct { 17 parent expand.Environ 18 values map[string]expand.Variable 19} 20 21func (o overlayEnviron) Get(name string) expand.Variable { 22 if vr, ok := o.values[name]; ok { 23 return vr 24 } 25 return o.parent.Get(name) 26} 27 28func (o overlayEnviron) Set(name string, vr expand.Variable) { 29 o.values[name] = vr 30} 31 32func (o overlayEnviron) Each(f func(name string, vr expand.Variable) bool) { 33 o.parent.Each(f) 34 for name, vr := range o.values { 35 if !f(name, vr) { 36 return 37 } 38 } 39} 40 41func execEnv(env expand.Environ) []string { 42 list := make([]string, 0, 64) 43 env.Each(func(name string, vr expand.Variable) bool { 44 if !vr.IsSet() { 45 // If a variable is set globally but unset in the 46 // runner, we need to ensure it's not part of the final 47 // list. Seems like zeroing the element is enough. 48 // This is a linear search, but this scenario should be 49 // rare, and the number of variables shouldn't be large. 50 for i, kv := range list { 51 if strings.HasPrefix(kv, name+"=") { 52 list[i] = "" 53 } 54 } 55 } 56 if vr.Exported { 57 list = append(list, name+"="+vr.String()) 58 } 59 return true 60 }) 61 return list 62} 63 64func (r *Runner) lookupVar(name string) expand.Variable { 65 if name == "" { 66 panic("variable name must not be empty") 67 } 68 var vr expand.Variable 69 switch name { 70 case "#": 71 vr.Kind, vr.Str = expand.String, strconv.Itoa(len(r.Params)) 72 case "@", "*": 73 vr.Kind, vr.List = expand.Indexed, r.Params 74 case "?": 75 vr.Kind, vr.Str = expand.String, strconv.Itoa(r.lastExit) 76 case "$": 77 vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getpid()) 78 case "PPID": 79 vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getppid()) 80 case "DIRSTACK": 81 vr.Kind, vr.List = expand.Indexed, r.dirStack 82 case "0": 83 vr.Kind = expand.String 84 if r.filename != "" { 85 vr.Str = r.filename 86 } else { 87 vr.Str = "gosh" 88 } 89 case "1", "2", "3", "4", "5", "6", "7", "8", "9": 90 vr.Kind = expand.String 91 i := int(name[0] - '1') 92 if i < len(r.Params) { 93 vr.Str = r.Params[i] 94 } else { 95 vr.Str = "" 96 } 97 } 98 if vr.IsSet() { 99 return vr 100 } 101 if value, e := r.cmdVars[name]; e { 102 return expand.Variable{Kind: expand.String, Str: value} 103 } 104 if vr, e := r.funcVars[name]; e { 105 vr.Local = true 106 return vr 107 } 108 if vr, e := r.Vars[name]; e { 109 return vr 110 } 111 if vr := r.Env.Get(name); vr.IsSet() { 112 return vr 113 } 114 if runtime.GOOS == "windows" { 115 upper := strings.ToUpper(name) 116 if vr := r.Env.Get(upper); vr.IsSet() { 117 return vr 118 } 119 } 120 if r.opts[optNoUnset] { 121 r.errf("%s: unbound variable\n", name) 122 r.exit = 1 123 r.exitShell = true 124 } 125 return expand.Variable{} 126} 127 128func (r *Runner) envGet(name string) string { 129 return r.lookupVar(name).String() 130} 131 132func (r *Runner) delVar(name string) { 133 vr := r.lookupVar(name) 134 if vr.ReadOnly { 135 r.errf("%s: readonly variable\n", name) 136 r.exit = 1 137 return 138 } 139 if vr.Local { 140 // don't overwrite a non-local var with the same name 141 r.funcVars[name] = expand.Variable{} 142 } else { 143 r.Vars[name] = expand.Variable{} // to not query r.Env 144 } 145} 146 147func (r *Runner) setVarString(name, value string) { 148 r.setVar(name, nil, expand.Variable{Kind: expand.String, Str: value}) 149} 150 151func (r *Runner) setVarInternal(name string, vr expand.Variable) { 152 if vr.Kind == expand.String { 153 if r.opts[optAllExport] { 154 vr.Exported = true 155 } 156 } else { 157 vr.Exported = false 158 } 159 if vr.Local { 160 if r.funcVars == nil { 161 r.funcVars = make(map[string]expand.Variable) 162 } 163 r.funcVars[name] = vr 164 } else { 165 r.Vars[name] = vr 166 } 167} 168 169func (r *Runner) setVar(name string, index syntax.ArithmExpr, vr expand.Variable) { 170 cur := r.lookupVar(name) 171 if cur.ReadOnly { 172 r.errf("%s: readonly variable\n", name) 173 r.exit = 1 174 return 175 } 176 if name2, var2 := cur.Resolve(r.Env); name2 != "" { 177 name = name2 178 cur = var2 179 } 180 181 if vr.Kind == expand.String && index == nil { 182 // When assigning a string to an array, fall back to the 183 // zero value for the index. 184 switch cur.Kind { 185 case expand.Indexed: 186 index = &syntax.Word{Parts: []syntax.WordPart{ 187 &syntax.Lit{Value: "0"}, 188 }} 189 case expand.Associative: 190 index = &syntax.Word{Parts: []syntax.WordPart{ 191 &syntax.DblQuoted{}, 192 }} 193 } 194 } 195 if index == nil { 196 r.setVarInternal(name, vr) 197 return 198 } 199 200 // from the syntax package, we know that value must be a string if index 201 // is non-nil; nested arrays are forbidden. 202 valStr := vr.Str 203 204 var list []string 205 switch cur.Kind { 206 case expand.String: 207 list = append(list, cur.Str) 208 case expand.Indexed: 209 list = cur.List 210 case expand.Associative: 211 // if the existing variable is already an AssocArray, try our 212 // best to convert the key to a string 213 w, ok := index.(*syntax.Word) 214 if !ok { 215 return 216 } 217 k := r.literal(w) 218 cur.Map[k] = valStr 219 r.setVarInternal(name, cur) 220 return 221 } 222 k := r.arithm(index) 223 for len(list) < k+1 { 224 list = append(list, "") 225 } 226 list[k] = valStr 227 cur.Kind = expand.Indexed 228 cur.List = list 229 r.setVarInternal(name, cur) 230} 231 232func (r *Runner) setFunc(name string, body *syntax.Stmt) { 233 if r.Funcs == nil { 234 r.Funcs = make(map[string]*syntax.Stmt, 4) 235 } 236 r.Funcs[name] = body 237} 238 239func stringIndex(index syntax.ArithmExpr) bool { 240 w, ok := index.(*syntax.Word) 241 if !ok || len(w.Parts) != 1 { 242 return false 243 } 244 switch w.Parts[0].(type) { 245 case *syntax.DblQuoted, *syntax.SglQuoted: 246 return true 247 } 248 return false 249} 250 251func (r *Runner) assignVal(as *syntax.Assign, valType string) expand.Variable { 252 prev := r.lookupVar(as.Name.Value) 253 if as.Naked { 254 return prev 255 } 256 if as.Value != nil { 257 s := r.literal(as.Value) 258 if !as.Append || !prev.IsSet() { 259 prev.Kind = expand.String 260 if valType == "-n" { 261 prev.Kind = expand.NameRef 262 } 263 prev.Str = s 264 return prev 265 } 266 switch prev.Kind { 267 case expand.String: 268 prev.Str += s 269 case expand.Indexed: 270 if len(prev.List) == 0 { 271 prev.List = append(prev.List, "") 272 } 273 prev.List[0] += s 274 case expand.Associative: 275 // TODO 276 } 277 return prev 278 } 279 if as.Array == nil { 280 // don't return the zero value, as that's an unset variable 281 prev.Kind = expand.String 282 if valType == "-n" { 283 prev.Kind = expand.NameRef 284 } 285 prev.Str = "" 286 return prev 287 } 288 elems := as.Array.Elems 289 if valType == "" { 290 valType = "-a" // indexed 291 if len(elems) > 0 && stringIndex(elems[0].Index) { 292 valType = "-A" // associative 293 } 294 } 295 if valType == "-A" { 296 amap := make(map[string]string, len(elems)) 297 for _, elem := range elems { 298 k := r.literal(elem.Index.(*syntax.Word)) 299 amap[k] = r.literal(elem.Value) 300 } 301 if !as.Append { 302 prev.Kind = expand.Associative 303 prev.Map = amap 304 return prev 305 } 306 // TODO 307 return prev 308 } 309 maxIndex := len(elems) - 1 310 indexes := make([]int, len(elems)) 311 for i, elem := range elems { 312 if elem.Index == nil { 313 indexes[i] = i 314 continue 315 } 316 k := r.arithm(elem.Index) 317 indexes[i] = k 318 if k > maxIndex { 319 maxIndex = k 320 } 321 } 322 strs := make([]string, maxIndex+1) 323 for i, elem := range elems { 324 strs[indexes[i]] = r.literal(elem.Value) 325 } 326 if !as.Append { 327 prev.Kind = expand.Indexed 328 prev.List = strs 329 return prev 330 } 331 switch prev.Kind { 332 case expand.String: 333 prev.Kind = expand.Indexed 334 prev.List = append([]string{prev.Str}, strs...) 335 case expand.Indexed: 336 prev.List = append(prev.List, strs...) 337 case expand.Associative: 338 // TODO 339 } 340 return prev 341} 342