1

I was surprised that the following is a valid Parameter Expansion. Notice there are unescaped double quotes within double quotes:

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

Can someone please parse this for me?

Roland
  • 7,525
  • 13
  • 61
  • 124

2 Answers2

2

There are double quotes nested in curly brackets which is OK.

But none of them is needed in this case.

result=${var1#$var2}

works the same even for values containing spaces and newlines.

choroba
  • 231,213
  • 25
  • 204
  • 289
  • Why does it work the same? Do the brackets prevent word-splitting and filename expansion? "The shell scans the results of parameter expansion, command substitution, and arithmetic expansion that did not occur within double quotes for word splitting." from the manual – Roland Nov 12 '21 at 14:05
  • 2
    From the POSIX spec: "In addition, a parameter expansion can be modified by using one of the following formats. In each case that a value of word is needed (based on the state of parameter, as described below), word shall be subjected to tilde expansion, parameter expansion, command substitution, and arithmetic expansion." The parameter expansion is parsed and evaluated *within* the quoted string, rather than the quoted string being constructed *from* the parameter expansion. – chepner Nov 12 '21 at 14:08
  • The expansion of `$var2`, in the context of the enclosing parameter expansion, is simply subject to different rules. – chepner Nov 12 '21 at 14:09
  • @chepner After parameter expansion doesn't bash do word splitting and filename expansion? – Roland Nov 12 '21 at 14:26
  • 2
    Also, the [3.4 Shell Parameters](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameters) section of the manual documentx that word splitting and filename expansion are not performed during variable **assignment**. This is why quotes are not needed in `var=$value` – glenn jackman Nov 12 '21 at 14:39
  • 1
    Still, quotes will virtually never do any *harm* here, and might be worth including just to keep someone from spending a lot of time chasing possible issues where there wouldn't be any. As a habit, quotes are your friend. If you want to be *really* friendly, use quotes *and* a commant that mentions why they aren't *required*. ;) – Paul Hodges Nov 12 '21 at 15:44
  • 1
    @Roland In general, no. It depends on the context in which the parameter expansion occurs. It *does* happen in the common context of a parameter expansion in the argument position of a simple command, but not as the argument to a parameter expansion operator, or on the RHS of an assignment, or as the word to match in a `case` statement, etc. – chepner Nov 12 '21 at 17:33
  • @PaulHodges Still no one has answered my question. How do the quotes inside quotes get parsed? – Roland Nov 13 '21 at 10:23
  • 1
    They are parsed as quotes inside curlies. – choroba Nov 13 '21 at 16:29
1

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?

Paul Hodges
  • 13,382
  • 1
  • 17
  • 36