nested double quotes in assignment with command substitution

15,385

Solution 1

Your puzzle isn't right about how bash (and the shell in general) parsed the input. In:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

First, bash parse the right hand side of assignment to one long string $( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) because double quote can appear inside double quotes.

After then, bash start parsing the command substitution. Because all characters following open parenthesis to enclosing parenthesis are used to construct the command inside command substitution, you will get:

cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd

The shell continue parsing that compound command, break it into two parts:

  • cd "$( dirname "${BASH_SOURCE[0]}" )"
  • pwd

Then applying the same parsing rule for cd "$( dirname "${BASH_SOURCE[0]}" )", but this time, double quotes are not redundant, but make sense. They prevent field splitting on result of $( dirname "${BASH_SOURCE[0]}" ), and also the expansion of ${BASH_SOURCE[0]} (In contrast with the outer most double quotes, will are not necessary in RHS of variable assignment to prevent split+glob).


This rule apply to command substitution in all POSIX shell. A more details puzzle you can read in Token Recognition section of POSIX spec.

Solution 2

Once one is inside $(...), quoting starts all over from scratch.

In other words, "..." and $(...) can nest within each other. Command substitution, $(...), can contain one or more complete double-quoted strings. Also, double-quoted strings may contain one or more complete command substitutions. But, they do not interlace. Thus, a double-quoted string that starts inside a command substitution will never extend outside of it or vice versa.

So, consider:

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

Inside the inner $(...) is:

dirname "${BASH_SOURCE[0]}"

In the above ${BASH_SOURCE[0]} is double-quoted. Any quotes, double or single, outside of the $(...) are irrelevant when determining that ${BASH_SOURCE[0]} is double-quoted.

The outer $(...) contains:

cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd

Here, the expression $( dirname "${BASH_SOURCE[0]}" ) is double-quoted. The fact that there are quotes outside of the outer $(...) is irrelevant when considering what is inside it. The fact that there are quotes inside the inner $(...) is also irrelevant.

Here is how the double-quotes match up:

enter image description here

Share:
15,385

Related videos on Youtube

kjo
Author by

kjo

Updated on September 18, 2022

Comments

  • kjo
    kjo almost 2 years

    A StackOverflow answer with > 3.5K votes features this one-liner for assigning to DIR the directory of the current bash script:

    DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    

    I'm puzzled by the nested double-quotes. As far as I can tell, the following fragments are double-quoted:

    "$( cd "
    "${BASH_SOURCE[0]}"
    " && pwd )"
    

    ...and everything else to the right of the = (i.e. $( dirname and )) is unquoted. In other words, I assume that the 2nd, 4th, and 6th " characters "close" the 1st, 3rd, and 5th " characters, respectively.

    I understand what the double-quotes in "${BASH_SOURCE[0]}" achieve, but what's the purpose of the other two pairs of double-quotes?

    If, on the other hand (and the high vote score notwithstanding), the above snippet is incorrect, what's the right way to achieve its nominal intent?

    (By nominal intent I mean: collect the value returned by pwd after first cd-ing to the directory returned by dirname "${BASH_SOURCE[0]}", and do the cd-ing in a sub-shell, so that the $PWD of the parent shell remains unchanged).

    • Olivier Dulac
      Olivier Dulac about 8 years
      it's because of a feature of $(...) : $( here, it's a subshell, but you are writing code as if you were writing it on the "first level" of the shell .... ).
    • David Tonhofer
      David Tonhofer almost 6 years
      I came here because of the docker install script. To find the name of the distro: lsb_dist="$(. /etc/os-release && echo "$ID")"; echo "$lsb_dist"
    • David Tonhofer
      David Tonhofer almost 6 years
      Note that spaces in the line are not needed: DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" works as well.
  • cuonglm
    cuonglm about 8 years
    What do you mean irrelevant? Except for outer most parenthesis, all other ones have its own meaning.
  • Wildcard
    Wildcard about 8 years
    And, this is why you shouldn't use backticks: The quoting rules are extremely weird and non-intuitive.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' about 8 years
    The sentence “$(...) binds tighter than "..."” doesn't make sense. They aren't infix operators, and there's no hierarchy between them (if there was then it would mean that either quotes can't be inside parentheses or parentheses can't be inside quotes, but that's not the case). The technical term is that $(…) and "…" nest.
  • John1024
    John1024 about 8 years
    @Gilles Very good. I just re-worded in hopes of capturing the concept better.