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
19 changes: 19 additions & 0 deletions crates/bashkit/src/interpreter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5795,12 +5795,31 @@ impl Interpreter {
"maximum nesting depth exceeded in command substitution".to_string(),
));
}
// Command substitution runs in a subshell: snapshot trap state
// so EXIT traps set inside $() fire and don't leak to parent.
let saved_traps = self.traps.clone();
let mut stdout = String::new();
for cmd in commands {
let cmd_result = self.execute_command(cmd).await?;
stdout.push_str(&cmd_result.stdout);
self.last_exit_code = cmd_result.exit_code;
}
// Fire EXIT trap set inside the command substitution
if let Some(trap_cmd) = self.traps.get("EXIT").cloned()
&& saved_traps.get("EXIT") != Some(&trap_cmd)
&& let Ok(trap_script) = Parser::with_limits(
&trap_cmd,
self.limits.max_ast_depth,
self.limits.max_parser_operations,
)
.parse()
&& let Ok(trap_result) =
self.execute_command_sequence(&trap_script.commands).await
{
stdout.push_str(&trap_result.stdout);
}
// Restore parent trap state
self.traps = saved_traps;
self.counters.pop_function();
self.subst_generation += 1;
let trimmed = stdout.trim_end_matches('\n');
Expand Down
38 changes: 38 additions & 0 deletions crates/bashkit/tests/spec_cases/bash/command-subst.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,41 @@ echo "$result"
### expect
(x)(y)(z)
### end

### subst_exit_trap_captured
# EXIT trap output should be captured inside $(), not leak to parent
result="$(trap 'echo TRAPPED' EXIT; echo hello)"
echo "captured=[${result}]"
### expect
captured=[hello
TRAPPED]
### end

### subst_exit_trap_with_explicit_exit
# EXIT trap fires on explicit exit inside $()
result="$(trap 'echo CLEANUP' EXIT; echo data; exit 0)"
echo "captured=[${result}]"
### expect
captured=[data
CLEANUP]
### end

### subst_exit_trap_no_leak
# Trap output must not leak to parent stdout
out="$(trap 'echo INSIDE' EXIT; echo body)"
echo "out=[${out}]"
### expect
out=[body
INSIDE]
### end

### subst_exit_trap_isolation
# EXIT trap in $() should not affect parent traps
trap 'echo PARENT' EXIT
result="$(trap 'echo CHILD' EXIT; echo inner)"
echo "result=[${result}]"
trap - EXIT
### expect
result=[inner
CHILD]
### end
Loading