Shell – Wrapping Commands with Single and Double Quotes

quotingshellzsh

I recently learned about watch, but am having trouble making it work with relatively sophisticated commands.

For example, I would like to ask watch to run the following command on zsh every three seconds*:

for x in `command_1 | grep keyword | cut -d' ' -f1`; do command_2 "word[word=number]" $x; done

as you can see the line above includes single quotes, double quotes, among other special characters.

So I tried:

watch -n 3 "for x in `my_command | grep keyword | cut -d' ' -f1`; do command2 "rusage[mem=7000]" $x; done"

but then I got:

no matches found for x in !@#$# ….; done

I tried other combinations without success. Here's one of those attempts:

watch -n 3 "for x in $(bjobs -w | grep pre_seg | cut -d' ' -f1); do bmod -R "rusage[mem=7000]" $x; done"

which also results in a similar error.

Any ideas how to make this work?


*I would also be intersted in solutions that work on bash

Best Answer

General tip: if you have two levels of nesting, avoid using single quotes in the inner command, and use single quotes around the outer command.

Additional tip: don't use backticks - `…` - to execute code, instead use $(…) around it. Dollar-parentheses is pretty much DWIM('Do what I mean') when it comes to nested quotes; backquotes have arcane, shell-dependent rules.

watch -n 3 'for x in $(my_command | grep keyword | cut -d" " -f1); do command2 "rusage[mem=7000]" "$x"; done'

If you need single quotes inside a single-quoted command, you can use '\''. Think of these four characters as the way to quote a single quote inside single quotes, though technically speaking this is constructed as end the single-quoted string, append a literal single quote, and start a new single-quoted string (still appended to the current word).

For more complex cases, either painstakingly count the quotes, or define temporary variables.

cmd='for x in $(my_command | grep keyword | cut -d" " -f1); do command2 "rusage[mem=7000]" "$x"; done'
watch_cmd='watch -n 3 "$cmd"'

This answer isn't specific to zsh. Zsh doesn't bring anything major here. You can save a bit of quoting because there's no need for double quotes around command substitutions, and sometimes there are ways to use built-ins rather than external commands which reduce the quoting needs, but the underlying issues are the same as in other shells.

Oh, and by the way, note that watch will execute your command in sh, not in zsh. If you want to execute the command in zsh, you need to run

watch -n 3 -x zsh -c "$cmd"

on Debian/Ubuntu, and

export cmd
watch -n 3 'exec zsh -c "$cmd"'

(even more quoting!) elsewhere.