44

Here is my small bash script snippet.

i=5
command='echo $i'
$command

I want this script to print 5 i.e., I want it to run echo and print 5. But it instead keeps printing $i. So how do I go about solving this?

posixKing
  • 1,157

1 Answers1

67

That would be:

eval "$command"

If you want the content of $command to be evaluated as shell code.

If you can't guarantee that $command won't start with - (which would cause eval in some shells like bash to treat it as an option), you may want to run:

eval " $command"

instead. That extra leading space won't affect the way the command is parsed and will prevent $command from being treated as an option to eval if it starts with -. eval -- "$command" would also work in some shells (including bash) but is not POSIX (IIRC) and wouldn't work in dash or the Bourne shell for instance.

Note that your $command should probably be:

command='echo "$i"'

Unless you did intend $i to be subject to split+glob

A potentially better way to store code in "variables" would be to use functions:

mycommand() { echo "$i"; }

(using mycommand instead of command, as command is already an existing command).

If $command is break/continue/return, behaviour will vary depending on the shell.

If you wanted $command to store a simple command, that is a list of words the first of which is looked up as the command to execute with the list of words as arguments, you'd use an array variable:

command=('echo' '$i' "$i")
"${command[@]}"

That would run echo, with echo, $i and the content of $i as arguments.

command='echo $i ;x /* '$i
$command

(with the default value of $IFS) is the one that would make littlest sense. There, $command would contain a string. The first $i left as is, the second expanded (as outside the single quotes), and then that string would be subject to split+glob (as $command is not inside double quotes), the outcome of which would result in a number of words, taken as a simple command again.

  • Wow thanks. I knew it was eval, but I was missing double quotes. – posixKing Apr 07 '17 at 09:09
  • 1
    Im guessing because you first suggested eval "$command" rather than just suggesting to change the quotes to ", i.e. i=5; command="echo $i"; $command which also would have worked, you prefer the eval method? personally I find using bash -c "$command" to be more reliable - assuming you dont need the command to be executed in current process – the_velour_fog Apr 07 '17 at 09:29
  • @the_velour_fog, command="echo $i"; $command is not the same thing. That's expanding $i into the value of $command and then apply split+glob to $command. That wouldn't work if $IFS was modified, that wouldn't work for command='cmd1; cmd2', etc. I can't see the point of using bash -c "$command" unless you do want that command to run in a separate bash instance. Here that wouldn't work unless you exported $i. – Stéphane Chazelas Apr 07 '17 at 09:34
  • 1
    ah ok, after re-reading my comment, I didnt explain very well, I meant I find bash -c "$command" more reliable than $command for the reasons you mentioned. the way I would use the commands is bash -c "$command" when you need to run as a separate instance, e.g. sudo -u $SUDO_USER bash -c "$command" , eval "$command" when you want to run in - and affect - the current process . – the_velour_fog Apr 07 '17 at 09:56
  • the suggestion to use a function instead was a huge help. thanks – FireDragon Jul 27 '18 at 00:27
  • But if $command has the following value: VAR1=foo VAR2=Lo23>-f, then echo $VAR2 displays Lo23. Any fix to that? – payne Dec 02 '21 at 19:00
  • 1
    @payne > is a redirection operator in the syntax of the shell, so you'd need it to be quoted if you want it to be part of the string that is assigned to the VAR2 variable: $command should contain VAR1=foo VAR2='Lo23>-f' for instance. – Stéphane Chazelas Dec 02 '21 at 20:09