Shell – Listing shell variables with a fixed prefix

shellvariable

I've looked this up and can't find an answer, I apologise in advance if it's been previously asked.

I'm using shell on FreeBSD (/bin/sh) and I want to dump to stdout all shell (not environment) variables starting with _myvar_. The closest I can get is set | grep '^_myvar_' but that only dumps the first line of multiline variables (some will be multiline and I need them in full), and it could be error prone in pathological edge cases such as a string containing "myvar" that happens to line-break just before that part.

if I could list just variable names (not values), then I could filter within a do...read...while and get the values one at a time just for vars with matching names, but I can't find a way to do this. I also can't filter the full output, because there is no deterministic way to tell whether an output line contains a continuation or a new variable, that doesn't have edge-case issues with strings containing _myvar_, = or newline (\n) characters, or possibly, trailing spaces. I don't want to modify the environment, because the code is included in other code and the environment has to be stable for it.

It isn't a problem for the output/list to include any matching environment variables if that helps (if any exist – it's extremely unlikely they will)

Is there a way to do this?

Best Answer

As the set builtin of FreeBSD sh outputs in a format that is suitable for reinput to the shell, you can do:

out() case $1 in (_myvar_*) printf '%s\n' "${1%%=*}"; esac
eval "$(set | sed 's/^/out /')"

That is prefix each line of the output of set with "out " and have that evaluated as shell code (where out is a function that prints the substring of its first argument up to the first =).

sed would also insert "out " in the content of multiline variable, but that would still be included in the argument that our out function receives and past the first =, so in the part we're not displaying.

For instance, on a set output like:

TERM=xterm
USER=stephane
_myvar_foo='line1
line2
line3'

We would be evaluating:

out TERM=xterm
out USER=stephane
out _myvar_foo='line1
out line2
out line3'

But that's still fine as out is only called 3 times for those 3 variables.

To print both variable name and value:

out() case $1 in (_myvar_*)
  eval 'printf "name: \"%s\" value: \"%s\"\n" "${1%%=*}" "${'"${1%%=*}"'}"'
esac
eval "$(set | sed 's/^/out /')"

Note that it only outputs variables, not other types of parameters like $-, positional parameters...

That approach only works for sh implementations where set only outputs scalar variables (won't work for arrays or associative arrays or compound variables, where out var=(x) becomes a syntax error). Those shells that have other variable types often also have better introspection features.

In zsh:

typeset -pm '_myvar_*'

or for the names only

echo $parameters[(I)_myvar_*]

In bash:

v=("${!_myvar_@}"); ((${#v[@]})) && typeset -p -- "${v[@]}"
echo "${!_myvar_@}"
Related Question