20

I recently came across this list of Exit Codes With Special Meanings from the Advanced Bash-Scripting Guide. They refer to these codes as being reserved and recommend that:

According to the above table, exit codes 1-2, 126-165, and 255 have special meanings, and should therefore be avoided for user-specified exit parameters.

A while ago, I wrote a script which used the following exit status codes:

  • 0 - success
  • 1 - incorrect hostname
  • 2 - invalid arguments specified
  • 3 - insufficient user privileges

When I wrote the script I wasn’t aware of any special exit codes so I simply started at 1 for the first error condition, and incremented the exit status for each successive error type.

I wrote the script with the intention that at a later stage it could be called by other scripts (which could check for the non-zero exit codes). I haven’t actually done that yet; so far I’ve only run the script from my interactive shell (Bash) and I was wondering what / if any problems could be caused by using my custom exit codes. How relevant/important is the recommendation from the Advanced Bash-Scripting Guide?

I couldn’t find any corroborating advice in the Bash documentation; its section on Exit Status simply lists the exit codes used by Bash but doesn’t state that any of these are reserved or warn against using them for your own scripts/programs.

4
  • 7
    I, and others, consider the ABSG to be of generally low quality. In my opinion the author of the page you linked is making an unsupported assertion that the listed exit codes are reserved based, apparently, on the fact that the shell itself uses them for specific meanings. There have been attempts to create standards for scripts, none of which have had any success. The important thing is to document the error codes you choose so that consumers of your scripts (e.g. other scripts) know what to do based on them. Commented Nov 10, 2015 at 18:30
  • @DennisWilliamson If you post your comment as answer, I’d be happy to upvote it; I’ve already voted all the other answers as I found each of them to be useful. While your answer is similar in content to David King’s (and to a lesser extent Zwol’s), you state explicitly that there’s no evidence for the assertion in the ABSG quote. Commented Nov 11, 2015 at 0:02
  • 1
    Thanks for the offer, but I believe my comment should stay as such. Commented Nov 11, 2015 at 0:41
  • I've since discovered that the POSIX specification includes similar advice so I've added that information to my own answer (containing the results of my research since asking this question). Commented Nov 12, 2015 at 16:31

6 Answers 6

13

No exit code has a special meaning, but the value in $? may have a special meaning.

The way Bourne Shell and ksh93 handled and forwarded exit codes and error situations to the shell variable $? is the problem. In contrary to what you list, only the following values for $? have a special meaning:

  • 126 Could not execute the binary even though it exists
  • 127 The specified binary does not exist
  • 128 exit status was == 0 but some unspecified problem exists

In addition, there is an unspecified shell and platform-specific range of $? codes > 128 that is reserved for a program that was interrupted by a signal:

  • Bourne Shell bash and ksh88 use 128 + signal number
  • ksh93 uses 256 + signal number.

Other values do not give problems as they may be distinguished from the shell-special $? values.

In particular, the values 1 and 2 are not used for special conditions but are just exit codes used by builtin commands that could act the same when they are no builtins. So is seems that the pointer to the bash scripting guide you provided is not a good manual as it just lists codes used by bash without commenting whether a specific code is a special value that should be avoided for own scripts.

Newer versions of the Bourne Shell use waitid() instead of waitpid() to wait for the program to exit and waitid() (introduced 1989 for SVr4) uses a better syscall interface (similar to what UNOS used in 1980 already).

As newer Bourne Shell versions encode the exit reason in a separate variable ${.sh.code} / ${.sh.codename} than the exit code that is in ${.sh.status}/ ${.sh.termsig}, see http://schillix.sourceforge.net/man/man1/bosh.1.html, the exit code is not overloaded with special states, and, as a result from using `waitid(), the Bourne Shell now supports returning all 32 bits of the exit code – not just the low 8 bits.

BTW: be careful not to exit(256) or similar from a C-program or shell script, as this results in $? being interpreted as 0 in a classic shell.

16
  • 3
    BTW: I made a bug report against FreeBSD and the Linux kernel for this waitid() bug around late May. The FreeBSD people fixed the problem within 20 hours, the Linux people are not interested in fixing their bug. ... and the Cygwin people says that they are bug by bug Linux compatible ;-)
    – schily
    Commented Nov 10, 2015 at 15:39
  • 2
    This behavior is required by the Single Unix Specification. There's a 32-bit value, yes, but that value contains an 8-bit bitfield containing the low 8 bits of the value from _exit. Please link the FreeBSD bug report you are referring to, maybe I'm misunderstanding the issue you describe.
    – Random832
    Commented Nov 10, 2015 at 17:58
  • 2
    The OP tagged the question with bash and mentioned Bash in the text of the question. Bash is a Bourne-derived shell. It does not support ${.sh.} variables. It is true, however, that you say "Bourne" and not "Bourne-derived" (although you do include ksh93). Commented Nov 10, 2015 at 18:21
  • 3
    This answer appears to be very specific to your particular variant of some SVR4-derived Unix. Please be clearer about what is portable and what isn't, keeping in mind that there is no such thing as "the" Bourne shell, unless you mean the one that was in V7.
    – zwol
    Commented Nov 10, 2015 at 19:38
  • 4
    On the contrary, I believe it is you who are understating the range of variation here, especially historical variation. You make it sound like /bin/sh can be relied on to behave consistently wrt these special exit codes cross-platform, which is not true. (I do not care whether any particular system's /bin/sh can be said to be a "real Bourne shell". Much more important to know that none of this is in POSIX, and that most of the things you cite as "real Unix systems" don't provide a POSIX-compliant /bin/sh anyway.)
    – zwol
    Commented Nov 10, 2015 at 21:12
12

There have been several attempts to standardize the meanings of process exit codes. In addition to the one you mention, I know of:

  • the BSDs have sysexits.h which defines meanings for values from 64 on up.

  • GNU grep documents that exit code 0 means at least one match was found, 1 means no matches were found, and 2 means an I/O error occurred; this convention is obviously also useful for other programs for which the distinction between "nothing went wrong but I didn't find anything" and "an I/O error occurred" is meaningful.

  • Many implementations of the C library function system use exit code 127 to indicate the program doesn't exist or failed to start.

  • On Windows, NTSTATUS codes (which are inconveniently scattered all over the 32-bit number space) may be used as exit codes, particularly the ones that indicate a process was terminated due to catastrophic misbehavior (e.g. STATUS_STACK_OVERFLOW).

You can't count on any given program obeying any particular one of these conventions. The only reliable rule is that exit code 0 is success and anything else is some sort of failure. (Note that C89's EXIT_SUCCESS is not guaranteed to have the value zero; however, exit(0) is required to behave identically to exit(EXIT_SUCCESS) even if the values are not the same.)

1
  • Thanks. It was difficult to choose one answer over the others but I'm accepting this one since it answered my question while also providing a wide flavour of the the different exit codes in use (with relevant links): it deserves more than the 3 upvotes it currently has. Commented Nov 11, 2015 at 12:25
9

For shell scripting, I sometimes in-source the shell equivalent of sysexist.h with shell-reserved exit codes (prefixed with S_EX_), which I've named exit.sh

It's basically:

EX_OK=0 # successful termination 
EX__BASE=64     # base value for error messages 
EX_USAGE=64     # command line usage error 
EX_DATAERR=65   # data format error 
EX_NOINPUT=66   # cannot open input 
EX_NOUSER=67    # addressee unknown 
EX_NOHOST=68    # host name unknown 
EX_UNAVAILABLE=69       # service unavailable 
EX_SOFTWARE=70  # internal software error 
EX_OSERR=71     # system error (e.g., can't fork) 
EX_OSFILE=72    # critical OS file missing 
EX_CANTCREAT=73 # can't create (user) output file 
EX_IOERR=74     # input/output error 
EX_TEMPFAIL=75  # temp failure; user is invited to retry 
EX_PROTOCOL=76  # remote error in protocol 
EX_NOPERM=77    # permission denied 
EX_CONFIG=78    # configuration error 
EX__MAX=78      # maximum listed value 

#System errors
S_EX_ANY=1      #Catchall for general errors
S_EX_SH=2       #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute         Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit       exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)                                                                                        
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)                               
#255*   Exit status out of range        exit -1 exit takes only integer args in the range 0 - 255              
S_EX_HUP=129                                                                                                   
S_EX_INT=130   
#...

And can be generated with:

#!/bin/sh
src=/usr/include/sysexits.h
echo "# Generated from \"$src\"" 
echo "# Please inspect the source file for more detailed descriptions"
echo
< "$src" sed -rn 's/^#define  *(\w+)\s*(\d*)/\1=\2/p'| sed 's:/\*:#:; s:\*/::'
cat<<'EOF'

#System errors
S_EX_ANY=1  #Catchall for general errors
S_EX_SH=2   #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute     Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit   exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)
#255*   Exit status out of range    exit -1 exit takes only integer args in the range 0 - 255
EOF
$(which kill) -l |tr ' ' '\n'| awk '{ printf "S_EX_%s=%s\n", $0, 128+NR; }'

I don't use it much, though, but what I do use is a shell function that inverses error codes to their string formats. I've named it exit2str. Assuming you've named the above exit.sh generator exit.sh.sh, the code for exit2str can be generated with (exit2str.sh.sh) :

#!/bin/sh
echo '
exit2str(){
  case "$1" in'
./exit.sh.sh | sed -nEe's|^(S_)?EX_(([^_=]+_?)+)=([0-9]+).*|\4) echo "\1\2";;|p'
echo "
  esac
}"

I use this in the PS1 of my interactive shell so that after each command I run, I can see its exit status and its string form (if it does have a known string form):

[15:58] pjump@laptop:~ 
(0=OK)$ 
[15:59] pjump@laptop:~ 
(0=OK)$ fdsaf
fdsaf: command not found
[15:59] pjump@laptop:~ 
(127=S_NOENT)$ sleep
sleep: missing operand
Try 'sleep --help' for more information.
[15:59] pjump@laptop:~ 
(1=S_ANY)$ sleep 100
^C
[15:59] pjump@laptop:~ 
(130=S_INT)$ sleep 100
^Z
[1]+  Stopped                 sleep 100
[15:59] pjump@laptop:~ 
(148=S_TSTP)$

To get these, you need an insourcable for the exit2str function:

$ ./exit2str.sh.sh > exit2str.sh #Place this somewhere in your PATH

and then use it in your ~/.bashrc to save and translate the exit code on each command prompt and display it your prompt (PS1):

    # ...
    . exit2str.sh
PROMPT_COMMAND='lastStatus=$(st="$?"; echo -n "$st"; str=$(exit2str "$st") && echo "=$str"); # ...'
    PS1="$PS1"'\n($lastStatus)\$'
    # ...                                                                                   

It's quite handy for observing how some programs follow the exit code conventions and some don't, for learning about exit code conventions, or just for being able to see what's going on more readily. Having been using it for some time, I can say that many system-oriented shell scripts do follow the conventions. EX_USAGE is particularly quite common, although other codes, not much. I try to follow the conventions from time to time, although there's always $S_EX_ANY (1) for lazy people (I am one).

2
  • I do wonder if there's anything like a mapping between an errno code and an exit code to use if the error reported with that errno code results in an error exit. I might need to come up with some reasonable mapping. Commented Nov 11, 2015 at 15:10
  • 1
    Wow! I wasn't expecting such an elaborate answer. I'll definitely try that out as a good way to see how different commands behave. Thanks. Commented Nov 11, 2015 at 15:19
5

The best reference I could find was this: http://tldp.org/LDP/abs/html/exitcodes.html

According to this:

1 is a general catchall for errors, and I've always seen it used for user defined errors.

2 is for misuse of shell built ins, such as a syntax error

To answer your question directly your script will be fine using the reserved error codes, it will function as expected assuming you handle the error based on the error code = 1/2/3.

However, it would possibly be confusing if you encounter anyone who knows and uses the reserved error codes, which seems quite rare.

Another option available to you is to echo the error if there is one and then exit, assuming your script follows the Linux convention of "no news is good news" and echo's nothing on success.

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi
4

As long as you document your exit codes so that you remember them a year from now when you have to come back and tweak the script you'll be fine. The idea of "reserved exit codes" doesn't really apply anymore other than to say it's customary to use 0 as a success code and anything else as a failure code.

4

Based on the answers I’ve received (it was difficult to pick one over the others), it isn’t harmful to indicate certain types of errors by using an exit code that Bash also uses. Bash (or any other Unix shell) won’t do anything special (such as running exception handlers) if a user script exits with one of these error codes.

It seems that the author of the Advanced Bash-Scripting Guide agrees with the BSD attempts to standardise exit codes (sysexits.h) and is simply recommending that when users write shell scripts, they don’t specify exit codes that conflict with pre-defined exit codes already in use, i.e., they restrict their custom exit codes to the 50 available status codes in the range 64-113.

I appreciate the idea (and the rationale) but I’d have preferred if the author was more explicit that it’s not harmful to ignore the advice – aside from cases where the consumer of a script is checking for errors such as the cited example of 127 (command not found).

Relevant POSIX specifications

I researched what POSIX has to say about exit codes and the POSIX specification seems to concur with the author of the Advanced Bash-Scripting Guide. I’ve quoted the relevant POSIX specifications (emphasis mine):

Exit Status for Commands

Each command has an exit status that can influence the behavior of other shell commands. The exit status of commands that are not utilities is documented in this section. The exit status of the standard utilities is documented in their respective sections.

If a command is not found, the exit status shall be 127. If the command name is found, but it is not an executable utility, the exit status shall be 126. Applications that invoke utilities without using the shell should use these exit status values to report similar errors.

If a command fails during word expansion or redirection, its exit status shall be greater than zero.

Internally, for purposes of deciding whether a command exits with a non-zero exit status, the shell shall recognize the entire status value retrieved for the command by the equivalent of the wait() function WEXITSTATUS macro (as defined in the System Interfaces volume of POSIX.1-2008). When reporting the exit status with the special parameter '?', the shell shall report the full eight bits of exit status available. The exit status of a command that terminated because it received a signal shall be reported as greater than 128.

The exit utility

As explained in other sections, certain exit status values have been reserved for special uses and should be used by applications only for those purposes:

  • 126 – A file to be executed was found, but it was not an executable utility.
  • 127 – A utility to be executed was not found.
  • >128 – A command was interrupted by a signal.

Further information

For what it’s worth, I was able to verify all but one of the list of Exit Codes With Special Meanings. This table of exit codes is useful as it provides more details – and examples of how to generate the error codes documented in the Bash reference.

Attempt to generate exit status of 128

Using Bash versions 3.2.25 and 4.2.46, I tried to throw a 128 Invalid argument to exit error but each time I received a 255 (Exit status out of range). E.g., if exit 3.14159 is executed as part of a shell script or in an interactive child shell, the shell exits with a code of 255:

$ exit 3.14159
exit
bash: exit: 3.14159: numeric argument required

For even more fun, I also tried running a simple C program but in this case, it seems that the exit(3) function simply converted the float to an int (3 in this case) before exiting:

#include <stdlib.h>
main()
{
    exit(3.14159);
}

You must log in to answer this question.

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