Exactly how do backslashes work within backticks?
The logic is quite simple as such. So we look at bash source code (4.4) itself
subst.c:9273
case '`': /* Backquoted command substitution. */{ t_index = sindex++; temp = string_extract(string, &sindex, "`", SX_REQMATCH); /* The test of sindex against t_index is to allow bare instances of ` to pass through, for backwards compatibility. */ if (temp == &extract_string_error || temp == &extract_string_fatal) { if (sindex - 1 == t_index) { sindex = t_index; goto add_character; } last_command_exit_value = EXECUTION_FAILURE; report_error(_("bad substitution: no closing \"`\" in %s"), string + t_index); free(string); free(istring); return ((temp == &extract_string_error) ? &expand_word_error : &expand_word_fatal); } if (expanded_something) *expanded_something = 1; if (word->flags & W_NOCOMSUB) /* sindex + 1 because string[sindex] == '`' */ temp1 = substring(string, t_index, sindex + 1); else { de_backslash(temp); tword = command_substitute(temp, quoted); temp1 = tword ? tword->word : (char *)NULL; if (tword) dispose_word_desc(tword); } FREE(temp); temp = temp1; goto dollar_add_string;}
As you can see calls a function de_backslash(temp);
on the string which updates the string in c. The code the same function is below
subst.c:1607
/* Remove backslashes which are quoting backquotes from STRING. Modifies STRING, and returns a pointer to it. */char * de_backslash(string) char *string;{ register size_t slen; register int i, j, prev_i; DECLARE_MBSTATE; slen = strlen(string); i = j = 0; /* Loop copying string[i] to string[j], i >= j. */ while (i < slen) { if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' || string[i + 1] == '$')) i++; prev_i = i; ADVANCE_CHAR(string, slen, i); if (j < prev_i) do string[j++] = string[prev_i++]; while (prev_i < i); else j = i; } string[j] = '\0'; return (string);}
The above just does simple thing if there is \
character and the next character is \
or backtick or $
, then skip this \
character and copy the next character
So if convert it to python for simplicity
text = r"\\\\$a"slen = len(text)i = 0j = 0data = ""while i < slen: if (text[i] == '\\' and (text[i + 1] == '`' or text[i + 1] == '\\' or text[i + 1] == '$')): i += 1 data += text[i] i += 1print(data)
The output of the same is \\$a
. And now lets test the same in bash
$ a=xxx$ echo "$(echo \\$a)"\xxx$ echo "`echo \\\\$a`"\xxx
Did some more research to find the reference and rule of what is happening. From the GNU Bash Reference Manual it states
When the old-style backquote form of substitution is used, backslash retains its literal meaning except when followed by ‘$’, ‘`’, or ‘\’. The first backquote not preceded by a backslash terminates the command substitution. When using the $(command) form, all characters between the parentheses make up the command; none are treated specially.
In other words \, \$, and ` inside of `` are processed by the CLI parser before the command substitution. Everything else is passed to the command substitution for processing.
Let's step through each example from the question. After the # I put how the command substitution was processed by the CLI parser before `` or $() is executed.
Your first example explained.
$ echo "`echo \\a`" # echo \a a $ echo "$(echo \\a)" # echo \\a \a
Your second example explained:
$ echo "`echo \\\\a`" # echo \\a \a $ echo "$(echo \\\\a)" # echo \\\\a \\a
Your third example:
a=xx$ echo "`echo $a`" # echo xx xx$ echo "`echo \$a`" # echo $axxecho "`echo \\$a`" # echo \$a$a
Your third example using $()
$ echo "$(echo $a)" # echo $axx$ echo "$(echo \$a)" # echo \$a$a$ echo "$(echo \\$a)" # echo \\$a\xx