4

Is there a possibility to write the following script without the loop?

IPv4_first=1.1.1.1
IPv4_second=2.2.2.2
IPv4_third=3.3.3.3

IPv4_all=() 

for var in ${!IPv4_@}
do
   IPv4_all+=(${!var})
done

printf "'%s'\n" "${IPv4_all[@]}"

Something like:

IPv4_all=${!${!IPv4_@}}
0

4 Answers 4

5

This might be the ugliest Bash code I've ever written, but...

IPv4_first=1.1.1.1
IPv4_second=2.2.2.2
IPv4_third=3.3.3.3

names=(${!IPv4_@})
eval "IPv4_all=(${names[@]/#/$})"
printf "'%s'\n" "${IPv4_all[@]}"

Look Ma, no loop!

${names[@]/#/$} prepends $ to the start of every element of the array, by matching an empty string anchored to the start of each element. That gives an array of variable dereferences, which we can expand inside eval to get the variable references inside the array initialiser. These need to be two separate lines because you can't apply multiple parameter expansions at the same time.

The output is:

'1.1.1.1'
'2.2.2.2'
'3.3.3.3'

as expected.

It's possible to replace the line with:

IPv4_all=($(eval "echo ${names[@]/#/$}"))

rather than evalling the array assignment. I'm not sure whether that's any better or not.

If your variable values might contain spaces or other IFS characters, you can change the eval:

eval "IPv4_all=($(printf '"$%s" ' "${names[@]}"))"

That properly double-quotes all the variable dereferences.

3
  • That assumes variable names or values don't contain IFS or wildcard characters. Commented Jul 25, 2014 at 10:55
  • I've edited in cover for the values with printf. If your variable names contain IFS characters I question your judgement, but you can modify the code accordingly. Commented Jul 25, 2014 at 11:09
  • IFS may have been modified beforehand (and contain underscore or digits or letters...). It's my policy to set IFS whenever I use the split+glob operator. Commented Jul 25, 2014 at 11:23
1

You don't need IPv4_all variable:

eval printf "\'%s\'\\\n" $(printf "$%s\n" ${!IPv4_@})

Output:

'1.1.1.1'
'2.2.2.2'
'3.3.3.3'
1

My competition entry on the ugliest/most convoluted bash code ;-):

eval 'declare(){ v=${2%%=*};[[ $v = IPv4_* ]]&&IPv4_all+=("${!v}");};'"$(declare -p)"
unset -f declare
0
1

This works for me, I think, though I'll accept that I could be missing some fundamental point:

IPv4_first=1.1.1.1
IPv4_second=2.2.2.2
IPv4_third=3.3.3.3

IPv4_all=( $(set | sed '/IPv4_.*[=)]/!d;s///') )

printf "'%s'\n" "${IPv4_all[@]}"

OUTPUT

'1.1.1.1'
'2.2.2.2'
'3.3.3.3'

This is better:

eval IPv4_all=( "$(set |
    grep -E '^IPv4_[_[:alnum:]]*=([^(]|$)' |
    sed 's/\([^=]*\).*/${\1+"$\1"} /')"
)

grep only gets safe lines that match your target var. sed surrounds them in parameter expansion tokens so they evaluate away to nothing if they're not actually current shell variable names.

8
  • That assumes the variable values are not multi-line and that you don't have variables whose value contains \nIPv4_...= or whose name contains (and doesn't start with) IPv4_. Commented Jul 25, 2014 at 10:42
  • Yeah - I just realized that - I have to evaluate them away - return only var names. I'm about to do that now.
    – mikeserv
    Commented Jul 25, 2014 at 10:46
  • @mikeserv: I can see what the s/// part is doing but I don't understand why... Can you explain it?
    – Ray
    Commented Jul 25, 2014 at 10:58
  • Umm... Actually, as it happens, I am, at the moment, very well disposed to do so, yes. I just answered a question about that: unix.stackexchange.com/a/146509/52934
    – mikeserv
    Commented Jul 25, 2014 at 11:19
  • 1
    A slightly more concise and legible idiom for sed '/.../!d;s///' is sed -n 's/...//p'. Commented Jul 25, 2014 at 11:37

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.