Skip to content

Commit

Permalink
interp: handle the last exit status code better
Browse files Browse the repository at this point in the history
In some edge cases, such as commands with no arguments or background
commands, we didn't properly reset it to 0.

Doing that breaks expansions like "$?"; to properly handle all cases, we
need to keep track of the current and last status codes separately, at
least while we execute a statement.

As a bonus, we can deduplicate a few places where we set r.exit = 0.

Fixes #506.
  • Loading branch information
mvdan committed Feb 15, 2020
1 parent dedd598 commit b71ab34
Show file tree
Hide file tree
Showing 4 changed files with 10 additions and 7 deletions.
2 changes: 1 addition & 1 deletion interp/builtin.go
Expand Up @@ -54,7 +54,7 @@ func (r *Runner) builtinCode(ctx context.Context, pos syntax.Pos, name string, a
r.exitShell = true
switch len(args) {
case 0:
return r.exit
return r.lastExit
case 1:
n, err := strconv.Atoi(args[0])
if err != nil {
Expand Down
11 changes: 6 additions & 5 deletions interp/interp.go
Expand Up @@ -435,7 +435,8 @@ type Runner struct {
noErrExit bool

err error // current shell exit code or fatal error
exit int // current (last) exit status code
exit int // current exit status code
lastExit int // last exit status code
exitShell bool // whether the shell needs to exit

bgShells errgroup.Group
Expand Down Expand Up @@ -752,6 +753,7 @@ func (r *Runner) stmt(ctx context.Context, st *syntax.Stmt) {
if r.stop(ctx) {
return
}
r.exit = 0
if st.Background {
r2 := r.Subshell()
st2 := *st
Expand All @@ -762,6 +764,7 @@ func (r *Runner) stmt(ctx context.Context, st *syntax.Stmt) {
} else {
r.stmtSync(ctx, st)
}
r.lastExit = r.exit
}

func (r *Runner) stmtSync(ctx context.Context, st *syntax.Stmt) {
Expand All @@ -777,9 +780,7 @@ func (r *Runner) stmtSync(ctx context.Context, st *syntax.Stmt) {
defer cls.Close()
}
}
if st.Cmd == nil {
r.exit = 0
} else {
if st.Cmd != nil {
r.cmd(ctx, st.Cmd)
}
if st.Negated {
Expand Down Expand Up @@ -825,6 +826,7 @@ func (r *Runner) Subshell() *Runner {
opts: r.opts,
usedNew: r.usedNew,
exit: r.exit,
lastExit: r.lastExit,

origStdout: r.origStdout, // used for process substitutions
}
Expand Down Expand Up @@ -1023,7 +1025,6 @@ func (r *Runner) cmd(ctx context.Context, cm syntax.Command) {
}
}
case *syntax.TestClause:
r.exit = 0
if r.bashTest(ctx, x.X, false) == "" && r.exit == 0 {
// to preserve exit status code 2 for regex errors, etc
r.exit = 1
Expand Down
2 changes: 2 additions & 0 deletions interp/interp_test.go
Expand Up @@ -232,6 +232,8 @@ var runTests = []runTest{
"for i in 1; do break a; done",
"usage: break [n]\nexit status 2 #JUSTERR",
},
{"false; a=b", ""},
{"false; false &", ""},

// we don't need to follow bash error strings
{"exit a", "invalid exit status code: \"a\"\nexit status 2 #JUSTERR"},
Expand Down
2 changes: 1 addition & 1 deletion interp/vars.go
Expand Up @@ -60,7 +60,7 @@ func (r *Runner) lookupVar(name string) expand.Variable {
case "@", "*":
vr.Kind, vr.List = expand.Indexed, r.Params
case "?":
vr.Kind, vr.Str = expand.String, strconv.Itoa(r.exit)
vr.Kind, vr.Str = expand.String, strconv.Itoa(r.lastExit)
case "$":
vr.Kind, vr.Str = expand.String, strconv.Itoa(os.Getpid())
case "PPID":
Expand Down

0 comments on commit b71ab34

Please sign in to comment.