1

I want to do something like this.

$ touch a\ b
$ cmd=cat\ a\ b
$ echo $cmd
cat a b
$cmd
cat: a: No such file or directory
cat: b: No such file or directory

There are problems with the spaces in the file a b. So I tried this.

$ cmd=cat\ a\\\ b
$ echo $cmd
cat a\ b
$ $cmd
cat: a\': No such file or directory
cat: b: No such file or directory

Which also fails. How do I make it so that when I run $cmd I cat a b?

2

2 Answers 2

2
set -- cat "a b"

"$@"

This would run cat with the single argument a b. The expansion of "$@" (including the double quotes!) would be a list of individually quoted positional parameters. The positional parameters are set with set.

It's uncommon to include the command name in the list itself though. It would be more common to see

set -- "a b"

cat "$@"

or, if you want to keep the actual command name variable,

cmd=cat
set -- "a b"

"$cmd" "$@"

If you, in-between setting the filename operands and running the command, want to add more filenames or options to the command, simply modify $@:

cmd=cat
set -- "a b"

# later (adding more files to the end):
set -- "$@" "more files" "some glob here-"[0-9]

# later (adding an option at the front):
set -- -v "$@"

"$cmd" "$@"

Note that it becomes difficult to add to the front of $@ if the command name is already there. In that case you would have to temporary shift the command name off of $@ into a separate variable, add the thing you want to add, and then add the command name again:

cmd=$1
shift
set -- "$cmd" -v "$@"

We usually want to use set -- rather than just set to set the positional parameters. The -- stops the parser of command line options from detecting command line options past that point in the command line argument list. Note that without --, it would be impossible to add the string -v at the front of $@ with set as set -v does something quite different.

Related:

1

When you're using echo $cmd or $cmd unquoted, split+glob is invoked on the content of $cmd. That has nothing to do with what happens when you do echo cat a b where it's the shell's syntax interpretation that separates cat, a and b into different arguments to pass to echo.

The split part of split+glob is done based on the content of $IFS which by default contain SPC, so the words resulting of that splitting can never contain SPC. You'd need to use a different separator:

IFS=:  # split on :
set -o noglob # disable glob part

cmd='cat:a b'
printf '<%s>\n' $cmd # split+glob
$cmd # split+glob

Alternatively, instead of relying on split+glob, you could have cmd contain a shell command line (code in the shell language) and invoke eval to interpret that code:

cmd='cat "a b"'
eval "$cmd" # note the quotes to disable split+glob

Not the answer you're looking for? Browse other questions tagged .