This works as advertised:
# example 1
#!/bin/bash
grep -ir 'hello world' .
This doesn't:
# example 2
#!/bin/bash
argumentString="-ir 'hello world'"
grep $argumentString .
Despite 'hello world'
being enclosed by quotes in the second example, grep interprets 'hello
as one argument and world'
as another, which means that, in this case, 'hello
will be the search pattern and world'
will be the search path.
Again, this only happens when the arguments are expanded from the argumentString
variable. grep properly interprets 'hello world'
as a single argument in the first example.
Can anyone explain why this is? Is there a proper way to expand a string variable that will preserve the syntax of each character such that it is correctly interpreted by shell commands?
Why
When the string is expanded, it is split into words, but it is not re-evaluated to find special characters such as quotes or dollar signs or ... This is the way the shell has 'always' behaved, since the Bourne shell back in 1978 or thereabouts.
Fix
In
bash
, use an array to hold the arguments:Or, if brave/foolhardy, use
eval
:On the other hand, discretion is often the better part of valour, and working with
eval
is a place where discretion is better than bravery. If you are not completely in control of the string that iseval
'd (if there's any user input in the command string that has not been rigorously validated), then you are opening yourself to potentially serious problems.Note that the sequence of expansions for Bash is described in Shell Expansions in the GNU Bash manual. Note in particular sections 3.5.3 Shell Parameter Expansion, 3.5.7 Word Splitting, and 3.5.9 Quote Removal.
When you put quote characters into variables, they just become plain literals (see http://mywiki.wooledge.org/BashFAQ/050; thanks @tripleee for pointing out this link)
Instead, try using an array to pass your arguments:
Inside the double quotes, single quotes lose their special meaning so they're mere ordinary characters. For that to work, use something like
eval
:which should properly re-assemble the command line from the concatenated strings.
In looking at this and related questions, I'm surprised that no one brought up using an explicit subshell. For bash, and other modern shells, you can execute a command line explicitly. In bash, it requires the -c option.
Works exactly as original questioner desired. There are two restrictions to this technique:
Also, this technique handles redirection and piping, and other shellisms work as well. You also can use bash internal commands as well as any other command that works at the command line, because you are essentially asking a subshell bash to interpret it directly as a command line. Here's a more complex example, a somewhat gratuitously complex ls -l variant.
I have built command processors both this way and with parameter arrays. Generally, this way is much easier to write and debug, and it's trivial to echo the command you are executing. OTOH, param arrays work nicely when you really do have abstract arrays of parameters, as opposed to just wanting a simple command variant.