ncfx1099
ncfx1099

Reputation: 367

Why does nesting this awk command with eval produce a different result than running it?

I have this script that's designed to assign variables to commands that collect information about a system and then echo them back. This works very well for the first few commands, but the last one continues to return the value without "PRETTY_NAME=" stripped out of the output.

Is there some problem with this that I'm not seeing?

I have tried using grep to separate awk:

grep PRETTY_NAME /etc/*-release | awk -F '=' '{print $2}'

Using escaped quotes:

awk -F \"=\" '/PRETTY_NAME/ {print $2}' /etc/*-release

Whole block (edited somewhat for relevance)

declare -A CMDS=(
   [primaryMacAddress]="cat /sys/class/net/$(ip route show default | awk '/default/ {print $5}')/address" 
   [primaryIpAddress]="hostname --ip-address"
   [hostname]="hostname"
   [osType]="awk -F '=' '/PRETTY_NAME/ {print $2}' /etc/*-release"
   )

#This bit is actually nested in another function
for kpair in "${!CMDS[@]}" do
   echo "$kpair=\"$( eval ${CMDS[$kpair]} )\""
done 

Results when run from .sh file:

osType="PRETTY_NAME="Red Hat Enterprise Linux Server 7.4 (Maipo)""

expected:

osType=""Red Hat Enterprise Linux Server 7.4 (Maipo)""

When this command is run by itself, it seems to work as intended:

$ awk -F '=' '/PRETTY_NAME/ {print $2}' /etc/*-release

"Red Hat Enterprise Linux Server 7.4 (Maipo)"

Upvotes: 1

Views: 357

Answers (1)

Kaz
Kaz

Reputation: 58627

Because your Awk command is specified in double quotes, interior dollar signs are subject to special treatment: the $2 is treated as a parameter substitution by your shell, and so the array element doesn't store the text $2 but rather its expansion. The Awk interpreter never sees the $2 syntax.

However, you have a second problem in your command dispatcher. Your eval command does not prevent word splitting:

eval ${CMDS[$kpair]}

you want this:

eval "${CMDS[$kpair]}"

without the quotes, your command is arbitrarily chopped into fields on whitespace. Then eval catenates the pieces together, using one space between them, and evaluates the resulting syntax. The difference can be demonstrated with the following example:

$ cmd="awk '/foo/ { print \$1\"    \"\$2 }'"
$ echo 'foo a' | eval $cmd
foo a
$ echo 'foo a' | eval "$cmd"
foo    a

We can just use echo to understand the issue:

$ echo $cmd
awk '/foo/ { print $1" "$2 }'
$ echo "$cmd"
awk '/foo/ { print $1"    "$2 }'

The substitution of $cmd and the subsequent word splitting is done irrespective of any shell syntax that `cmd contains. We can see the pieces like this:

$ for x in $cmd ; do echo "<$x>" ; done
<awk>
<'/foo/>
<{>
<print>
<$1">
<"$2>
<}'>

When we execute eval $cmd, the above pieces are generated and re-combined by eval and evaluated. Needless to say, you don't want your command syntax to be chopped up and re-combined like this; who knows what sort of hidden bug will arise. It may be okay for the commands you have now, but as a generic command dispatch mechanism, it is flawed.

Upvotes: 1

Related Questions