Why does a failed cat return 1, but other fails return 2?

Let's address some of the parts bottom to top and get rid of non-important parts first:

Is this a problem in the BASH code?

No, cat is entirely separate binary application and unrelated to bash. In some shell configurations, as pointed out by Stephane Chazelas , cat can be a built in, but even then return status of an application is entirely separate from whether or not that application is related to shell or not.

Do we need to call Linus and Richard? If this is correct, please help me understand why.

No, it is not a problem, and Linus and Richard are completely unrelated here. Well, correction: unless they someday declare that exit() and errno absolutely MUST be related and for some odd reason we must follow all their technical decisions.


It is entirely OK that both applications return different exit status , because POSIX specifications have no explicit restrictions or assignments that say "This non-zero exit status shall mean this and that".

POSIX documentation of exit syscall states:

The value of status may be 0, EXIT_SUCCESS, EXIT_FAILURE, or any other value, though only the least significant 8 bits (that is, status & 0377) shall be available to a waiting parent process.

This means that only status 0 has assigned meaning, which is assigned to EXIT_SUCCESS as specified in stdlib.h specs. But this is POSIX spec, how does Linux spec compare ? Well, it's about the same: Linux exit(3) manual doesn't even specify what possible values may be.

Note also that it says "may be" and not "shall be", in the sense that it is not absolutely required for application to exit with specific value, even in case of errors. Your application can encounter an error or failure and still return 0 upon exit.

However, POSIX spec for each portable application does specify EXIT STATUS section, which is specific to each application. Again, there's no pattern besides 0 for success and non-zero for anything else. For instance, POSIX cat specs requires:

The following exit values shall be returned:

0    All input files were output successfully.

>0   An error occurred.

For grep we have:

The following exit values shall be returned:

 0    One or more lines were selected.
 1    No lines were selected.
>1    An error occurred.

Within Linux context, cat(1) doesn't explicitly state these status values, but GNU documentation does. grep(1) manual mentions using exit code of 2, but even then acknowledges that POSIX implementation only requires greater-than-zero condition for errors and urges "...for the sake of portability, to use logic that tests for this general condition instead of strict equality with 2."


It is worth mentioning that in some cases there is assumption that exit() status value equals to errno value. I couldn't find any documentation or reference that would suggest POSIX requires that so far. In fact, it's the opposite. Note, that POSIX exit spec and Linux exit(3) man page do not explicitly state that exit status has to somehow match errno. So the fact that return value of 2 in GNU grep matches ENOENT error value 2 is purely coincidental.

In fact, if we consider errno.h specific integer value isn't even required to be assigned and is implementation dependent. So there could very well be Unix-like implementation that treats ENOENT as integer 2. But again - that's entirely unrelated, because exit status and errno are separate things.

In conclusion:

The fact that cat returns different exit code than grep is appropriate and consistent with the spec for those applications. Exit code meaning is not fixed, and is dependent on each individual application (unless it's a POSIX application like cat or grep, in which case for the sake of portability they should follow).

To quote GNU OS documentation: "The most common convention is simply 0 for success and 1 for failure. Programs that perform comparison use a different convention: they use status 1 to indicate a mismatch, and status 2 to indicate an inability to compare. Your program should follow an existing convention if an existing convention makes sense for it."


The GNU coreutils documentation of cat:

An exit status of zero indicates success, and a nonzero value indicates failure.

...a non-zero exit status indicates a failure, nothing more and nothing less.

The man page of grep:

Normally the exit status is 0 if a line is selected, 1 if no lines were selected, and 2 if an error occurred. However, if the -q or --quiet or --silent is used and a line is selected, the exit status is 0 even if an error occurred.

And the man page of ls:

Exit status:
0   if OK,
1   if minor problems (e.g., cannot access subdirectory),
2   if serious trouble (e.g., cannot access command-line argument).

Your results are consistent with the documentation.


The exit status of a program must follow a few rules, and beyond these rules there are common conventions. None of these conventions are related to the low-level error that caused the program to exit. It's possible to write a program that exits with a certain error code it decided to exit because a file didn't exist, and a different error code if it decided to exit because it was denied permission to access a file, and a different error code if a directory component of a path turned out to be a non-directory, and so on, but that would be extremely unusual and difficult to arrange.

The exit status of a program is an integer value. On POSIX systems, the type of this value is int, which usually ranges from -231 to 231+1. However most of this range is unusable in practice for several reasons. First and foremost, for historical reasons, most interfaces that allow a program to observe the exit status of its children only return the lower 8 bits of the exit status, which is a value between 0 and 255. This includes the system functions wait and waitpid¹ as well as the exit status in the shell². So for almost all intents and purposes, an exit status is an 8-bit value.

The value 0 is treated as a success and every other value is treated as a failure. This is the case in the shell, where boolean operators, if and while constructs and anything else that involves a notion of true/false considers the exit status 0 to be true and any other status to be false. This is also the case with make, where a nonzero exit status causes the build to stop with an error message and an error status. You can quibble whether it's a convention (since the author of a program technically can return whatever status it wants, and “success” and “failure” aren't defined formally anyway), but in practice, a program that exits with the status 0 is considered to have succeeded and a program that exits with another status (1–255) is considered to have failed.

A further feature of the shell specifically that limits the range is that the exit status in the shell (observed via $?) encodes other information:

  • 126 indicates that the command name is an existing file which is not executable.
  • 127 indicates that the command name could not be found.
  • 128+N traditionally (and still today in most shells) indicates that the command exited with signal N. A few shells use a different range, always beyond 128.

Therefore, in practice, programs cannot usefully use an exit status beyond 125. This leaves the values 1–125 to express different errors.

There is a somewhat widespread, but far from universal convention that larger values are treated as “worse” failures. In particular, for search commands such as grep, 1 indicates “not found” and 2 or more indicates some error that prevented the search (e.g. file not found, as opposed to file found but does not contain the search string). Similarly, comparison commands such as cmp and diff exit with the status 0 to mean “identical files”, 1 to mean “different files” and 2 or more to mean “the comparison could not be completed due to an error”.

A few programs define different error codes for different errors, for example sendmail and some other mail-related programs (values defined in sysexits.h), rsync, curl, wget.

By far the most common convention for error codes is 0 for success, 1 for failure. The C and C++ programming languages define EXIT_FAILURE as an exit status code to use to report a failure if there's no particular reason to choose a specific value, and EXIT_FAILURE is 1 on most systems.

Errors such as “No such file or directory”, “Permission denied”, “Not a directory” and so on have a numerical encoding under the hood: they're errno values, returned by system functions to indicate what went wrong. Errno values are generally not useful as exit status from programs. They encode the minutiae of what went wrong, rather than what it means for the specific program. For example wget's exit status distinguishes “parse error in options” (usually no underlying system error), “local input/output error” (regardless of the underlying system error), “network failure” (which would largely share the same system errors as local I/O), etc. Knowing whether wget failed due to a network error or to a local file error is more useful than knowing whether it failed due to a broken pipe (writing to a pipe, or closed connection on a network socket?) or to a permission error (unable to read the configuration file, or network access denied by a local policy?).

It's somewhat uncommon for return statuses to follow errno values. It does happen, especially with Perl scripts, due to the way Perl's die function works. But it's a bad idea, not only because as I mentioned above the errno value is rarely the most useful part of the information, but mostly because there's no reason why errno values would be in the range 1–125. Fortunately I don't know of any system where errno values are outside the range 1–255, so at least exit(errno) (or Perl's die) won't exit with a value which is a multiple of 256, which as we saw above would convey success. But on Linux, for example, they do reach 126, and a program that exited with exit(errno) with errno == ERFKILL (“Operation not possible due to RF-kill”) would be undistinguishable from the shell from a program that died of SIGILL (illegal instruction).

¹ waitid grants access to the full int value via infop->si_status.
² Via $? or otherwise. For example, if exit256 is a program that exits with exit(256), the shell command if exit256; then echo "exited with 0"; fi prints “exited with 0”.