fix(parser): preserve glob expansion inside process substitution#1341
Merged
fix(parser): preserve glob expansion inside process substitution#1341
Conversation
Token-string reconstruction of the `<(...)` / `>(...)` body wrapped every `QuotedWord` and `QuotedGlobWord` in `"..."`, collapsing the unquoted-glob boundary and dropping `has_unquoted_glob` on re-parse. Patterns like `./"$var"*.ext` therefore skipped glob expansion only inside process substitution (top-level, `$(...)`, subshell, and pipes already worked after #1287). Extract the body directly from the original source via span offsets and hand it to the inner parser. This preserves exact quoting, escapes, and whitespace without any lossy reconstruction, and deletes ~150 lines of per-token rebuild logic. Closes #1333
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
7 tasks
chaliy
added a commit
that referenced
this pull request
Apr 19, 2026
…csub while-read Issue #1343 reported that a sourced function using `{...} 3>&1 >file` plus `while read; done < <(ls glob)` produced an empty output file. Verified on current main that #1341 (glob inside process substitution) already resolves this. Add a spec case that exercises the exact pattern so a regression is caught before it ships. Closes #1343
5 tasks
chaliy
added a commit
that referenced
this pull request
Apr 19, 2026
…csub while-read (#1345) ## Summary Issue #1343 reported that a sourced function mixing `{ ... } 3>&1 >"$file"` with a `while read; done < <(ls glob)` loop produced an empty output file — the while body never ran. On current `main` this pattern already works: PR #1341 (glob inside `<(...)`) resolved the underlying cause. This PR adds a spec case that exercises the exact shape from the issue so any future regression is caught before shipping. ## What - New spec case `source_function_fd3_block_redirect_with_procsub_while_read` in `crates/bashkit/tests/spec_cases/bash/source.test.sh` - Test creates `/tmp/issue1343_dir/*.txt`, sources a file defining `myfunc`, calls it, then asserts both the fd3 progress stream on stdout and the `>file` contents populated by the `while read` loop ## Test plan - [x] New spec case passes (`cargo test --test spec_tests bash_spec_tests`) - [x] All 1978 bash spec tests green (0 failed, 25 skipped) - [x] Expected output verified against real bash — identical - [x] `cargo fmt --check` clean - [x] `cargo clippy --all-targets --all-features -- -D warnings` clean Closes #1343
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
./"$var"*.extglob pattern expanded everywhere (top-level,$(...), subshells, pipes — fixed in #1287) except inside<(...)/>(...)process substitution, breaking real-world scripts like bashblog'srebuild_tags()that drives awhile readloop from<(ls ./"$p"*.tmp.html).Root cause
Parser::parse_primary_wordreconstructed the process-substitution body by walking the token stream and re-emitting each token as a string. For aQuotedGlobWord(w)token (introduced in #1287 to carry the "has unquoted glob but IFS-split-suppressed" bit), the reconstruction wrapped the entire processed word in"...". When the inner parser re-lexed that string, everything ended up inside one pair of double quotes, so the unquoted*was swallowed and the resultingWordcame back withhas_unquoted_glob = false.expand_command_argsthen took the quoted-word fast path and skipped glob expansion.Fix
Extract the body directly from the original source via span offsets and hand it to the inner parser. The
ProcessSubIn/ProcessSubOuttoken's span ends exactly after the<(/>(; the matching)token's span starts exactly at the closing paren. Slicingself.inputbetween those two offsets yields the exact original body, preserving quoting, escapes, and whitespace without any lossy reconstruction.This deletes ~150 lines of per-token rebuild logic and fixes the glob bug as a side effect.
Test plan
test_glob_adjacent_to_quoted_var_in_process_substitutioncovers the exact bashblogwhile read < <(ls ./"$p"*.tmp.html)pattern — fails before the fix, passes after.bashkitlib tests pass.test_process_sub_*,test_proc_sub_*) and glob tests (test_glob_*) still green.cargo fmt --checkandcargo clippy --all-targets -- -D warningsclean.cargo test --features http_clientgreen across all crates.Closes #1333