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