diff --git a/plugins/dotenv/dotenv.plugin.zsh b/plugins/dotenv/dotenv.plugin.zsh index d12f5b609..f8d4b67d8 100644 --- a/plugins/dotenv/dotenv.plugin.zsh +++ b/plugins/dotenv/dotenv.plugin.zsh @@ -143,11 +143,9 @@ _parse_dotenv_content() { fi ## END: FILTER COMMAND EXPANSION - # Unquote the value to handle special characters and multiline values - value="${(Q)value}" - # Single-quoted values are fully literal and must not participate in expansion. if [[ "$raw_value" == \'*\' ]]; then + value="${(Q)value}" parsed_vars[$key]="$value" if [[ "$mode" == "export" ]]; then typeset -x "$key"="$value" @@ -155,6 +153,13 @@ _parse_dotenv_content() { continue fi + # Preserve escaped dollars so they remain literal after unquoting. + local escaped_dollar_placeholder=$'\001DOTENV_ESCAPED_DOLLAR\001' + value="${value//\\\$/$escaped_dollar_placeholder}" + + # Unquote the value to handle special characters and multiline values. + value="${(Q)value}" + # Expand previously parsed in-file variables without partial name matches. local expanded="" prefix remainder="$value" var_name while [[ "$remainder" == *'$'* ]]; do @@ -179,6 +184,7 @@ _parse_dotenv_content() { fi done value="${expanded}${remainder}" + value="${value//$escaped_dollar_placeholder/\$}" # Store in parsed vars (for in-file expansion) parsed_vars[$key]="$value" diff --git a/plugins/dotenv/tests/basic-parsing.zunit b/plugins/dotenv/tests/basic-parsing.zunit index 7061cca4b..ef899f706 100644 --- a/plugins/dotenv/tests/basic-parsing.zunit +++ b/plugins/dotenv/tests/basic-parsing.zunit @@ -366,3 +366,23 @@ EOF assert "DOTENV_TEST_VARS" var_same_as "expected_vars" } + +@test 'parse preserves escaped dollar signs before variable expansion' { + > "$fixture" <<'EOF' +BAR=expanded +ESCAPED_UNQUOTED=foo\$BAR +ESCAPED_DOUBLE="foo\$BAR" +ESCAPED_BRACED="\${BAR}" +EOF + + expected_vars=( + BAR 'expanded' + ESCAPED_UNQUOTED 'foo$BAR' + ESCAPED_DOUBLE 'foo$BAR' + ESCAPED_BRACED '${BAR}' + ) + + _parse_dotenv_test "$fixture" + + assert "DOTENV_TEST_VARS" var_same_as "expected_vars" +}