2

TL; DR: is there a zsh equivalent of Ksh/Bash's "${!varnamepfx@}" expansion?

So that, for example, if I have the following variables set:

[...]
foo='random value'
bar=$'amazing\n value'
baz='that other value'
[...]

then by requesting printf -- %s\\n "${!ba@}" in Bash I get:

bar
baz

I've been perusing the zsh manual but haven't been able to find anything direct like the above syntax. The best I could resort to has been the following nested-expansion:

(for the example above)

printf -- %s\\n "${(M)${${(f)$(set)}[@]%%=*}[@]:#ba*}"

It seems to be doing the job reliably (at least on MacOS Catalina's zsh v5.3) but looks quite convoluted and I also wonder whether the $(set) Command Substitution in there really spawns a process or is instead optimized by zsh.

Admittedly, I have so far ruled out (and thus not looked into) getting that job done through the Completion System as it would seem a bit of an overkill for such a simple task.

2

Another option to match on the keys of the $parameter special associative array is to use the I subscript flag:

$ print -rC1 - $parameters[(I)ba*]
bar
baz

That's an associative array operator which results in list/array, while @Gille's ${(kM)parameters:#ba*} is a list/array operator applied to the keys of the associative array also resulting in a list/array. The end result is essentially the same in this case here.

If you have to remember only one, the ${(M)array:#pattern} which is grep-for-arrays is the more useful one as it's more generic. The things it can't do compared to array subscript flags is match on keys and return the corresponding values or match on values and return the corresponding keys.

You can combine both like in:

$ print -rC1 - ${(Mk)parameters[(R)array*]:#pa*}
path
patchars

Here to return the names of arrays that start with pa (values of $parameters are matched against array* and keys against pa*).

Of course, you can do that for all of zsh's introspection variables ($aliases, $commands, $functions, $builtins, $modules, $history...)

For some of them, you can also query using a -m pattern (match) option of the corresponding builtin (whence -m, alias -m, autoload -m, fc -m...), but like for bash's compgen, you can't reliably post-process their output as it's line-based while some of the things they report may contain newline characters.

2
  • Thanks. As I understand it, this idiom remains a direct expansion from array whereas Gilles' (and mine even more) has turned into text-manipulation. Hence eg this one can be simply double-quoted to obtain a $IFS-delimited expansion, whereas Gilles' and mine would require additional nesting/flags I suppose. – LL3 Mar 9 at 23:01
  • 1
    @LL3, not really, the ${array:#pattern} is filtering array members that match the pattern, you still get a list, there's no conversion to scalar. It's only that if you quote it without adding the @ flag, that becomes the text operator as you're no longer in list context. In any case, if you want to join array elements with some separator, it's better to use the j[sep] parameter expansion flag than having to rely on a global parameter like $IFS. print -r - ${(kMj[, ])parameters:#ba*} or print -r - ${(j[, ])parameters[(I)ba*]}. See also my edit. – Stéphane Chazelas Mar 10 at 7:47
2

Zsh doesn't have special syntax to filter variable names. Just combine the parameters associative array with parameter expansion features: ${(k)…} to get the keys (which are the parameter names), ${…:#…} to filter on a pattern, and ${(M)…} to keep matches rather than remove matches.

printf '%s\n' ${(kM)parameters:#ba*}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.