Say I have two shell scripts, a.sh and b.sh, where a.sh calls b.sh somewhere inside. Then, say that b.sh could exit with exit codes 0, 1, or 2.

What are some techniques that can be employed to ensure that the exit codes of a.sh are distinguishable from the exit codes of b.sh ? For example, if we decide that a.sh could exit with exit code 1, then that is ambiguous to the caller -- did a.sh fail, or did b.sh fail ? So that means that a.sh shouldn't use exit codes 1 or 2. But there are only a limited number of exit codes to pick from, and b.sh may theoretically add any number of additional exit codes as it evolves.

Are there techniques that can be employed to pass along some bit of extra data with an exit code to ensure that we, as script users, are able to differentiate between which program actually failed ? Or as script authors, are we meant to assume that our scripts will be treated as a black box, and that our users aren't meant to have any insight into the individual operations that the script we're running is performing ? In other words is the exit code of a shell script meant to only ever be interpreted as the result of the outermost script ?

  • How about printing a descriptive and unambiguous error message to stderr? Commented Feb 13 at 19:00
  • How about documenting the exit codes?
    – Kusalananda
    Commented Feb 13 at 19:59

2 Answers 2


Usually one prints an error message along with returning a falsy status:

# a.sh
if ! b.sh foo bar; then
    echo >&2 "a.sh: b.sh failed"
    exit 1
if something else; then
    echo >&2 "a.sh: some other error"
    exit 1

Then, if you want to use distinct numbers to separate the situations, you can do that within a.sh. And if you like also check the specific error that b.sh returned and use that to decide what to return from a.sh.

Of course, you could also build the error messages using some particular format, which would then let you use longer textual identifiers.

I'm not sure it would make sense to return the exit status returned by b.sh directly from a.sh, since the fact that the latter even calls the former should be an implementation detail. Unless the whole function of A is to be a wrapper for B, the user shouldn't need to care what A does internally. Furthermore, a tool that originally uses another tool can be later modified to do the same task itself if it's better that way. Or the other way around, a tool that does some task itself can be modified to utilize another existing tool, if that reduces code duplication. Passing the exit values directly would expose internal changes like this.

In any case, if the caller is to know what a particular exit status means (other than a generic "success/failure"), it has to be documented for that particular tool.

Then again, if you're building a larger library of tools, you could create a common system of exit values for various situations, and document that for the whole library.

  • "the fact the latter even calls the former should be an implementation detail" .. this needs to be highllighted more - I think this goes right to the heart of the problem. If tomorrow OP decided to inline whatever features of b.sh they use into a.sh and stop using b.sh itself, users would still expect the exit status for failures in those features to remain the same, so they should have been independent of b.sh in the first place.
    – muru
    Commented Feb 14 at 2:16

Kusalananda’s comment:

How about documenting the exit codes?

makes a good bottom line.  But, if you want/need b.sh’s exit codes to be directly visible to a.sh’s callers, consider adding an offset to b.sh’s exit codes.  So, for example, if b.sh exits with a 2, a.sh could exit with a 42 (i.e., $?+40).

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .