The choice is yours. If you do not quote $@
any of its values undergo additional expansion and interpretation. If you do quote it all of the arguments passed the function are reproduced in its expansion verbatim. You'll never be able to reliably handle shell syntax tokens like &>|
and etc either way without parsing out the arguments yourself anyway - and so you're left with the more reasonable choices of handing your function one of either:
- Exactly the words used in the execution of a single simple command with
"$@"
.
...or...
- A further expanded and interpreted version of your arguments which are only then applied together as a simple command with
$@
.
Neither way is wrong if it is intentional and if the effects of what you choose are well understood. Both ways have advantages one over the other, though the advantages of the second are seldom likely to be particularly useful. Still...
(run_this(){ $@; }; IFS=@ run_this 'ls@-dl@/tmp')
drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp
...it isn't useless, just rarely likely to be of much use. And in a bash
shell, because bash
doesn't by default stick a variable definition to its environment even when said definition is prepended to the command-line of a special builtin or to a function, the global value for $IFS
is unaffected, and its declaration is local only to the run_this()
call.
Similarly:
(run_this(){ $@; }; set -f; run_this ls -l \*)
ls: cannot access *: No such file or directory
...the globbing is also configurable. Quotes serve a purpose - they're not for nothing. Without them shell expansion undergo extra interpretation - configurable interpretation. It used to be - with some very old shells - that $IFS
was globally applied to all input, and not just expansions. In fact, said shells behaved very like run_this()
does in that they broke all input words on the value of $IFS
. And so, if what you're looking for is that very old shell behavior, then you should use run_this()
.
I'm not looking for it, and I'm fairly hard pressed at the moment to come up with a useful example for it. I generally prefer for the commands my shell runs to be those which I type at it. And so, given the choice, I would almost always run_that()
. Except that...
(run_that(){ "$@"; }; IFS=l run_that 'ls' '-ld' '/tmp')
drwxrwxrwt 22 root root 660 Dec 28 19:58 /tmp
Just about anything can be quoted. Commands will run quoted. It works because by the time the command is actually run, all input words have already undergone quote-removal - which is the last stage of the shell's input interpretation process. So the difference between 'ls'
and ls
can only matter while the shell is interpreting - and that's why quoting ls
ensures that any alias named ls
is not substituted for my quoted ls
command word. Other than that, the only things which quotes affect are the delimiting of words (which is how and why variable/input-whitespace quoting works), and the interpretation of metacharacters and reserved words.
So:
'for' f in ...
do :
done
bash: for: command not found
bash: do: unexpected token 'do'
bash: do: unexpected token 'done'
You'll never be able to do that with either of run_this()
or run_that()
.
But function names, or $PATH
'd commands, or builtins will execute just fine quoted or unquoted, and that's exactly how run_this()
and run_that()
work in the first place. You won't be able to do anything useful with $<>|&(){}
any of those. Short of eval
, is.
(run_that(){ "$@"; }; run_that eval printf '"%s\n"' '"$@"')
eval
printf
"%s\n"
"$@"
But without it, you're constrained to the limits of a simple command by virtue of the quotes you use (even when you don't because the $@
acts like a quote at the beginning of the process when the command is parsed for metacharacters). The same constraint is true of command-line assignments and redirections, which are limited to the function's command-line. But that's not a big deal:
(run_that(){ "$@";}; echo hey | run_that cat)
hey
I could have as easily <
redirected input or >
output there as I did open the pipe.
Anyway, in a round-about way, there is no right or wrong way here - each way has its uses. It's just that you should write it as you intend to use it, and you should know what you mean to do. Omitting quotes can have a purpose - otherwise there wouldn't be quotes at all - but if you omit them for reasons not relevant to your purpose, you're just writing bad code. Do what you mean; I try to anyway.
run_that
's behaviour is definitely what I'd expect (what if there's a space in the path to the command?). If you wanted the other behaviour, surely you'd unquote it at the call-site where you know what the data is? I'd expect to call this function asrun_that ls -l
, which works out the same in either version. Is there a case that made you expect differently? – Michael Homer Dec 23 '15 at 09:22${mycmd[@]}
. – chepner Dec 23 '15 at 15:00