views:

57

answers:

3

Using the following simplified code extract:

DIR='a b'
mount_command="./mount.cpfs $loop $DIR -f $OPTS"
sudo $mount_command

Executes this line when trace is on:

+ sudo ./mount.cpfs /dev/loop0 a b -f -o default_permissions,allow_other,attr_timeout=0

But DIR is not quoted, and so a and b are passed as different parameters, rather than the same to ./mount.cpfs.

What's the best way to go about creating a command sequence like this, and then expanding it into a later command line?

Please keep in mind the code example is simplified to the core problem, I'm using mount_command in several places with various additions before and after it, and DIR is passed in by the user. I've tried several combinations of quoting DIR when assigning to mount_command, and a primitive attempt at using an array.

Example Usage of mount_command

mount_command="./mount.cpfs $loop $DIR -f $OPTS"
case "$MODE" in
    gdb)
        sudo gdb -return-child-result -x gdbbatch \
            --args $mount_command
        ;;
    normal)
        sudo $mount_command
        ;;
    valgrind)
        sudo valgrind --track-fds=yes --leak-check=full --malloc-fill=0x80 \
            --free-fill=0xff $mount_command
        ;;
    *)
        echo "Mode '$MODE' unknown"
        mounted=''
        exit 2
        ;;
esac

Update0

Please test your suggestions, I don't think the solution is straightforward.

A: 
aularon
No this doesn't work: it generates this: `+ sudo ./mount.cpfs /dev/loop0 '"a' 'b"' -f -o default_permissions,allow_other,attr_timeout=0`
Matt Joiner
@Matt I edited adding another suggestion, try with it.
aularon
A nice suggestion but I'm not looking for hackery as intense as this :)
Matt Joiner
@aularon: now it'll generate `+ sudo ./mount.cpfs /dev/loop0 'a\' 'b' -f -o default_permissions,allow_other,attr_timeout=0` -- neither quoting nor escaping is parsed when word-splitting expanded variables, so there's nothing you can do in the variable itself to keep "a b" from being parsed as multiple words. I'll repeat Dennis Williamson's recommendation to read [BashFAQ/050](http://mywiki.wooledge.org/BashFAQ/050).
Gordon Davisson
+1  A: 

Try using eval.

Given the following script, called test:

#!/bin/bash
# test

echo $1

If I do this:

DIR='a b'
CMD=."/test \"$DIR\""
eval $CMD

It outputs

a b

See this question

Joel
This works but it's not as awesome as camh's answer. Also it requires putting `eval` on the command using `mount_command`, which is somewhat invasive. Is there a way to do this without using eval on the final command?
Matt Joiner
+3  A: 

The best way in bash is to use an array. The array will keep words separated that are meant to be separated and keep spaces inside individual words:

DIR='a b'
mount_command=(./mount.cpfs $loop "$DIR" -f $OPTS)
sudo "${mount_command[@]}"

When "${mount_command[@]}" is expanded, each element is expanded as a single argument to sudo even if it has spaces.

Note how I quoted "$DIR" but not $OPTS, as your $OPTS contains multiple words intended to be passed to the mount command as separate words but $DIR should be kept together. However, you could make OPTS an array in the same way as mount_command and expand it inside the mount_command definition as "${OPTS[@]}":

DIR='a b'
OPTS=(-o default_permissions,allow_other,attr_timeout=0)
mount_command=(./mount.cpfs $loop "$DIR" -f "${OPTS[@]}")
sudo "${mount_command[@])"

Having done lots of building of commands in bash scripts, I find arrays to be far superior to trying to figure out the correct quoting (if possible) to maintain the command in a simple string.

camh