Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7392,10 +7392,10 @@ impl Interpreter {
for redirect in redirects {
// Resolve fd from either explicit fd or {var} fd-variable syntax
let resolved_fd_var: Option<i32> = redirect.fd_var.as_ref().and_then(|var_name| {
self.scoped
.variables
.get(var_name)
.and_then(|val| val.parse::<i32>().ok())
self.expand_name_or_array_element(var_name)
.trim()
.parse::<i32>()
.ok()
});
match redirect.kind {
RedirectKind::Input => {
Expand Down
5 changes: 4 additions & 1 deletion crates/bashkit/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2749,9 +2749,12 @@ impl<'a> Parser<'a> {
&& s.starts_with('{')
&& s.ends_with('}')
&& s.len() > 2
// `{fd}` or `{arr[i]}` (a coproc hands its descriptors out as
// `${NAME[0]}` / `${NAME[1]}`, and `exec {NAME[1]}>&-` closes one).
&& s[1..s.len() - 1]
.chars()
.all(|c| c.is_alphanumeric() || c == '_')
.all(|c| c.is_alphanumeric() || matches!(c, '_' | '[' | ']'))
&& s[1..s.len() - 1].starts_with(|c: char| c.is_alphabetic() || c == '_')
{
let var_name = s[1..s.len() - 1].to_string();
words.pop();
Expand Down
21 changes: 21 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/fdvar-array.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### fd_var_array_subscript_closes
exec {fd}>/tmp/fv.txt
echo one >&$fd
exec {fd}>&-
cat /tmp/fv.txt
coproc UP { echo from_coproc; }
exec {UP[1]}>&-
read -r line <&"${UP[0]}"
echo "[$line]"
### expect
one
[from_coproc]
### end

### fd_var_array_is_not_a_command
coproc C { echo x; }
exec {C[1]}>&- 2>&1
echo "rc=$?"
### expect
rc=0
### end
1 change: 1 addition & 0 deletions knowledge/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
* **Fix**: parameter expansion and `mapfile` gaps from the Debian oracle. `${v~}`/`${v~~}` toggle case (also per array element). `\/` in a `${v/pat/rep}` replacement is a plain `/`. Unquoted `${!prefix@}`, `${!ref}`, `${v:off:len}`, `${a[@]:o:n}` and `${v@op}` now undergo field splitting like other expansions, so `for v in ${!cfg_@}` loops per name. `mapfile` gained `-d DELIM` (`''` is NUL), `-n`, `-s`, `-O` (keeps other elements) and grouped options; `-u` other than 0 and `-C`/`-c` exit 2 (L-MAPFILE-001).
* **Fix**: `[[ a < b ]]` and `[[ a > b ]]` compare strings instead of being swallowed as redirections (the conditional grammar has no redirects), so `[[ a < B ]]` is false in byte order like bash. `shopt` listings pad the name to 15 columns and separate the state with a tab, matching bash.
* **Fix**: arithmetic evaluation reports errors instead of yielding 0. `$((1/0))` and `$((08))` now print bash's `line N: EXPR: division by 0 (error token is "0")` / `value too great for base` and abort the command with status 1; `(( ))` and `let` print the same with their `((: ` / `let: ` prefix and fail without aborting the script. A new `arith_error` channel on the interpreter carries the failure out of the `&self` evaluator, reported where `set -u` errors are. Error lines say `bash: line N:` because the interpreter does not track the running script's path (see architecture).
* **Fix**: `{name[i]}` is accepted as an fd-variable redirect, so `exec {COPROC[1]}>&-` closes that descriptor instead of running `{COPROC[1]}` as a command. Fd-variable values resolve through array elements, which is how a coproc hands its descriptors out.
* **Decision**: CPython's workload priority is agents running `python3` for scripted logic (processing a file, transforming data). Low-use stdlib modules are no longer shipped: `unittest`, `doctest`, profilers, `compileall`/`zipapp`, `dbm`/`shelve`, `plistlib`, `wave`, `netrc`, `cmd`, `tty`/`pty`, and `bz2`/`lzma`/`compression.zstd` (no C codec in the guest anyway). Stdlib zip 3.58 to 3.16 MB. L-CPY-009 updated. See [CPython WebAssembly Runtime](runtimes/cpython-wasm.md#workload-priority).
* **Feature**: `yes` and `seq` stream into pipelines. As a producer stage they write chunks into the pipe as they go (`Context::stdout_stream`), so `yes | head -1` and `seq 1000000 | head -1` stop after the first chunk with `PIPESTATUS` `141 0` instead of running to the output cap and printing a truncation notice. Step 2 of the roadmap (streaming builtin I/O), starting with the generators.
* **Feature**: `sudo` and `busybox`, the top missing commands in the gap replay (`sudo tee /etc/x <<EOF`, `busybox tar --help`). `sudo` skips its options and runs the command (leading `VAR=VALUE` through `env`, `-s`/`-i` through `bash -c`; `-v`/`-k` succeed, `-l` lists ALL); `busybox APPLET` runs the builtin of that name, `--list` lists them (L-SUDO-001). `nohup`/`nice`/`flock` no longer steal the inner command's `--help`.
Expand Down
Loading