Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

expand: improve support for array keys and values #893

Merged
merged 1 commit into from
Jul 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions expand/expand.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,31 +661,57 @@ func (cfg *Config) wordFields(wps []syntax.WordPart) ([][]fieldPart, error) {
}

// quotedElemFields returns the list of elements resulting from a quoted
// parameter expansion if it was in the form of ${*}, ${@}, ${foo[*], ${foo[@]},
// or ${!foo@}.
// parameter expansion that should be treated especially, like "${foo[@]}".
func (cfg *Config) quotedElemFields(pe *syntax.ParamExp) []string {
if pe == nil || pe.Length || pe.Width {
return nil
}
name := pe.Param.Value
if pe.Excl {
if pe.Names == syntax.NamesPrefixWords {
switch pe.Names {
case syntax.NamesPrefixWords: // "${!prefix@}"
return cfg.namesByPrefix(pe.Param.Value)
case syntax.NamesPrefix: // "${!prefix*}"
return nil
}
switch nodeLit(pe.Index) {
case "@": // "${!name[@]}"
switch vr := cfg.Env.Get(name); vr.Kind {
case Indexed:
keys := make([]string, 0, len(vr.Map))
for key := range vr.List {
keys = append(keys, strconv.Itoa(key))
}
return keys
case Associative:
keys := make([]string, 0, len(vr.Map))
for key := range vr.Map {
keys = append(keys, key)
}
return keys
}
}
return nil
}
name := pe.Param.Value
switch name {
case "*":
case "*": // "${*}"
return []string{cfg.ifsJoin(cfg.Env.Get(name).List)}
case "@":
case "@": // "${@}"
return cfg.Env.Get(name).List
}
switch nodeLit(pe.Index) {
case "@":
if vr := cfg.Env.Get(name); vr.Kind == Indexed {
case "@": // "${name[@]}"
switch vr := cfg.Env.Get(name); vr.Kind {
case Indexed:
return vr.List
case Associative:
elems := make([]string, 0, len(vr.Map))
for _, elem := range vr.Map {
elems = append(elems, elem)
}
return elems
}
case "*":
case "*": // "${name[*]}"
if vr := cfg.Env.Get(name); vr.Kind == Indexed {
return []string{cfg.ifsJoin(vr.List)}
}
Expand Down
8 changes: 5 additions & 3 deletions expand/param.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,18 +160,20 @@ func (cfg *Config) paramExp(pe *syntax.ParamExp) (string, error) {
strs = cfg.namesByPrefix(pe.Param.Value)
case orig.Kind == NameRef:
strs = append(strs, orig.Str)
case vr.Kind == Indexed:
case pe.Index != nil && vr.Kind == Indexed:
for i, e := range vr.List {
if e != "" {
strs = append(strs, strconv.Itoa(i))
}
}
case vr.Kind == Associative:
case pe.Index != nil && vr.Kind == Associative:
for k := range vr.Map {
strs = append(strs, k)
}
case !syntax.ValidName(str):
case vr.Kind == Unset:
return "", fmt.Errorf("invalid indirect expansion")
case str == "":
return "", nil
default:
vr = cfg.Env.Get(str)
strs = append(strs, vr.String())
Expand Down
37 changes: 37 additions & 0 deletions interp/interp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,23 @@ set +o pipefail
`a=(b); echo ${a[-2]}`,
"negative array index\nexit status 1 #JUSTERR",
},
// TODO: also test with gaps in arrays.
{
`a=([0]=' x ' [1]=' y '); for v in "${a[@]}"; do echo "$v"; done`,
" x \n y \n",
},
{
`a=([0]=' x ' [1]=' y '); for v in "${a[*]}"; do echo "$v"; done`,
" x y \n",
},
{
`a=([0]=' x ' [1]=' y '); for v in "${!a[@]}"; do echo "$v"; done`,
"0\n1\n",
},
{
`a=([0]=' x ' [1]=' y '); for v in "${!a[*]}"; do echo "$v"; done`,
"0 1\n",
},

// associative arrays
{
Expand Down Expand Up @@ -2353,6 +2370,22 @@ set +o pipefail
`a=(['x']=b); echo ${a['y']}`,
"\n #IGNORE bash requires -A",
},
{
`declare -A a=(['a 1']=' x ' ['b 2']=' y '); for v in "${a[@]}"; do echo "$v"; done | sort`,
" x \n y \n",
},
{
`declare -A a=(['a 1']=' x ' ['b 2']=' y '); for v in "${a[*]}"; do echo "$v"; done | sort`,
" x y \n",
},
{
`declare -A a=(['a 1']=' x ' ['b 2']=' y '); for v in "${!a[@]}"; do echo "$v"; done | sort`,
"a 1\nb 2\n",
},
{
`declare -A a=(['a 1']=' x ' ['b 2']=' y '); for v in "${!a[*]}"; do echo "$v"; done | sort`,
"a 1 b 2\n",
},

// weird assignments
{"a=b; a=(c d); echo ${a[@]}", "c d\n"},
Expand Down Expand Up @@ -2500,6 +2533,10 @@ set +o pipefail
// "declare -n foo_interp_missing=bar_interp_missing bar_interp_missing=baz; foo_interp_missing=xxx; echo $foo_interp_missing $bar_interp_missing; echo $baz",
// "xxx xxx\nxxx\n",
//},
{
"echo ${!@}-${!*}-${!1}; set -- foo_interp_missing; echo ${!@}-${!*}-${!1}; foo_interp_missing=value; echo ${!@}-${!*}-${!1}",
"--\n--\nvalue-value-value\n",
},

// read-only vars
{"declare -r foo_interp_missing=bar_interp_missing; echo $foo_interp_missing", "bar_interp_missing\n"},
Expand Down