Skip to content

fix(parser): preserve glob expansion inside process substitution#1341

Merged
chaliy merged 1 commit intomainfrom
claude/kind-mayer-aXfY7
Apr 18, 2026
Merged

fix(parser): preserve glob expansion inside process substitution#1341
chaliy merged 1 commit intomainfrom
claude/kind-mayer-aXfY7

Conversation

@chaliy
Copy link
Copy Markdown
Contributor

@chaliy chaliy commented Apr 18, 2026

Summary

The ./"$var"*.ext glob pattern expanded everywhere (top-level, $(...), subshells, pipes — fixed in #1287) except inside <(...) / >(...) process substitution, breaking real-world scripts like bashblog's rebuild_tags() that drives a while read loop from <(ls ./"$p"*.tmp.html).

Root cause

Parser::parse_primary_word reconstructed the process-substitution body by walking the token stream and re-emitting each token as a string. For a QuotedGlobWord(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 resulting Word came back with has_unquoted_glob = false. expand_command_args then 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 / ProcessSubOut token's span ends exactly after the <( / >(; the matching ) token's span starts exactly at the closing paren. Slicing self.input between 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

  • New test test_glob_adjacent_to_quoted_var_in_process_substitution covers the exact bashblog while read < <(ls ./"$p"*.tmp.html) pattern — fails before the fix, passes after.
  • All 2093 bashkit lib tests pass.
  • Existing process-sub tests (test_process_sub_*, test_proc_sub_*) and glob tests (test_glob_*) still green.
  • cargo fmt --check and cargo clippy --all-targets -- -D warnings clean.
  • cargo test --features http_client green across all crates.

Closes #1333

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
@chaliy chaliy merged commit 2dd15b8 into main Apr 18, 2026
32 checks passed
@chaliy chaliy deleted the claude/kind-mayer-aXfY7 branch April 18, 2026 18:21
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(glob): glob * adjacent to quoted variable still fails inside process substitution <(...)

1 participant