Before executing a command in backticks certain preprocessing takes place:
\\
becomes\
(`echo \\\\`
->echo \\
->\
)- but if there's a
\
not followed by\
, then\
remains\
(`echo \\\ `
->echo \\
->\
) - unquoted
\
at the end of the resulting command remains\
(`echo \\`
->echo \
->\
), but this is not exactly about backticks (bash -c 'echo \'
->\
) \"
remains\"
(`echo \"`
->echo \"
->"
)- but in double quotes
\"
becomes"
("`echo \\\"`"
->echo \"
->"
) \$
becomes$
(`a=1; echo \$a`
->a=1; echo $a
->1
)\`
becomes`
(`echo \`echo \\\\\\\\\``
->echo `echo \\\\`
->\
)- for a nested command substitution of level
n
the rules above applyn
times (takeecho \\\\\\\\
from the previous rule, apply the first rule twice and you getecho \\
, which produces\
)
a.bats
:
strict() { set -euo pipefail; shopt -s inherit_errexit; "$@"; }
setup() {
[ "$BATS_LIB_PATH" = /usr/lib/bats ] && BATS_LIB_PATH=$HOME/.bats/lib:$BATS_LIB_PATH
bats_load_library bats-support
bats_load_library bats-assert
strict
}
# unquoted \\ becomes \
@test "unquoted \\\\ becomes \\" {
a=`echo \\\\` # |echo \\|
assert_equal "$a" '\'
}
# unquoted \ remains \
@test "unquoted \\ remains \\" {
a=`echo \\\ ` # |echo \\ |
assert_equal "$a" '\'
}
# unquoted \ at the end of the resulting command remains \
@test "unquoted \\ at the end of the resulting command remains \\" {
a=`echo \\` # |echo \|
assert_equal "$a" '\'
}
# single-quoted \ remains \
@test "single-quoted \\ remains \\" {
a=`echo '\'` # |echo '\'|
assert_equal "$a" '\'
}
# single-quoted \\ becomes \
@test "single-quoted \\\\ becomes \\" {
a=`echo '\\'` # |echo '\'|
assert_equal "$a" '\'
}
# double-quoted \\ becomes \
@test "double-quoted \\\\ becomes \\" {
a=`echo "\\\\"` # |echo "\\"|
assert_equal "$a" '\'
}
# unquoted \" remains \"
@test "unquoted \\\" remains \\\"" {
a=`echo \"` # |echo \"|
assert_equal "$a" '"'
}
# unquoted \" inside double quotes becomes "
@test "unquoted \\\" inside double quotes becomes \"" {
a="`echo \\\"`" # |echo \"|
assert_equal "$a" '"'
}
# single-quoted \" remains \"
@test "single-quoted \\\" remains \\\"" {
a=`echo '\"'` # |echo '\"'|
assert_equal "$a" '\"'
}
# single-quoted \" inside double quotes becomes "
@test "single-quoted \\\" inside double quotes becomes \"" {
a="`echo '\"'`" # |echo '"'|
assert_equal "$a" '"'
}
# double-quoted \" remains \"
@test "double-quoted \\\" remains \\\"" {
a=`echo "\""` # |echo "\""|
assert_equal "$a" '"'
}
# double-quoted \" inside double quotes becomes "
@test "double-quoted \\\" inside double quotes becomes \"" {
a="`echo "\\\""`" # |echo "\""|
assert_equal "$a" '"'
}
# unquoted \$ becomes $
@test "unquoted \\\$ becomes \$" {
a=`a=1; echo \$a` # |echo $a|
assert_equal "$a" '1'
}
# single-quoted \$ becomes $
@test "single-quoted \\\$ becomes \$" {
a=`a=1; echo '\$a'` # |echo '$a'|
assert_equal "$a" '$a'
}
# double-quoted \$ becomes $
@test "double-quoted \\\$ becomes \$" {
a=`a=1; echo "\$a"` # |echo "$a"|
assert_equal "$a" '1'
}
# nested: unquoted \\ becomes \
@test "nested: unquoted \\\\ becomes \\" {
a=`echo \`echo \\\\\\\\\`` # |echo `echo \\\\`| -> |echo \\|
assert_equal "$a" '\'
}
# nested: unquoted \ remains \
@test "nested: unquoted \\ remains \\" {
a=`echo \`echo \\\\\\ \`` # |echo `echo \\\ `| -> |echo \\ |
assert_equal "$a" '\'
}
# nested: unquoted \ at the end of the resulting command remains \
@test "nested: unquoted \\ at the end of the resulting command remains \\" {
a=`echo \`echo \\\\\`` # |echo `echo \\`| -> |echo \|
assert_equal "$a" '\'
}
# nested: single-quoted \ remains \
@test "nested: single-quoted \\ remains \\" {
a=`echo \`echo '\'\`` # |echo `echo '\'`| -> |echo '\'|
assert_equal "$a" '\'
}
# nested: single-quoted \\ becomes \
@test "nested: single-quoted \\\\ becomes \\" {
a=`echo \`echo '\\\\'\`` # |echo `echo '\\'`| -> |echo '\'|
assert_equal "$a" '\'
}
# nested: double-quoted \\ becomes \
@test "nested: double-quoted \\\\ becomes \\" {
a=`echo \`echo "\\\\\\\\"\`` # |echo `echo "\\\\"`| -> |echo "\\"|
assert_equal "$a" '\'
}
# nested: unquoted \" remains \"
@test "nested: unquoted \\\" remains \\\"" {
a=`echo \`echo \"\`` # |echo `echo \"`| -> |echo \"|
assert_equal "$a" '"'
}
# nested: unquoted \" inside double quotes becomes "
@test "nested: unquoted \\\" inside double quotes becomes \"" {
a=`echo "\`echo \\\\\"\`"` # |echo "`echo \\\"`"| -> |echo \"|
assert_equal "$a" '"'
}
# nested: single-quoted \" remains \"
@test "nested: single-quoted \\\" remains \\\"" {
a=`echo \`echo '\"'\`` # |echo `echo '\"'`| -> |echo '\"'|
assert_equal "$a" '\"'
}
# nested: single-quoted \" inside double quotes becomes "
@test "nested: single-quoted \\\" inside double quotes becomes \"" {
a=`echo "\`echo '\"'\`"` # |echo "`echo '\"'`"| -> |echo '"'|
assert_equal "$a" '"'
}
# nested: double-quoted \" remains \"
@test "nested: double-quoted \\\" remains \\\"" {
a=`echo \`echo "\""\`` # |echo `echo "\""`| -> |echo "\""|
assert_equal "$a" '"'
}
# nested: double-quoted \" inside double quotes becomes "
@test "nested: double-quoted \\\" inside double quotes becomes \"" {
a=`echo "\`echo "\\\\\""\`"` # |echo "`echo "\\\""`"| -> |echo "\""|
assert_equal "$a" '"'
}
# nested: unquoted \$ becomes $
@test "nested: unquoted \\\$ becomes \$" {
a=`a=1; echo \`echo \\\$a\`` # |echo `echo \$a`| -> |echo $a|
assert_equal "$a" '1'
}
# nested: single-quoted \$ becomes $
@test "nested: single-quoted \\\$ becomes \$" {
a=`a=1; echo \`echo '\\\$a'\`` # |echo `echo '\$a'`| -> |echo '$a'|
assert_equal "$a" '$a'
}
# nested: double-quoted \$ becomes $
@test "nested: double-quoted \\\$ becomes \$" {
a=`a=1; echo \`echo "\\\$a"\`` # |echo `echo "\$a"`| -> |echo "$a"|
assert_equal "$a" '1'
}
$ docker run --rm -itv "$PWD":/app -w /app alpine:3.21
/ # apk add git bash ncurses
/ # git clone https://github.com/bats-core/bats-core ~/.bats
/ # git clone https://github.com/bats-core/bats-support ~/.bats/lib/bats-support
/ # git clone https://github.com/bats-core/bats-assert ~/.bats/lib/bats-assert
/ # ~/.bats/bin/bats a.bats