Skip to content

Commit 4039d7a

Browse files
committed
fix(interpreter): check errexit_suppressed in execute_script_body
set -e incorrectly exited when a for/while/until loop body ended with an AND-OR chain (e.g. [[ test ]] && cmd) that returned non-zero. Check result.errexit_suppressed in execute_script_body to suppress errexit for compound commands whose non-zero exit came from && or ||. Closes #873
1 parent 9752fe0 commit 4039d7a

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,7 +1142,8 @@ impl Interpreter {
11421142
// Run ERR trap on non-zero exit (unless in conditional chain)
11431143
if exit_code != 0 {
11441144
let suppressed = matches!(command, Command::List(_))
1145-
|| matches!(command, Command::Pipeline(p) if p.negated);
1145+
|| matches!(command, Command::Pipeline(p) if p.negated)
1146+
|| result.errexit_suppressed;
11461147
if !suppressed {
11471148
self.run_err_trap(&mut stdout, &mut stderr).await;
11481149
}
@@ -1151,9 +1152,11 @@ impl Interpreter {
11511152
// errexit (set -e): stop on non-zero exit for top-level simple commands.
11521153
// List commands handle errexit internally (with && / || chain awareness).
11531154
// Negated pipelines (! cmd) explicitly handle the exit code.
1155+
// Compound commands propagate errexit_suppressed from inner AND-OR chains.
11541156
if self.is_errexit_enabled() && exit_code != 0 {
11551157
let suppressed = matches!(command, Command::List(_))
1156-
|| matches!(command, Command::Pipeline(p) if p.negated);
1158+
|| matches!(command, Command::Pipeline(p) if p.negated)
1159+
|| result.errexit_suppressed;
11571160
if !suppressed {
11581161
break;
11591162
}

crates/bashkit/tests/set_e_and_or_tests.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,30 @@ echo "SHOULD NOT APPEAR"
191191
assert!(!result.stdout.contains("SHOULD NOT APPEAR"));
192192
}
193193

194+
/// set -e: && chain failure at end of for loop body should NOT exit (issue #873)
195+
#[tokio::test]
196+
async fn set_e_and_chain_at_end_of_for_body() {
197+
let mut bash = Bash::new();
198+
let result = bash
199+
.exec(
200+
r#"
201+
set -euo pipefail
202+
result=""
203+
for src in yes no; do
204+
[[ "${src}" == "yes" ]] && result="${src}"
205+
done
206+
echo "result: ${result}"
207+
"#,
208+
)
209+
.await
210+
.unwrap();
211+
assert!(
212+
result.stdout.contains("result: yes"),
213+
"should print 'result: yes' but got: {:?}",
214+
result.stdout
215+
);
216+
}
217+
194218
/// set -e should still exit on non-AND-OR failures
195219
#[tokio::test]
196220
async fn set_e_exits_on_plain_failure() {

0 commit comments

Comments
 (0)