Repository navigation
shell: stop field splitting command substitution in export operands and assignments - #40764
Conversation
An unquoted $(cmd) in an assignment value was field split into several words. A plain assignment re-joined the words with spaces, but an export operand like export NAME=$(cmd) kept the split and exported the extra words as separate variables. Mark the expansion of an assignment value with assign_ctx, which skips the field split of command substitution output. Cmd applies it to a declaration-builtin operand that starts with a literal NAME= prefix. Assigns applies it to every assignment value, so interior whitespace is now preserved instead of collapsed.
|
Warning Review limit reached
On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file. Or wait 15 minutes for your next included review. View limit detailsLimit details: You’ve used all 5 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
Comment |
|
Updated 4:16 AM PT - Aug 28th, 2026
❌ @robobun, your commit dcfe3d8 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 40764That installs a local version of the PR into your bun-40764 --bun |
|
CI on dcfe3d8: no failure touches this diff. The shell suites passed on every lane. test/js/web/url/url.test.ts fails on darwin x64 on main as well (ICU IDNA table test). The other failures passed on retry or in isolation (hot reload, bun init, install, streams, net, http2, fetch leak, napi worker tests). |
|
#40710 (identifier validation in If this PR lands second, consider a test with a split word that is not an identifier, for example |
Fixes #40763.
Problem
export NAME=$(echo "hello world")setsNAME=helloand exports a stray empty variableworld. bash and dash giveNAME=hello world.src/runtime/shell/states/Cmd.rs, theExpandingArgsarm ofchild_done). The expansion does not know the word is an assignment operand ofexport.NAME=$(cmd)hides the same split:Assigns::child_donere-joins the words with single spaces, so runs of whitespace and newlines collapse.Fix
assign_ctxflag toExpansion. When set, the command substitution output is not field split (the same path as a quoted"$(...)"). POSIX 2.9.1: an assignment word undergoes no field splitting.Cmdsets the flag for an operand ofexport(the only declaration builtin) that starts with a literalNAME=prefix whereNAMEis a valid identifier. A word whose name comes from an expansion, for exampleexport $(echo "A=a b"), still splits, like in bash.Assignssets the flag for every assignment value, soVAR=$(echo a && echo b)now keeps the newline instead of collapsing toa b.test/js/bun/shell/bunshell.test.tsfail on current bun and pass with the fix. The fulltest/js/bun/shell/suite passes except pre-existing environment failures (root-user permission tests, ASAN timeouts), which fail the same way without the change.Background
Cmdexpands each argv atom through anExpansionchild, which returns a buffer plusbounds, the offsets that split the buffer into argv words.$(...)runspost_subshell_expansion, which turns newlines into spaces and splits on space runs. A quoted"$(...)"skips that and only trims trailing whitespace.assign_ctxreuses the quoted path.Notes
bun -e 'await Bun.$export NAME=$(echo "hello world"); echo "NAME=[$NAME]"'printsNAME=[hello]before,NAME=[hello world]after.export), the split also turns into a hard error when a split word is not a valid identifier, for exampleexport NAME=$(echo "a b-c"). This fix removes the split, so the two compose.CondExprpassesassign_ctx: falseto keep its behavior unchanged. Bash also suppresses splitting inside[[ ]], but that is a separate concern.$X) was never field split in the Bun shell, so only command substitution output was affected.SimpleAtom::Text, soexport "NAME="$(cmd)also gets assignment treatment, while bash splits there. Restoring the quoting bit needs a parser AST change. The effect is benign:exportalready treats any operand with=as an assignment at runtime, so the flag only stops split words from becoming stray exported variables. zsh does not split here either.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