How Does Bash Deal with Nested Quotes

Triple nested quotations in shell script

Here is an example. caller.sh uses gnome-terminal to execute foo.sh, which in turn prints all the arguments and then calls rsync with the first argument.

caller.sh:

#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh 'long path' arg2 arg3"

foo.sh:

#!/bin/bash
echo $# arguments
for i; do # same as: for i in "$@"; do
echo "$i"
done
rsync "$1" "some other path"

Edit: If $1 contains several parameters to rsync, some of which are long paths, the above won't work, since bash either passes "$1" as one parameter, or $1 as multiple parameters, splitting it without regard to contained quotes.

There is (at least) one workaround, you can trick bash as follows:

caller2.sh:

#!/bin/bash
gnome-terminal -t "TEST" -e "./foo.sh '--option1 --option2 \"long path\"' arg2 arg3"

foo2.sh:

#!/bin/bash
rsync_command="rsync $1"
eval "$rsync_command"

This will do the equivalent of typing rsync --option1 --option2 "long path" on the command line.

WARNING: This hack introduces a security vulnerability, $1 can be crafted to execute multiple commands if the user has any influence whatsoever over the string content (e.g. '--option1 --option2 \"long path\"; echo YOU HAVE BEEN OWNED' will run rsync and then execute the echo command).

Nesting quotes in bash

The stuff inside $() represents a subshell, so you are allowed to place un-escaped double quotes inside

alias foo='bar="$(echo testing hello world | grep "hello world")"; echo "$bar"'

How does Bash deal with brackets inside of double quoting?

Each command substitution establishes a new quoting context, so the correct way to avoid word splitting inside nested command substitutions is to use double quotes.

In this example, white space is preserved:

$ echo "$(echo "$(echo 'foo     bar')")" # outer echo sees 'foo     bar'
foo bar

However, missing any of the double quotes means that the string is split by the shell:

$ echo $(echo "$(echo 'foo     bar')") # outer echo sees 'foo' 'bar'
foo bar
$ echo "$(echo $(echo 'foo bar'))" # middle echo sees 'foo' 'bar'
foo bar

echo outputs each argument, separated by a spaces, which is where the single space between "foo" and "bar" comes from.

Nested quotes in Bash

because arguments are subject to word splitting, you may change $IFS and put the the new separator into your echo

IFS=$'_\n'
./argtest.sh $(for i in {1..2} ; do echo "Number ${i}_"; done )

don't forget to restore the old $IFS

oIFS=$IFS
...
IFS=$oIFS

btw {1..2} is equivalent to seq 1 2 but you don't need an external command

Bash nested quotes and eval


argv="su -c \"$RVM_PATH wrapper $config_rvm \\\"$PASSENGER_RVM_BIN $command $options\\\"\" web"

How to escape single quotes within single quoted strings

If you really want to use single quotes in the outermost layer, remember that you can glue both kinds of quotation. Example:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
# ^^^^^ ^^^^^ ^^^^^ ^^^^
# 12345 12345 12345 1234

Explanation of how '"'"' is interpreted as just ':

  1. ' End first quotation which uses single quotes.
  2. " Start second quotation, using double-quotes.
  3. ' Quoted character.
  4. " End second quotation, using double-quotes.
  5. ' Start third quotation, using single quotes.

If you do not place any whitespaces between (1) and (2), or between (4) and (5), the shell will interpret that string as a one long word.

Nested double quotes in parameter expansion

The answer is that they get parsed separately. Let's take a simplified tour of the string.

result="${var1#"$var2"}" doesn't actually need any quotes in this case, but look over the string anyway...

result="...

The Parser says meh, it's an assignment, I know what to do with this, I'll ignore these, they aren't hurting anything, but now I have to find the terminating match. Then it reads the value after the quote, byte by byte, looking for the terminating double-quote. This starts a new context-1.

result="${...

Once it sees the open curly, it knows that the terminating quote cannot happen until it sees the matching closing curly. It starts a new context-2.

result="${var1#"...

Seeing a new double quote in this subcontext make this one the opening quote of an internal new context-3.

result="${var1#"$var2"...

When it sees this double-quote it matches it to the previous one, closing context-3, dropping back into context-2.

result="${var1#"$var2"}...

This close-curly allows it to close the still-open context-2, dropping back into context-1.

result="${var1#"$var2"}"

And finding this now-closing double-quote allows it to close context-1. The following newline may be used as a terminating character for the entire term, so it can be evaluated and assigned.

Backslash-Quoting the internal double-quotes, for example, would have added them to the string-term used for the tail trim, which would likely have failed because of it.

$: var1=aaa
$: var2=a
$: result="${var1#"$var2"}"
$: echo $result # does what you want/expect
aa
$: result="${var1#\"$var2\"}" # does NOT
$: echo $result
aaa

Doing it without the quotes, the parser knows this is an assignment and handles the values a little differently as mentioned in comments, but generally kinda treating them as if they were quoted.

$: result=${var1#$var2}
$: echo $result
aa

This means it doesn't have to deal with context-1 or context-3, and only has the curlies to worry about. The end result is the same.

Better?



Related Topics



Leave a reply



Submit