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
10 changes: 7 additions & 3 deletions crates/bashkit/src/builtins/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl Builtin for False {
}

/// The exit builtin - exit the shell with a status code.
/// Bash truncates exit codes to 8-bit unsigned range (0-255) via `& 0xFF`.
pub struct Exit;

#[async_trait]
Expand All @@ -52,7 +53,8 @@ impl Builtin for Exit {
.args
.first()
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(0);
.unwrap_or(0)
& 0xFF;

// For now, we just return the exit code
// In a full implementation, this would terminate the shell
Expand Down Expand Up @@ -92,7 +94,8 @@ impl Builtin for Continue {
}
}

/// The return builtin - return from a function
/// The return builtin - return from a function.
/// Bash truncates return codes to 8-bit unsigned range (0-255) via `& 0xFF`.
pub struct Return;

#[async_trait]
Expand All @@ -102,7 +105,8 @@ impl Builtin for Return {
.args
.first()
.and_then(|s| s.parse::<i32>().ok())
.unwrap_or(0);
.unwrap_or(0)
& 0xFF;

Ok(ExecResult::with_control_flow(ControlFlow::Return(
exit_code,
Expand Down
15 changes: 12 additions & 3 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2894,10 +2894,19 @@ impl Interpreter {
});
}

// If name is empty, this is an assignment-only command - keep permanently.
// Preserve last_exit_code from any command substitution in the value
// (bash behavior: `x=$(false)` sets $? to 1).
// If name is empty after expansion, behavior depends on context:
// - Quoted empty string ('', "", "$empty") -> "command not found" (exit 127)
// - Unquoted expansion that vanished ($empty, $(true)) -> no-op, preserve $?
// - Assignment-only (VAR=val) -> no-op, preserve $?
if name.is_empty() {
if command.name.quoted && command.assignments.is_empty() {
// Bash: '' as a command is "command not found"
self.last_exit_code = 127;
return Ok(ExecResult::err(
"bash: : command not found\n".to_string(),
127,
));
}
return Ok(ExecResult {
stdout: String::new(),
stderr: String::new(),
Expand Down
9 changes: 0 additions & 9 deletions crates/bashkit/tests/spec_cases/bash/exit-status.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ status=255

### exit_truncation_256
# exit 256 truncates to 0
### skip: TODO exit status not truncated to 8-bit range
bash -c 'exit 256'
echo status=$?
### expect
Expand All @@ -21,7 +20,6 @@ status=0

### exit_truncation_257
# exit 257 truncates to 1
### skip: TODO exit status not truncated to 8-bit range
bash -c 'exit 257'
echo status=$?
### expect
Expand All @@ -30,7 +28,6 @@ status=1

### exit_negative_minus1
# exit -1 wraps to 255
### skip: TODO negative exit codes not wrapped to unsigned 8-bit
bash -c 'exit -1' 2>/dev/null
echo status=$?
### expect
Expand All @@ -39,7 +36,6 @@ status=255

### exit_negative_minus2
# exit -2 wraps to 254
### skip: TODO negative exit codes not wrapped to unsigned 8-bit
bash -c 'exit -2' 2>/dev/null
echo status=$?
### expect
Expand All @@ -56,7 +52,6 @@ status=255

### return_truncation_256
# return 256 truncates to 0
### skip: TODO return status not truncated to 8-bit range
f() { return 256; }; f
echo status=$?
### expect
Expand All @@ -65,7 +60,6 @@ status=0

### return_truncation_257
# return 257 truncates to 1
### skip: TODO return status not truncated to 8-bit range
f() { return 257; }; f
echo status=$?
### expect
Expand All @@ -74,7 +68,6 @@ status=1

### return_negative_minus1
# return -1 wraps to 255
### skip: TODO negative return codes not wrapped to unsigned 8-bit
f() { return -1; }; f 2>/dev/null
echo status=$?
### expect
Expand All @@ -83,7 +76,6 @@ status=255

### return_negative_minus2
# return -2 wraps to 254
### skip: TODO negative return codes not wrapped to unsigned 8-bit
f() { return -2; }; f 2>/dev/null
echo status=$?
### expect
Expand All @@ -92,7 +84,6 @@ status=254

### if_empty_command
# If empty command string - '' as command should fail
### skip: TODO empty string as command not treated as failed command
if ''; then echo TRUE; else echo FALSE; fi
### exit_code: 0
### expect
Expand Down
Loading