Bash – How to prevent command substitution on the command line

bashcommand linecommand-substitutionquotingshell

I find that when writing text as input to another program, any command substitutions in double quotes within the intended text are interpreted and expanded by the shell

The links in the answer here states that single quotes can be used to prevent parameter expansion or command substitution. However I'm finding that enclosing a command substitution in single-quotes also fails to stop the shell from expanding the command substitution

How do you prevent the shell from interpreting command substitutions that are intended as text rather than a command to be executed?

A demonstration

$ echo "`wc -l *`"

attempts to count lines in all files in the current directory

$ echo "'`wc -l *`'"

Same result, i.e. counts lines in all files in the current directory

update From this demonstration I've spotted that the problem seems to be that I am quoting the single quotes. I think enclosing single quotes and ` (backtick) in double quotes preserves the literal meaning of (i.e. suppresses) the single quotes but does not preserve the literal meaning of the backquote (i.e. backtick) that introduces the command substitution.

In my use case the input for another command needs to be quoted. With this document saying that:

A single-quote cannot occur within single quotes

How do you prevent a single-quoted command substitution from being expanded when the single-quoted command substitution is within a (double) quoted string? There should be a way to do it other than using backslash escapes

Actual situation

In a program I'm using the only way to split a description of a task into separate lines is to enclose the description in double-quotes:

$ task add "first line doesn\'t say much
Second line says a lot but part of this line does not appear in the resulting description 'truncate -s0 !(temp_file | temp_dir)' truncates all files to 0 bytes as shown by: '`wc -l *`'"

The resulting description:

first line doesn\ -s0 !(temp_file | temp_dir)' truncates all files to 0 bytes as shown by: 0 file1 10 file2 0 directory1 0 directory2 502 file3 123 file4 162 file5 0 directory3

As you can see

't say much
Second line says a lot but part of this line does not appear in the resulting description 'truncate

is missing from the description and the shell has interpreted 'wc -l *' as a command substitution, thereby including the line counts of all files in the current directory as part of the description

What's causing the shell to remove the part of the argument to task between \ (backslash) and -s, and how do you prevent the shell from interpreting the above single-quoted command substitution (i.e. '`wc -l *`')?

Best Answer

Here (linebreaks added),

$ task add "first line doesn\'t say much
Second line says a lot but part of this line does not appear in the
resulting description 'truncate -s0 !(temp_file | temp_dir)' truncates
all files to 0 bytes as shown by: '`wc -l *`'"

the whole string is double-quoted, so command substitutions and other expansions will run there. That happens in the shell, before task sees that string, and you'll need to prevent it with backslashes or putting that part in single quotes.

E.g.

$ printf "%s\n" "...shown by: '\`wc -l *\`'"
...shown by: '`wc -l *`'

So,

task add "...shown by: '\`wc -l *\`'"

would pass the string ...shown by: '`wc -l *`' to task. It's up to it what does with that.

If you don't want to use backslashes, here's the way to put it in single quotes:

#               aaaaaaaaaaaaaaaaBBBBBBBBBBBaaa
$ printf "%s\n" "...shown by: '"'`wc -l *`'"'"
...shown by: '`wc -l *`'

(The a's mark the double-quoted parts, the B's the single-quoted parts. They are just concatenated on the shell command line. The literal single quotes are within the double-quoted strings.)


As for the single quote and the backslash, you don't need to escape a single quote within double quotes, and in fact the backslash will remain there:

$ printf "%s\n" "foo'bar"
foo'bar
$ printf "%s\n" "foo\'bar"
foo\'bar

From what you show, it seems like task removes at least the first single-quoted string from the argument (plus a word after that, since the removed part was 't say much ... 'truncate)

The shell will not do that, this works fine:

$ printf "%s\n" "a 'quoted string' to test"
a 'quoted string' to test

What's causing the shell to remove the part of the argument to task between \ (backslash) and -s,

It's highly likely it's not the shell doing that.

and how do you prevent the shell from interpreting the above single-quoted command substitution (i.e. '`wc -l *`')?

It's not single-quoted, it's double-quoted with quoted single quotes next to it.

Related Question