Skip to content

shell: keep the backslash before # inside double quotes - #45496

Open
robobun wants to merge 2 commits into
mainfrom
robobun/2c0309c4/shell-dquote-backslash-hash
Open

robobun wants to merge 2 commits into
mainfrom
robobun/2c0309c4/shell-dquote-backslash-hash

Conversation

@robobun

@robobun robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • Inside double quotes the Bun shell drops the backslash before #. echo "a\#b" prints a#b. Bash 5.2 and dash print a\#b.
  • The cause is the list of characters that a backslash escapes in double quotes, in read_char (src/shell_parser/parse.rs:3757). It has the five POSIX characters and also #, since the first shell commit (feat: Bun shell #7748).

Fix

  • Remove # from the list. The backslash stays in the word, as it does in "\x" and "\!".
  • Correct because POSIX 2.2.3 lets a backslash escape only $, `, ", \ and newline inside double quotes. A # inside quotes never starts a comment, so the escape had no use.
  • Verified: test/js/bun/shell/bunshell.test.ts (7 new tests, 5 fail without the fix). Also ran lex, parse, brace, exec and run-shell.

Background

  • read_char returns the next character of a shell script and an escaped flag. Every shell source passes through it: a Bun.$ template, a .sh file and bun exec.
  • Interpolated values and $.escape do not change. The escaper table (BACKSLASHABLE_CHARS) has no #.
  • Considered a lookup in BACKSLASHABLE_CHARS in place of the list. Then the quoting rule depends on the escaper table. One new test pins all 95 printable ASCII characters instead.

Downsides

  • Behaviour change: a script that wrote "\#" to get # now gets \#, as in bash. "#" prints # before and after.
  • Cost for other scripts: none found. The diff removes one comparison and adds no code.
Notes

Repro (1.4.3-canary.1+367d939d9, beside bash 5.2.37 and dash 0.5.12)

bun exec 'echo "a\#b"'        # 1.4.3: a#b        bash, dash, this PR: a\#b
bun exec 'echo "\# Title"'    # 1.4.3: # Title    bash, dash, this PR: \# Title

Unquoted text (echo a\#b prints a#b) and single-quoted text (echo 'a\#b' prints a\#b) are right without the fix and do not change. Two of the new tests pin that.

Checked against bash

  • A script ran echo with a backslash before each of the 95 printable ASCII characters, in 7 double-quote shapes, through Bun.$ and through bash 5.2.37. The shapes: the middle, the start and the end of the quotes, after a non-ASCII character, after a variable, inside $(...), and in quotes around $(...).
  • Without the fix, 7 of 665 outputs differ. All 7 are #, one per shape. With the fix, 0 of 665 differ.
  • Interpolated values: a#b, a\#b, #, \#, a \# b, # lead and x\\#y, each bare, in double quotes, and through $.escape with { raw }. 21 of 21 print the value, without and with the fix.
  • A .sh file and bun exec print the same as Bun.$. They share the lexer.

History

  • The # is in the list in the first shell commit, 1b1760a9c9 (feat: Bun shell #7748). In that commit the comment arm of the lexer already skips a # inside quotes, so the entry never had a function.
  • No test and no doc of the shell has a backslash before #.

Tests

  • 5 of the 7 new tests fail on 1.4.3-canary.1+367d939d9: the four "\#" cases and the table of 95 characters. The unquoted case and the single-quoted case pass on both builds.
  • The non-ASCII case passes its script through { raw }. A template literal gives the shell \u00E9 for é (raw tagged template literals show escapes for non ascii text #8745).
  • With the fix, on a debug build: bunshell.test.ts 458 pass (after the merge with main), lex.test.ts 43 pass, parse.test.ts 18 pass, brace.test.ts 47 pass, exec.test.ts 19 pass, run-shell.test.ts 3 pass.

Open PRs on the same lines


no test proof · iteration 0 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/bun/shell/bunshell.test.ts

Inside double quotes a backslash escapes only $, `, ", \ and newline
(POSIX 2.2.3). The lexer also listed #, so "a\#b" printed a#b where bash
and dash print a\#b.
@robobun

robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 4:05 AM PT - Oct 17th, 2026

❌ @robobun, your commit 8d339b8 has some failures in Build #127295 (All Failures)


🧪   To try this PR locally:

bunx bun-pr 45496

That installs a local version of the PR into your bun-45496 executable, so you can run:

bun-45496 --bun

@robobun

robobun commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Reproduced on 1.4.3-canary.1+367d939d9, beside bash 5.2.37 and dash 0.5.12:

bun exec 'echo "a\#b"'        # Bun: a#b        bash, dash: a\#b
bun exec 'echo "\# Title"'    # Bun: # Title    bash, dash: \# Title

The fix is this PR, #45496.

CI on 8d339b8 (build 127295): 179 of 181 jobs passed and no test failed. test/js/bun/shell/bunshell.test.ts passed on each lane that ran, Windows and darwin aarch64 included. The other 2 jobs are darwin any x64 - test-bun. They expired before an agent took them, so they did not run. The diff is ready for review.

@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository: oven-sh/bun/.coderabbit.yaml
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 8d027743-5407-41c1-8b00-73b706eb289d
📥 Commits

Reviewing files that changed from the base of the PR and between 5866b5f and dafc16a.

📒 Files selected for processing: 2
  • src/shell_parser/parse.rs
  • test/js/bun/shell/bunshell.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

Walkthrough

The shell parser now preserves a backslash before # inside double-quoted input. Tests cover this behavior and check how backslashes are handled before printable ASCII characters and in related quoting cases.

Changes

Shell Quoting

Layer / File(s) Summary
Double-quoted escape rules
src/shell_parser/parse.rs, test/js/bun/shell/bunshell.test.ts
The parser no longer treats # as escapable inside double quotes. Tests cover #, other printable ASCII characters, and related quoting cases.

Suggested reviewers: jarred-sumner

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to dafc1

The shell retains the backslash before # inside double quotes, matching the stated behavior. No concrete merge-blocking risk was found.

Pre-merge checks: Passed 4
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Title check Passed The title clearly and concisely describes the main change: preserving the backslash before # inside double quotes.
Description check Passed The description explains the problem, fix, behavior change, standards basis, test coverage, verification results, and potential downsides. It does not use the exact template headings, but it provides …
Full details: Description check

Explanation

The description explains the problem, fix, behavior change, standards basis, test coverage, verification results, and potential downsides. It does not use the exact template headings, but it provides the required information in greater detail.

✨ Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding (a pre-existing gap on a trailing backslash, not introduced here), I checked the one-line change for sibling sites and ruled out: the lexer's # arm at src/shell_parser/parse.rs:2518 already treats # as literal in both quote states, so the removed entry had no other consumer; the ASCII fast path lists \ in SPECIAL_CHARS, so both Src::Ascii and Src::Unicode reach read_char; BACKSLASHABLE_CHARS (line 3937) has no #, so $.escape and interpolated values are unaffected; and src/js/builtins/shell.ts passes strings.raw, so the new "\#" TestBuilder cases exercise the fix rather than a cooked #.

Extended reasoning...

The change removes # from the set of characters a backslash escapes inside double quotes in the Bun shell lexer and adds seven tests, including an exhaustive printable-ASCII table and a non-ASCII { raw } case. It touches no security-sensitive surface; it is a parser behaviour change aligned with POSIX 2.2.3 and bash/dash. The only confirmed finding is a pre-existing truncation on a trailing backslash in an open double quote that the PR does not address, so a human should weigh whether it belongs in this PR; the core change itself has no sibling sites left unfixed.

Additional findings (outside the current diff — GitHub can't attach inline comments there):

  • 🟣 src/shell_parser/parse.rs — A double-quoted word that ends the script with a trailing backslash is silently truncated instead of producing an error, and this PR rewrites the same escape rule without closing that gap. At src/shell_parser/parse.rs:3749 the ? on index_next returns None when the backslash is the last byte, so eat() at parse.rs:2383-2388 treats it as end of input and pushes Eof; no "unterminated" error is raised for the open double quote (only parse.rs:2892-2894 report unclosed constructs). Bash reports an unexpected EOF for echo "a\. Fix: when a backslash is the final character inside double quotes, return it as literal text (parse.rs:3745-3749) and have the lexer report an unclosed double quote at parse.rs:2889 rather than ending silently.

    Why this was flagged

    Trigger: a script whose last character is a backslash inside an open double quote, e.g. echo "abc\ arriving through Bun.$, bun exec, or a .sh file. src/shell_parser/parse.rs:3749 uses ? on index_next, so read_char returns None; parse.rs:3707-3717 eat() then returns None and parse.rs:2383-2388 calls break_word and exits the loop; parse.rs:2889-2900 only errors for an open subshell and pushes Eof. The result is the command running with abc as its argument and exit code 0, where bash and dash fail with an unexpected EOF. The base commit has the same ? (confirmed in git show of 5866b5f lines 3740-3748), so this is pre-existing, but the PR edits the adjacent escape rule in the name of POSIX 2.2.3 and leaves this sibling case of the same backslash-in-double-quotes rule wrong. Remedy: treat the trailing backslash as literal and report the unterminated quote.

    Verification: Pre-existing. Trigger: any script whose input ends with a backslash inside an open double quote. /home/claude/bun/src/shell_parser/parse.rs:3745-3749 does index_next().map(...)?, so 2889-2899 pushes Token::Eof; no "unterminated quote" diagnostic exists, so the command runs with exit 0 where bash exits 2. The ? at 3749 is byte-identical on the base, so merging this PR makes nothing worse.

@robobun

robobun commented Oct 17, 2026

Copy link
Copy Markdown
Collaborator Author

On the review finding about a backslash as the last byte inside an open double quote (echo "abc\):

  • It is real, and it is not from this PR. On 1.4.3-canary.1+367d939d9 the script prints abc with exit 0. bash 5.2.37 and dash 0.5.12 exit 2.
  • The backslash is not the cause. echo "abc and echo 'abc give the same result. The lexer does not report a quote that is still open at the end of the script. At the end of Lexer::lex only $(, a backtick and ( get an Unclosed ... error.
  • shell: error on unterminated single/double quote #34902 added that error for quotes. It was closed in a cleanup of stale PRs, and no maintainer objected to the fix.
  • This PR does not change. A new parse error for scripts that run today is a separate behaviour change, so it belongs in a follow-up to shell: error on unterminated single/double quote #34902.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants