I have a complex script that takes variables from files and uses them to run programs (Wine specifically)
Passing options from the variables in the other file isn't working as expected:
#!/bin/bash
. settingsfile
wine $run
And in the other file:
run="run.exe -withoption \"This text\""
When I change wine $run
to echo wine $run
, it echos a string, which when run explicitly works fine:
#!/bin/bash
. settingsfile
wine run.exe -withoption "This text"
Edit: Running with #!/bin/bash -x
shows me:
+ wine run.exe -withoption '"This' 'text"'
How do I fix this?
The problem is that
"This
andtext"
are treated as separate arguments, each containing a double-quote, rather than as a single argumentThis text
. You can see this if you write a function to print out one argument per line; this:prints this:
rather than this:
The simplest solution is to tack on an
eval
to re-process the quoting:But a more robust solution is to have
run
be an array, and then you can refer to it as"${run[@]}"
:so that the quoting is handled properly from the get-go.
Short answer: see BashFAQ #050: I'm trying to put a command in a variable, but the complex cases always fail!
Long answer: putting quotes in a variable doesn't do anything useful, because bash parses quotes before it replaces variable with their values; this means that by the time it's replaced
$run
withrun.exe -withoption "This text"
it's already done quote parsing, and isn't going to go back and notice those double-quotes, which means they don't do what you expect.BTW, using
echo
to check what's happening is extremely misleading, becauseecho
shows you what its arguments looked like after they're parsed, but you're reading it as though it was showing them before parsing. Instead, use @Eduardo's suggestion of-x
to see what's really going. (BTW, you can also useset -x
to turn that on for the interesting part of the script, thenset +x
to turn it off.)So how do you solve it? Generally the best way is to put your command in an array rather than a simple variable, and then use the idiom
"${arrayname[@]}"
to pass each array element as a separate argument: