0

I have a following script where variables($1) inside variable($ARG) needs to be substituted:

#! /bin/bash

ARGS="-enable-kvm -hda /root/"$1".raw -display vnc=:"$1""

do_start()
{
          echo $ARGS
}

case "$1" in
  start)
        for i in {1..5}; do
          do_start $i
        done
        ;;
esac

However, if I execute the script with ./scriptname start then output is following:

-enable-kvm -hda /root/start.raw -display vnc=:start
-enable-kvm -hda /root/start.raw -display vnc=:start
-enable-kvm -hda /root/start.raw -display vnc=:start
-enable-kvm -hda /root/start.raw -display vnc=:start
-enable-kvm -hda /root/start.raw -display vnc=:start

How to substitute variable inside variable in case of bash?

2
  • place double quotes around $ARGS in definition of do_start() Commented Mar 27, 2015 at 17:04
  • 1
    Or, rather, don't. See BashFAQ #50: mywiki.wooledge.org/BashFAQ/050 Commented Mar 27, 2015 at 17:07

3 Answers 3

4

Use an array variable, per BashFAQ #50, defined inside the context where you know the value for $i:

#!/bin/bash

do_start() {
  local -a args=( -enable-kvm -hda "/root/$1.raw" -display vnc=:"$1" )
  printf '%q ' "${args[@]}"; echo # print args for the VM in shell-quoted form
  qemu "${args[@]}"               # actually start the VM
}

case "$1" in
  start)
    for ((i=1; i<=5; i++)); do
      do_start "$i"
    done
    ;;
esac

Note that this won't print literal quotes, but it doesn't need to (and, in fact, shouldn't: Content printed by echo is data; the quotes you care about are syntactical, and not data; so if echo $args printed quotes, this would mean that qemu $args wouldn't work correctly for reasons given in BashFAQ 50 -- as linked above -- which I strongly recommend reading).

Quoting will still be correct for content that needs quoting, though for integer values this will never be the case.

$ ./run-script start
-enable-kvm -hda /root/1.raw -display vnc=:1
-enable-kvm -hda /root/2.raw -display vnc=:2
-enable-kvm -hda /root/3.raw -display vnc=:3
-enable-kvm -hda /root/4.raw -display vnc=:4
-enable-kvm -hda /root/4.raw -display vnc=:5

Escaping the backslash before the space has the same effect as putting literal quotes around content: It still ensures that the filename is viewed as a single string.


If you then want to run qemu with your arguments:

qemu "${args[@]}"

...will do the trick correctly.

Sign up to request clarification or add additional context in comments.

Comments

1

You can fix it by declaring ARGS locally and fixing your quotes

do_start()
{
    ARGS="-enable-kvm -hda /root/$1.raw -display vnc=:"$1"
    echo "$ARGS"
}

Note that depending on the form your arguments are expected to take, this approach is prone to errors and a more robust way is shown by @Charles Duffy

4 Comments

This doesn't make the command actually work when $1 contains spaces, glob characters, etc. (And what was the point of quoting the $1 expansion in the first place if it couldn't?)
agreed and a different solution completely is probably the way to go. I was just showing how to fix it in the current scope and definition
One additional minor note, by the way -- ARGS is actually being declared as a global here, even though that declaration is inside a function. Using local or declare (inside a function) will make it actually a local.
I meant locally as where to define it, I was not referring to the scope. Unfortunate choice of words on my part :)
-1

First you have to change you double-quotes to single-quotes so that the $1 won't disappear on initial assignment. Then to get it evaluated at the time you want it to happen you can use the eval command.

#! /bin/bash

args=' -enable-kvm -hda "/root/$1.raw" -display vnc=:"$1" '

do_start()
{
    #echo "1=$1"
    eval "echo $args"
}

case "$1" in
  start)
        for i in {1..5}; do
          do_start $i
        done
        ;;
esac

Results:

$ ./foo.sh start
-enable-kvm -hda /root/1.raw -display vnc=:1
-enable-kvm -hda /root/2.raw -display vnc=:2
-enable-kvm -hda /root/3.raw -display vnc=:3
-enable-kvm -hda /root/4.raw -display vnc=:4
-enable-kvm -hda /root/5.raw -display vnc=:5

6 Comments

...if you let untrusted users set port numbers, for instance, consider the course of events if a port number were given containing 123$(rm -rf .)
It's not a security risk if your program is completely defining the value of the variable - it's not like you're getting it from the user or something.
It's bad practice/habit though. And as pointed out, it's unnecessary. Why use it if there are better alternatives?
Argh. I misread what the OP was doing here, and my above objection was based on same. This isn't as dangerous as I took it for -- though using eval unnecessarily is still something I object to on principal (as even when something is safe as originally written, code gets repurposed and used in new ways and scenarios, and not always with a new security evaluation).
If this is a script for the front-line defense of the DoD (yes, that's an exaggeration) then I'm 100% agreed. But if this is a program I'm running on my local system to make my life easy (about 99% of my use of bash scripts) then I would argue that (1) the ease/simplicity and (2) the fact that the input is absolutely checked before used make it that this is an acceptable use of bash. You're all right to caution the indiscriminate or habitual use, but I there are times when it is entirely acceptable and, depending on how the OP is going to use this, this seems to me to be a likely candidate.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.