Skip to content

fix(parser): accept {arr[i]} as an fd-variable redirect - #2605

Merged
chaliy merged 1 commit into
mainfrom
claude/fdvar-array
Oct 8, 2026
Merged

chaliy merged 1 commit into
mainfrom
claude/fdvar-array

Conversation

@chaliy

@chaliy chaliy commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Requested by Михайло · project thread

What changed

An fd-variable redirect accepts an array element, {name[i]}, not only a plain identifier, and its value is resolved through the array. A coproc publishes its descriptors as ${NAME[0]} and ${NAME[1]}, so exec {NAME[1]}>&- is how scripts close the write end.

Why

Debian-oracle case coproc-basic. The old parse left {UP[1]} as a word, so the shell tried to run it as a command.

Before / After

Script Before After (matches bash)
coproc UP { echo x; }; exec {UP[1]}>&- bash: {UP[1]}: command not found, status 127 closes the descriptor, status 0
exec {fd}>f; echo one >&$fd; exec {fd}>&- worked unchanged

New spec file fdvar-array.test.sh (2 cases), verified against bash 5.2.

Risk

Low. The name must still start with a letter or _, so a brace group or a brace expansion is not mistaken for an fd variable. A coproc body that filters what the script writes to it still runs before the write (bashkit runs the body synchronously), so coproc UP { tr a-z A-Z; } followed by a write and a read gives an empty line; that part of the oracle case is unchanged.

Checklist

  • Failing spec first, verified against real bash
  • cargo test -p bashkit --lib, --test integration
  • cargo clippy --all-targets -D warnings, cargo fmt --check
  • knowledge/log.md updated

https://claude.ai/code/session_019aFikmptPc91Fj4N2iDXQA


Generated by Claude Code

@chaliy chaliy self-assigned this Oct 7, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit 1150a57 Commit Preview URL

Branch Preview URL
Oct 07 2026, 11:51 PM

exec {COPROC[1]}>&- closed nothing and ran {COPROC[1]} as a command, because
the fd-variable name had to be a plain identifier.

Claude-Session: https://claude.ai/code/session_019aFikmptPc91Fj4N2iDXQA
@chaliy
chaliy force-pushed the claude/fdvar-array branch from d23a4f1 to 1150a57 Compare October 7, 2026 23:51
@chaliy
chaliy merged commit 951eab5 into main Oct 8, 2026
44 checks passed
@chaliy
chaliy deleted the claude/fdvar-array branch October 8, 2026 00:17
chaliy added a commit that referenced this pull request Oct 8, 2026
…2617)

<!-- ccr-projects-attribution: {"github_login":"chaliy"} -->
_Requested by **Михайло** · [project
thread](https://claude.ai/code/project/chan_019YEkBJuZMHJdXrjom86cGX?thread=cmsg_019YEkBJuZMHJdXrjom86cGX6hNmzgZPpfCgsgRMptbhdM)_

## What changed

`{arr[i]}` is recognised as a redirect's fd variable wherever one may
appear,
not only in the close form: `exec {A[1]}> file` and `exec {A[1]}>&-`
both work,
and a subscript may be a variable (`{A[i]}`).

## Why

Follow-up to #2605. That PR allowed brackets in the name but the check
still
required the word to be a single literal part, and `{A[1]}` lexes as
several —
`[` opens a glob bracket. So the redirect parsed as a command:

```
$ exec {A[1]}> o.txt
bash: {A[1]}: command not found    # and status 127
```

## Before / After

```bash
declare -a A
A[1]=7
exec {A[1]}> o.txt
echo hi >&"${A[1]}"
exec {A[1]}>&-
cat o.txt
```

| | output | status |
|---|---|---|
| bash 5.2 | `hi` | 0 |
| bashkit before | `bash: {A[1]}: command not found` | 127 |
| bashkit after | `hi` | 0 |

A brace word that is not an fd variable is still a command: `{A-1}>
o.txt`
reports `command not found` in both shells.

## Risk

Low and parser-local. The name is now read from the word's literal parts
joined; a word with any non-literal part (an expansion, a substitution)
yields
nothing and is left alone, so `${x}> file` and `$(cmd)> file` parse as
before.

## Checklist

- [x] `cargo test -p bashkit --lib` (2920 passed)
- [x] `cargo test -p bashkit --test integration` (1372 passed)
- [x] New spec case and 4 integration tests, verified against bash 5.2
- [x] `bash_comparison` differential tests against real bash
- [x] `cargo clippy -p bashkit --all-targets -- -D warnings`
- [x] `cargo fmt --all --check`
- [x] `cargo test -p bashkit-cli` (56 passed)
- [x] `just check-okf`
- [x] Stack probe: 1900 KB, inside the two-MiB TM-DOS-020 budget
- [x] Knowledge updated (`log.md`)

https://claude.ai/code/session_019aFikmptPc91Fj4N2iDXQA

---
_Generated by [Claude
Code](https://claude.ai/code/session_019aFikmptPc91Fj4N2iDXQA)_
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.

1 participant