Quoting inside parameter expansion inside double-quotes (#40751)
@@ -13,6 +13,10 @@ | ||
13 | 13 | = Quote removal in arithmetic expansion has been modified to match |
14 | 14 | the behavior defined in POSIX. It no longer allows things like |
15 | 15 | $(("2" + \5)). |
16 | + = The quotation rules for the substitution word in a parameter | |
17 | + expansion inside double-quotes have been changed to match with | |
18 | + the behavior of other existing shells. For example, "${x-\a'b'}" | |
19 | + now expands to \a'b' rather than ab. | |
16 | 20 | * The "command" built-in with the -v or -V option was printing |
17 | 21 | the pathnames of external commands with a redundant leading slash |
18 | 22 | when the current working directory is "/" or "//". |
@@ -88,7 +88,7 @@ | ||
88 | 88 | __attribute__((nonnull)); |
89 | 89 | static void **trim_array(void **a, ssize_t startindex, ssize_t endindex) |
90 | 90 | __attribute__((nonnull)); |
91 | -static void print_subst_as_error(const paramexp_T *p) | |
91 | +static void print_subst_as_error(const paramexp_T *p, quoting_T quoting) | |
92 | 92 | __attribute__((nonnull)); |
93 | 93 | static void match_each(void **restrict slist, const wchar_t *restrict pattern, |
94 | 94 | paramexptype_T type) |
@@ -405,8 +405,12 @@ | ||
405 | 405 | while (*ss != L'\0') { |
406 | 406 | switch (*ss) { |
407 | 407 | case L'"': |
408 | - if (quoting != Q_WORD) | |
409 | - goto default_; | |
408 | + switch (quoting) { | |
409 | + case Q_WORD: case Q_DQPARAM: | |
410 | + break; | |
411 | + case Q_INDQ: case Q_LITERAL: | |
412 | + goto default_; | |
413 | + } | |
410 | 414 | indq = !indq; |
411 | 415 | wb_wccat(&e->valuebuf, L'"'); |
412 | 416 | sb_ccat(&e->ccbuf, defaultcc | CC_QUOTATION); |
@@ -430,6 +434,10 @@ | ||
430 | 434 | if (indq && wcschr(CHARS_ESCAPABLE, ss[1]) == NULL) |
431 | 435 | goto default_; |
432 | 436 | break; |
437 | + case Q_DQPARAM: | |
438 | + if (wcschr(CHARS_ESCAPABLE "}", ss[1]) == NULL) | |
439 | + goto default_; | |
440 | + break; | |
433 | 441 | case Q_INDQ: |
434 | 442 | if (wcschr(L"$`\\", ss[1]) == NULL) |
435 | 443 | goto default_; |
@@ -721,6 +729,7 @@ | ||
721 | 729 | unset = true; |
722 | 730 | |
723 | 731 | /* PT_PLUS, PT_MINUS, PT_ASSIGN, PT_ERROR */ |
732 | + quoting_T substq = indq ? Q_DQPARAM : Q_WORD; | |
724 | 733 | wchar_t *subst; |
725 | 734 | switch (p->pe_type & PT_MASK) { |
726 | 735 | case PT_PLUS: |
@@ -732,7 +741,7 @@ | ||
732 | 741 | if (unset) { |
733 | 742 | subst: |
734 | 743 | plfree(values, free); |
735 | - return expand_four_inner(p->pe_subst, TT_SINGLE, Q_WORD, | |
744 | + return expand_four_inner(p->pe_subst, TT_SINGLE, substq, | |
736 | 745 | CC_SOFT_EXPANSION | (indq * CC_QUOTED), e); |
737 | 746 | } |
738 | 747 | break; |
@@ -755,7 +764,7 @@ | ||
755 | 764 | p->pe_name); |
756 | 765 | return false; |
757 | 766 | } |
758 | - subst = expand_single(p->pe_subst, TT_SINGLE, Q_WORD, ES_NONE); | |
767 | + subst = expand_single(p->pe_subst, TT_SINGLE, substq, ES_NONE); | |
759 | 768 | if (subst == NULL) |
760 | 769 | return false; |
761 | 770 | if (v.type != GV_ARRAY) { |
@@ -781,7 +790,7 @@ | ||
781 | 790 | case PT_ERROR: |
782 | 791 | if (unset) { |
783 | 792 | plfree(values, free); |
784 | - print_subst_as_error(p); | |
793 | + print_subst_as_error(p, substq); | |
785 | 794 | return false; |
786 | 795 | } |
787 | 796 | break; |
@@ -937,10 +946,11 @@ | ||
937 | 946 | } |
938 | 947 | |
939 | 948 | /* Expands `p->pe_subst' and prints it as an error message. */ |
940 | -void print_subst_as_error(const paramexp_T *p) | |
949 | +void print_subst_as_error(const paramexp_T *p, quoting_T quoting) | |
941 | 950 | { |
942 | 951 | if (p->pe_subst != NULL) { |
943 | - wchar_t *subst = expand_single(p->pe_subst, TT_SINGLE, Q_WORD, ES_NONE); | |
952 | + wchar_t *subst = | |
953 | + expand_single(p->pe_subst, TT_SINGLE, quoting, ES_NONE); | |
944 | 954 | if (subst != NULL) { |
945 | 955 | if (p->pe_type & PT_NEST) |
946 | 956 | xerror(0, "%ls", subst); |
@@ -34,7 +34,11 @@ | ||
34 | 34 | typedef enum { |
35 | 35 | Q_WORD, /* Single quotations, double quotations, and backslashes are |
36 | 36 | recognized as in the normal word. */ |
37 | - Q_INDQ, /* The string is quoted as if it is inside a pair of double | |
37 | + Q_DQPARAM, /* The string is treated as the substitution word of a parameter | |
38 | + expansion inside a pair of double quotations: Double | |
39 | + quotations are recognized, but single quotations are not. | |
40 | + Backslashes are recognized only before $, `, ", \ or }. */ | |
41 | + Q_INDQ, /* The string is treated as if it is inside a pair of double | |
38 | 42 | quotations: Single and double quotations are not recognized. |
39 | 43 | Backslashes are recognized only before a $, `, or \. */ |
40 | 44 | Q_LITERAL, /* No quotations are recognized. */ |