7

I have a script something like this:

for chain in http https ssh
do
 iptables -nvxL $chain | tail -1 | awk '{print $2}'
done

But what I actually want to do is capture the output of the iptables command for each iteration into a different variable, whose name should be the equal to the current value of chain.

So for the first loop iteration (where $chain is http) I want this to happen:

http=$(iptables -nvxL http | tail -1 | awk '{print $2}')

Then for the next I want this:

https=$(iptables -nvxL https | tail -1 | awk '{print $2}')

Hopefully you get the idea, not sure how to do this.

AdminBee
  • 22,803
Aditya K
  • 2,060

3 Answers3

13

In your case, I would use an associative array for that:

declare -A rules

for chain in http https ssh do rules[$chain]=$(iptables -nvxL $chain | tail -1 | awk '{print $2}') done

You can then access the output by dereferencing, as in

printf -- "%s\n" "${rules['http']}"

or

for chain in http https ssh
do
    printf -- "%s\n" "${rules[$chain]}"
done
AdminBee
  • 22,803
2

The associative array answer is almost certainly how you want to do this.

However if you literally want variables named http, https, and ssh (that you would reference with $http, $https, and $ssh), then you can use printf -v to assign the command output to a variable whose name is contained in $chain:

for chain in http https ssh; do
    printf -v "$chain" '%s' "$(iptables -nvxL "$chain" | tail -1 | awk '{print $2}')"
done

Note also, as per this answer, you can use typeset or declare to achieve the same thing:

for chain in http https ssh; do
    typeset "$chain"="$(iptables -nvxL "$chain" | tail -1 | awk '{print $2}')"
done

No evals were harmed in the making of this answer. Also, if you want to print the variable values from a loop as in @penguin359's answer, you can use bash variable indirection to dereference the variable names - again, no evals needed:

for chain in http https ssh; do
    echo ${!chain}
done
2

Update: I forgot to quote the embedded $2 in the example. It should now work as requested by the OP. Also, the point behind this answer and the use of eval is to provide a portable example using standard Bourne shell syntax across multiple different implementations. I have tested all the following code across bash, dash, and zsh. Dash is a better test of portability as it uses a much more limited and more true to Bourne shell syntax. It is also the default shell on Debian/Ubuntu for /bin/sh. The typeset and printf -v do not exist in Dash. I do think eval is ugly, but it was the first mechanism in Bourne shell and is the best method for true portability.

While I think that associative arrays are the way to go, I want to provide an exact answer to his original question and one that is portable to other Bourne shells besides Bash. This here would be how dynamic variable names would be done traditionally:

for chain in http https ssh; do
    eval $chain="\"\$(iptables -nvxL $chain | tail -1 | awk '{print \$2}')\""
done

The eval operator in Bourne shell is used to substitute variables into a command and then re-run (re-evaluate) it as a command after that is complete. After the first substitution of that command, the $chain variable and first set of quoting is completed leaving it to look like this:

http="$(iptables -nvxL http | tail -1 | awk '{print $2}')"

Which then evaluates as a variable assignment to a command expansion of iptables. Since the inner command used single-quotes, I couldn't use them to quote the eval statement, hence the use of "\" and \"" to quote it for eval and to pass the double and single quotes through as needed in the resulting command. The double-quotes around the command expansion might not be needed, but I have run into issues with some shell if the command expands into multiple words. To dump out the contents of these, do something similar:

for chain in http https ssh; do
    eval echo \$$chain
done

The first dollar-sign is quoted and passed though. This results in the following three commands being executed after the loop is un-rolled:

echo $http
echo $https
echo $ssh
penguin359
  • 12,077
  • I'm sure you're aware, eval is usually considered evil, for its exciting code-insertion properties. In this case, we can do all of the above with printf/typeset/declare and variable indirection, instead of eval. – Digital Trauma Nov 23 '22 at 16:17
  • Yes, but it is not portable. I learned new things from your answer that I might use, but it does presume the use of Bash and is not a valid approach if you need to write a script that might run on other Bourne shell derivatives. Eval is hard to do right, but I wouldn't trust any shell script when privilege escalation might be a concern. – penguin359 Nov 23 '22 at 21:01