Running a command from a variable with spaces in it
This is one of those annoying little problems I've had for a while and not been able to solve. I've used this construct in a couple of places and now I found I need it again. So I thought I'd find out how to do it right.
Here's the setup if you want to play along at home:
$
$ > hello
$ > "a b"
$ v="a b"
$
$ cmd="ls -l" ; set|grep ^cmd= ; $cmd
cmd='ls -l'
total 0
-rw-r--r-- 1 mrn staff 0 Jun 2 17:34 a b
-rw-r--r-- 1 mrn staff 0 Jun 2 17:34 hello
Nice and easy. Ok let's see if we can do the same thing but include listing the hello
file too:
$ cmd="ls -l hello" ; set|grep ^cmd= ; $cmd
cmd='ls -l hello'
-rw-r--r-- 1 mrn staff 0 Jun 2 17:34 hello
Ok, now we are cooking on gas! Let's go for the file with a space in it:
$ cmd="ls -l $v" ; set|grep ^cmd= ; $cmd
cmd='ls -l a b'
ls: a: No such file or directory
ls: b: No such file or directory
$
$ cmd="ls -l \"$v\"" ; set|grep ^cmd= ; $cmd
cmd='ls -l "a b"'
ls: "a: No such file or directory
ls: b": No such file or directory
$
$ cmd="ls -l '$v'" ; set|grep ^cmd= ; $cmd
cmd='ls -l '\''a b'\'''
ls: 'a: No such file or directory
ls: b': No such file or directory
$
$ cmd="ls -l \\\"$v\\\"" ; set|grep ^cmd= ; $cmd
cmd='ls -l \"a b\"'
ls: \"a: No such file or directory
ls: b\": No such file or directory
$
$ cmd="ls -l a\ b" ; set|grep ^cmd= ; $cmd
cmd='ls -l a\ b'
ls: a\: No such file or directory
ls: b: No such file or directory
Oh no! What's going on? From StackOverflow:
It's because variables get expanded partway through the process of parsing a command; because of this, the value of the variable is only partly parsed (it misses the earlier phases of parsing... like quote processing).
and from Bash FAQ:
Variables hold data. Functions hold code. Don't put code inside variables! There are many situations in which people try to shove commands, or command arguments, into variables and then run them. Each case needs to be handled separately.
For the simple case in bash, you can use an array to store arguments to pass to a command (similar to constructing a command using args only known at runtime)
Yes but what is the answer!!! Pretty simple really:
$ cmd=(ls -l "$v") ; set|grep ^cmd= ; "${cmd[@]}"
cmd=([0]="ls" [1]="-l" [2]="a b")
-rw-r--r-- 1 mrn staff 0 Jun 2 17:34 a b
$
or if you are doing it in a program then something like this should do the trick:
$ cmd=("ls" "-l" "$v") ; set|grep ^cmd= ; "${cmd[@]}"
No feedback yet
Form is loading...