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
30 changes: 25 additions & 5 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2169,9 +2169,18 @@ impl Interpreter {
arr.insert(key, value);
}
} else {
// Indexed array: use numeric index
let index: usize =
self.evaluate_arithmetic(index_str).try_into().unwrap_or(0);
// Indexed array: use numeric index (supports negative)
let raw_idx = self.evaluate_arithmetic(index_str);
let index = if raw_idx < 0 {
let len = self
.arrays
.get(&assignment.name)
.and_then(|a| a.keys().max().map(|m| m + 1))
.unwrap_or(0) as i64;
(len + raw_idx).max(0) as usize
} else {
raw_idx as usize
};
let arr = self.arrays.entry(assignment.name.clone()).or_default();
if assignment.append {
let existing = arr.get(&index).cloned().unwrap_or_default();
Expand Down Expand Up @@ -4029,8 +4038,19 @@ impl Interpreter {
result.push_str(value);
}
} else {
// ${arr[n]} - get specific element
let idx: usize = self.evaluate_arithmetic(index).try_into().unwrap_or(0);
// ${arr[n]} - get specific element (supports negative indexing)
let raw_idx = self.evaluate_arithmetic(index);
let idx = if raw_idx < 0 {
// Negative index: count from end
let len = self
.arrays
.get(name)
.map(|a| a.keys().max().map(|m| m + 1).unwrap_or(0))
.unwrap_or(0) as i64;
(len + raw_idx).max(0) as usize
} else {
raw_idx as usize
};
if let Some(arr) = self.arrays.get(name) {
if let Some(value) = arr.get(&idx) {
result.push_str(value);
Expand Down
30 changes: 30 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/arrays.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,33 @@ printf 'hello\nworld\n' | mapfile -t; echo ${MAPFILE[0]}; echo ${MAPFILE[1]}
hello
world
### end

### array_negative_index_last
# Negative index gets last element
arr=(a b c d e); echo "${arr[-1]}"
### expect
e
### end

### array_negative_index_second_last
# Negative index gets second-to-last
arr=(a b c d e); echo "${arr[-2]}"
### expect
d
### end

### array_negative_index_first
# Negative index wrapping to first
arr=(a b c); echo "${arr[-3]}"
### expect
a
### end

### array_negative_all_values
# ${arr[@]} with negative indexing for assignment
arr=(10 20 30 40 50)
arr[-1]=99
echo "${arr[@]}"
### expect
10 20 30 40 99
### end
8 changes: 4 additions & 4 deletions specs/009-implementation-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,23 +103,23 @@ Bashkit implements IEEE 1003.1-2024 Shell Command Language. See

## Spec Test Coverage

**Total spec test cases:** 1170 (1165 pass, 5 skip)
**Total spec test cases:** 1174 (1169 pass, 5 skip)

| Category | Cases | In CI | Pass | Skip | Notes |
|----------|-------|-------|------|------|-------|
| Bash (core) | 809 | Yes | 804 | 5 | `bash_spec_tests` in CI |
| Bash (core) | 813 | Yes | 808 | 5 | `bash_spec_tests` in CI |
| AWK | 96 | Yes | 96 | 0 | loops, arrays, -v, ternary, field assign, getline, %.6g |
| Grep | 76 | Yes | 76 | 0 | -z, -r, -a, -b, -H, -h, -f, -P, --include, --exclude, binary detect |
| Sed | 75 | Yes | 75 | 0 | hold space, change, regex ranges, -E |
| JQ | 114 | Yes | 114 | 0 | reduce, walk, regex funcs, --arg/--argjson, combined flags, input/inputs, env |
| **Total** | **1170** | **Yes** | **1165** | **5** | |
| **Total** | **1174** | **Yes** | **1169** | **5** | |

### Bash Spec Tests Breakdown

| File | Cases | Notes |
|------|-------|-------|
| arithmetic.test.sh | 57 | includes logical, bitwise, compound assign, increment/decrement |
| arrays.test.sh | 20 | includes indices, `${arr[@]}` / `${arr[*]}` expansion |
| arrays.test.sh | 27 | indices, `${arr[@]}` / `${arr[*]}`, negative indexing `${arr[-1]}` |
| background.test.sh | 4 | |
| bash-command.test.sh | 34 | bash/sh re-invocation |
| brace-expansion.test.sh | 21 | {a,b,c}, {1..5}, for-loop brace expansion |
Expand Down
Loading