mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-11-22 11:22:52 +08:00
Fix regression expanding \$()
When expanding command substitutions, we use a naïve way of detecting whether the cmdsub has the optional leading dollar. We check if the last character was a dollar, which breaks if it's an escaped dollar. We wrongly expand \$(echo "") to the empty string. Fix this by checking if the dollar was escaped. The parse_util_* functions have a bunch of output parameters. We should return a parameter bag instead (I think I tried once and failed).
This commit is contained in:
parent
d87bbf9433
commit
3e3f507012
|
@ -20,6 +20,7 @@ Deprecations and removed features
|
|||
Scripting improvements
|
||||
----------------------
|
||||
- Quoted command substitution that directly follow a variable expansion (like ``echo "$var$(echo x)"``) no longer affect the variable expansion (:issue:`8849`).
|
||||
- Fish now correctly expands command substitutions that are preceded by an escaped dollar (like ``echo \$(echo)``). This regressed in version 3.4.0.
|
||||
- ``math`` can now handle underscores (``_``) as visual separators in numbers (:issue:`8611`, :issue:`8496`)::
|
||||
|
||||
math 5 + 2_123_252
|
||||
|
|
|
@ -626,8 +626,9 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
|||
wcstring subcmd;
|
||||
|
||||
bool is_quoted = false;
|
||||
bool has_dollar = false;
|
||||
switch (parse_util_locate_cmdsubst_range(input, &cursor, &subcmd, &paren_begin, &paren_end,
|
||||
false, &is_quoted)) {
|
||||
false, &is_quoted, &has_dollar)) {
|
||||
case -1: {
|
||||
append_syntax_error(errors, SOURCE_LOCATION_UNKNOWN, L"Mismatched parenthesis");
|
||||
return expand_result_t::make_error(STATUS_EXPAND_ERROR);
|
||||
|
@ -646,8 +647,6 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
|||
}
|
||||
}
|
||||
|
||||
bool have_dollar = paren_begin > 0 && input.at(paren_begin - 1) == L'$';
|
||||
|
||||
wcstring_list_t sub_res;
|
||||
int subshell_status = exec_subshell_for_expand(subcmd, *ctx.parser, ctx.job_group, sub_res);
|
||||
if (subshell_status != 0) {
|
||||
|
@ -757,7 +756,7 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
|||
wcstring whole_item;
|
||||
whole_item.reserve(paren_begin + 1 + sub_res_joined.size() + 1 +
|
||||
tail_item.completion.size());
|
||||
whole_item.append(input, 0, paren_begin - have_dollar);
|
||||
whole_item.append(input, 0, paren_begin - has_dollar);
|
||||
whole_item.push_back(INTERNAL_SEPARATOR);
|
||||
whole_item.append(sub_res_joined);
|
||||
whole_item.push_back(INTERNAL_SEPARATOR);
|
||||
|
@ -776,7 +775,7 @@ static expand_result_t expand_cmdsubst(wcstring input, const operation_context_t
|
|||
wcstring whole_item;
|
||||
whole_item.reserve(paren_begin + 1 + sub_item2.size() + 1 +
|
||||
tail_item.completion.size());
|
||||
whole_item.append(input, 0, paren_begin - have_dollar);
|
||||
whole_item.append(input, 0, paren_begin - has_dollar);
|
||||
whole_item.push_back(INTERNAL_SEPARATOR);
|
||||
whole_item.append(sub_item2);
|
||||
whole_item.push_back(INTERNAL_SEPARATOR);
|
||||
|
|
|
@ -91,7 +91,8 @@ size_t parse_util_get_offset(const wcstring &str, int line, long line_offset) {
|
|||
}
|
||||
|
||||
static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, const wchar_t **end,
|
||||
bool allow_incomplete, bool *inout_is_quoted) {
|
||||
bool allow_incomplete, bool *inout_is_quoted,
|
||||
bool *out_has_dollar) {
|
||||
bool escaped = false;
|
||||
bool is_first = true;
|
||||
bool is_token_begin = true;
|
||||
|
@ -104,10 +105,12 @@ static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, co
|
|||
assert(in && "null parameter");
|
||||
|
||||
const wchar_t *pos = in;
|
||||
const wchar_t *last_dollar = nullptr;
|
||||
auto process_opening_quote = [&](wchar_t quote) -> bool /* ok */ {
|
||||
const wchar_t *q_end = quote_end(pos, quote);
|
||||
if (!q_end) return false;
|
||||
if (*q_end == L'$') {
|
||||
last_dollar = q_end;
|
||||
quoted_cmdsubs.push_back(paran_count);
|
||||
}
|
||||
// We want to report whether the outermost comand substitution between
|
||||
|
@ -131,10 +134,15 @@ static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, co
|
|||
escaped = true;
|
||||
} else if (*pos == L'#' && is_token_begin) {
|
||||
pos = comment_end(pos) - 1;
|
||||
} else if (*pos == L'$') {
|
||||
last_dollar = pos;
|
||||
} else {
|
||||
if (*pos == L'(') {
|
||||
if ((paran_count == 0) && (paran_begin == nullptr)) {
|
||||
paran_begin = pos;
|
||||
if (out_has_dollar) {
|
||||
*out_has_dollar = last_dollar == pos - 1;
|
||||
}
|
||||
}
|
||||
|
||||
paran_count++;
|
||||
|
@ -151,7 +159,7 @@ static int parse_util_locate_cmdsub(const wchar_t *in, const wchar_t **begin, co
|
|||
break;
|
||||
}
|
||||
|
||||
// Check if the ) did complete a quoted command substituion.
|
||||
// Check if the ) did complete a quoted command substitution.
|
||||
if (!quoted_cmdsubs.empty() && quoted_cmdsubs.back() == paran_count) {
|
||||
quoted_cmdsubs.pop_back();
|
||||
// Quoted command substitutions temporarily close double quotes.
|
||||
|
@ -244,7 +252,8 @@ long parse_util_slice_length(const wchar_t *in) {
|
|||
|
||||
int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_offset,
|
||||
wcstring *out_contents, size_t *out_start, size_t *out_end,
|
||||
bool accept_incomplete, bool *inout_is_quoted) {
|
||||
bool accept_incomplete, bool *inout_is_quoted,
|
||||
bool *out_has_dollar) {
|
||||
// Clear the return values.
|
||||
if (out_contents != nullptr) out_contents->clear();
|
||||
*out_start = 0;
|
||||
|
@ -261,7 +270,7 @@ int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_o
|
|||
const wchar_t *bracket_range_end = nullptr;
|
||||
|
||||
int ret = parse_util_locate_cmdsub(valid_range_start, &bracket_range_begin, &bracket_range_end,
|
||||
accept_incomplete, inout_is_quoted);
|
||||
accept_incomplete, inout_is_quoted, out_has_dollar);
|
||||
if (ret <= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
@ -303,7 +312,7 @@ void parse_util_cmdsubst_extent(const wchar_t *buff, size_t cursor_pos, const wc
|
|||
const wchar_t *pos = buff;
|
||||
for (;;) {
|
||||
const wchar_t *begin = nullptr, *end = nullptr;
|
||||
if (parse_util_locate_cmdsub(pos, &begin, &end, true, nullptr) <= 0) {
|
||||
if (parse_util_locate_cmdsub(pos, &begin, &end, true, nullptr, nullptr) <= 0) {
|
||||
// No subshell found, all done.
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -30,10 +30,12 @@ long parse_util_slice_length(const wchar_t *in);
|
|||
/// the end of the string if it was incomplete
|
||||
/// \param accept_incomplete whether to permit missing closing parenthesis
|
||||
/// \param inout_is_quoted whether the cursor is in a double-quoted context.
|
||||
/// \param out_has_dollar whether the command substitution has the optional leading $.
|
||||
/// \return -1 on syntax error, 0 if no subshells exist and 1 on success
|
||||
int parse_util_locate_cmdsubst_range(const wcstring &str, size_t *inout_cursor_offset,
|
||||
wcstring *out_contents, size_t *out_start, size_t *out_end,
|
||||
bool accept_incomplete, bool *inout_is_quoted = nullptr);
|
||||
bool accept_incomplete, bool *inout_is_quoted = nullptr,
|
||||
bool *out_has_dollar = nullptr);
|
||||
|
||||
/// Find the beginning and end of the command substitution under the cursor. If no subshell is
|
||||
/// found, the entire string is returned. If the current command substitution is not ended, i.e. the
|
||||
|
|
|
@ -66,3 +66,11 @@ echo "quoted1""quoted2"(echo unquoted3)"$(echo quoted4)_$(echo quoted5)"
|
|||
|
||||
var=a echo "$var$(echo b)"
|
||||
# CHECK: ab
|
||||
|
||||
# Make sure we don't swallow an escaped dollar.
|
||||
echo \$(echo 1)
|
||||
# CHECK: $1
|
||||
echo "\$(echo 1)"
|
||||
# CHECK: $(echo 1)
|
||||
echo "\$$(echo 1)"
|
||||
# CHECK: $1
|
||||
|
|
Loading…
Reference in New Issue
Block a user