Skip to content

Commit db1300d

Browse files
committed
feat(interpreter): exec with command argument — execute and don't return
exec cmd args... now resolves and executes the command, then exits the current script/shell with its exit code. Subsequent statements are not executed. FD-only exec (no command) continues to work as before. Closes #794 Closes #797
1 parent 2b9d9c3 commit db1300d

File tree

2 files changed

+117
-5
lines changed

2 files changed

+117
-5
lines changed

crates/bashkit/src/interpreter/mod.rs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3596,12 +3596,41 @@ impl Interpreter {
35963596
redirects: &[Redirect],
35973597
) -> Result<ExecResult> {
35983598
if !args.is_empty() {
3599-
let cmd = args.join(" ");
3600-
self.last_exit_code = 127;
3599+
// exec cmd args... — execute command and exit with its exit code.
3600+
// In a real shell this replaces the process; in VFS we run + exit.
3601+
// Build the command as a script string and execute it.
3602+
// Single-quote each arg to prevent re-expansion.
3603+
let mut script_str = String::new();
3604+
for (i, arg) in args.iter().enumerate() {
3605+
if i > 0 {
3606+
script_str.push(' ');
3607+
}
3608+
script_str.push('\'');
3609+
script_str.push_str(&arg.replace('\'', "'\\''"));
3610+
script_str.push('\'');
3611+
}
3612+
3613+
let parser = Parser::with_limits(
3614+
&script_str,
3615+
self.limits.max_ast_depth,
3616+
self.limits.max_parser_operations,
3617+
);
3618+
let script = match parser.parse() {
3619+
Ok(s) => s,
3620+
Err(_) => {
3621+
return Ok(ExecResult::err(
3622+
format!("-bash: exec: {}: command not found\n", args[0]),
3623+
127,
3624+
));
3625+
}
3626+
};
3627+
3628+
let result = self.execute(&script).await?;
3629+
3630+
// Signal exit so subsequent statements don't execute
36013631
return Ok(ExecResult {
3602-
stderr: format!("-bash: exec: {}: command not found\n", cmd),
3603-
exit_code: 127,
3604-
..ExecResult::default()
3632+
control_flow: ControlFlow::Return(result.exit_code),
3633+
..result
36053634
});
36063635
}
36073636
for redirect in redirects {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
### exec_replaces_execution
2+
# exec stops subsequent statements
3+
cat > /tmp/greeter.sh <<'SCRIPT'
4+
#!/usr/bin/env bash
5+
echo "hello from greeter"
6+
SCRIPT
7+
chmod +x /tmp/greeter.sh
8+
9+
cat > /tmp/dispatcher.sh <<'SCRIPT'
10+
#!/usr/bin/env bash
11+
echo "before exec"
12+
exec /tmp/greeter.sh
13+
echo "SHOULD NOT APPEAR"
14+
SCRIPT
15+
chmod +x /tmp/dispatcher.sh
16+
17+
/tmp/dispatcher.sh
18+
### expect
19+
before exec
20+
hello from greeter
21+
### end
22+
23+
### exec_propagates_exit_code
24+
# exec propagates exit code from executed command
25+
cat > /tmp/exit-42.sh <<'SCRIPT'
26+
#!/usr/bin/env bash
27+
exit 42
28+
SCRIPT
29+
chmod +x /tmp/exit-42.sh
30+
31+
cat > /tmp/exec-it.sh <<'SCRIPT'
32+
#!/usr/bin/env bash
33+
exec /tmp/exit-42.sh
34+
SCRIPT
35+
chmod +x /tmp/exec-it.sh
36+
37+
/tmp/exec-it.sh
38+
echo $?
39+
### expect
40+
42
41+
### end
42+
43+
### exec_with_builtin
44+
# exec with builtin command
45+
cat > /tmp/exec-echo.sh <<'SCRIPT'
46+
#!/usr/bin/env bash
47+
exec echo "via exec"
48+
echo "SHOULD NOT APPEAR"
49+
SCRIPT
50+
chmod +x /tmp/exec-echo.sh
51+
52+
/tmp/exec-echo.sh
53+
### expect
54+
via exec
55+
### end
56+
57+
### exec_passes_arguments
58+
# exec passes arguments to command
59+
cat > /tmp/echo-args.sh <<'SCRIPT'
60+
#!/usr/bin/env bash
61+
echo "args: $*"
62+
SCRIPT
63+
chmod +x /tmp/echo-args.sh
64+
65+
cat > /tmp/exec-args.sh <<'SCRIPT'
66+
#!/usr/bin/env bash
67+
exec /tmp/echo-args.sh one two three
68+
SCRIPT
69+
chmod +x /tmp/exec-args.sh
70+
71+
/tmp/exec-args.sh
72+
### expect
73+
args: one two three
74+
### end
75+
76+
### exec_fd_redirections_still_work
77+
# exec without command still does FD redirections
78+
echo "file content" > /tmp/exec-test-file.txt
79+
exec 3< /tmp/exec-test-file.txt
80+
echo "after exec redirect"
81+
### expect
82+
after exec redirect
83+
### end

0 commit comments

Comments
 (0)