Reputation: 41251
Is there more elegant way of doing lazy evaluation than the following:
pattern='$x and $y' x=1 y=2 eval "echo $pattern"
results:
1 and 2
It works but eval "echo ..."
just feels sloppy and may be insecure in some way. Is there a better way to do this in Bash?
Upvotes: 17
Views: 14500
Reputation: 1508
I know this question was from a long time ago, but the information was useful to me recently, so here is what I found.
This is actually much more straightforward now within bash. In version 4.4 a new feature called "Parameter Transformation" was introduced that solves this problem directly.
With parameter transformation, a number of transformations are possible by appending @{letter-code}
to the variable name within the curly braces (For example: ${MYVAR@P}
). The letter-code you use determines which transformation you wish to use, and the transformation associated with the letter-code P
is prompt-string transformation. (For details on other transformations, consult the bash online documentation on parameter expansion.)
What that means is that any string that could previously be evaluated within your $PS1
prompt variable can also be evaluated within your regular variables by using this transformation. Here are some examples:
# First example: Not extremely useful, since neither value changes after login
bash> USERATHOST='\u@\h'
bash> echo ${USERATHOST}
\u@\h
bash> echo ${USERATHOST@P}
root@mysystem
# More interesting example: Dynamically adjusting a variable based on changes in OS.
# Using single-quotes or escaping $-chars inside double quotes, expansion is delayed.
bash> REALJAVAHOME='$(dirname $(dirname $(realpath $(which java))))'
bash> echo ${REALJAVAHOME}
$(dirname $(dirname $(realpath $(which java))))
bash> echo ${REALJAVAHOME@P}
/usr/lib/jvm/java-17-openjdk-amd64
bash> echo 2 | update-alternatives --config java &>/dev/null
bash> echo ${REALJAVAHOME@P}
/usr/lib/jvm/java-11-openjdk-amd64
bash> echo 3 | update-alternatives --config java &>/dev/null
bash> echo ${REALJAVAHOME@P}
/usr/lib/jvm/java-17-openjdk-amd64
Sadly this didn't solve my problem, which was trying to find a way to make $JAVA_HOME
itself dynamically reflect the JDK folder matching the java
binary currently set by update-alternatives
. However, for any situation where you can control how the variable is being called (i.e., using it in your own scripts), this works like a charm.
Upvotes: 2
Reputation: 67900
You can use the command envsubst from gettext, for example:
$ pattern='x=$x and y=$y'
$ x=1 y=2 envsubst <<< $pattern
x=1 and y=2
Upvotes: 11
Reputation: 46883
One safe possibility is to use a function:
expand_pattern() {
pattern="$x and $y"
}
That's all. Then use as follows:
x=1 y=1
expand_pattern
echo "$pattern"
You can even use x
and y
as environment variables (so that they are not set in the main scope):
x=1 y=1 expand_pattern
echo "$pattern"
Upvotes: 9
Reputation: 360535
You're right, eval
is a security risk in this case. Here is one possible approach:
pattern='The $a is $b when the $z is $x $c $g.' # simulated input from user (use "read")
unset results
for word in $pattern
do
case $word in
\$a)
results+=($(some_command)) # add output of some_command to array (output is "werewolf"
;;
\$b)
results+=($(echo "active"))
;;
\$c)
results+=($(echo "and"))
;;
\$g)
results+=($(echo "the sky is clear"))
;;
\$x)
results+=($(echo "full"))
;;
\$z)
results+=($(echo "moon"))
;;
*)
do_something # count the non-vars, do a no-op, twiddle thumbs
# perhaps even sanitize %placeholders, terminal control characters, other unwanted stuff that the user might try to slip in
;;
esac
done
pattern=${pattern//\$[abcgxz]/%s} # replace the vars with printf string placeholders
printf "$pattern\n" "${results[@]}" # output the values of the vars using the pattern
printf -v sentence "$pattern\n" "${results[@]}" # put it into a variable called "sentence" instead of actually printing it
The output would be "The werewolf is active when the moon is full and the sky is clear." The very same program, if the pattern is 'The $x $z is out $c $g, so the $a must be $b.' then the output would be "The full moon is out and the sky is clear, so the werewolf must be active."
Upvotes: 0