Home » Default exit code when process is terminated?

Default exit code when process is terminated?


Processes can call the _exit() system call (on Linux, see also exit_group()) with an integer argument to report an exit code to their parent. Though it’s an integer, only the 8 least significant bits are available to the parent (exception to that is when using waitid() or handler on SIGCHLD in the parent to retrieve that code, though not on Linux).

The parent will typically do a wait() or waitpid() to get the status of their child as an integer (though waitid() with somewhat different semantics can be used as well).

On Linux and most Unices, if the process terminated normally, bits 8 to 15 of that status number will contain the exit code as passed to exit(). If not, then the 7 least significant bits (0 to 6) will contain the signal number and bit 7 will be set if a core was dumped.

perl‘s $? for instance contains that number as set by waitpid():

$ perl -e 'system q(kill $$); printf "%04xn", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04xn", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04xn", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Bourne-like shells also make the exit status of the last run command in their own $? variable. However, it does not contain directly the number returned by waitpid(), but a transformation on it, and it’s different between shells.

What’s common between all shells is that $? contains the lowest 8 bits of the exit code (the number passed to exit()) if the process terminated normally.

Where it differs is when the process is terminated by a signal. In all cases, and that’s required by POSIX, the number will be greater than 128. POSIX doesn’t specify what the value may be. In practice though, in all Bourne-like shells that I know, the lowest 7 bits of $? will contain the signal number. But, where n is the signal number,

  • in ash, zsh, pdksh, bash, the Bourne shell, $? is 128 + n. What that means is that in those shells, if you get a $? of 129, you don’t know whether it’s because the process exited with exit(129) or whether it was killed by the signal 1 (HUP on most systems). But the rationale is that shells, when they do exit themselves, by default return the exit status of the last exited command. By making sure $? is never greater than 255, that allows to have a consistent exit status:

    $ bash -c 'sh -c "kill $$"; printf "%xn" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill $$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill $$"; exit'; printf '%xn' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill $$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
  • ksh93, $? is 256 + n. That means that from a value of $? you can differentiate between a killed and non-killed process. Newer versions of ksh, upon exit, if $? was greater than 255, kills itself with the same signal in order to be able to report the same exit status to its parent. While that sounds like a good idea, that means that ksh will generate an extra core dump (potentially overwriting the other one) if the process was killed by a core generating signal:

    $ ksh -c 'sh -c "kill $$"; printf "%xn" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL $$"; exit'; printf '%xn' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.

    Where you could even say there’s a bug is that ksh93 kills itself even if $? comes from a return 257 done by a function:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
  • yash. yash offers a compromise. It returns 256 + 128 + n. That means we can also differentiate between a killed process and one that terminated properly. And upon exiting, it will report 128 + n without having to suicide itself and the side effects it can have.

    $ yash -c 'sh -c "kill $$"; printf "%xn" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill $$"; exit'; printf '%xn' "$?"
    8f  # that's from a exit(143), yash was not killed

To get the signal from the value of $?, the portable way is to use kill -l:

$ /bin/kill 0
$ kill -l "$?"

(for portability, you should never use signal numbers, only signal names)

On the non-Bourne fronts:

  • csh/tcsh and fish same as the Bourne shell except that the status is in $status instead of $? (note that zsh also sets $status for compatibility with csh (in addition to $?)).
  • rc: the exit status is in $status as well, but when killed by a signal, that variable contains the name of the signal (like sigterm or sigill+core if a core was generated) instead of a number, which is yet another proof of the good design of that shell.
  • es. the exit status is not a variable. If you care for it, you run the command as:

    status = <={cmd}

    which will return a number or sigterm or sigsegv+core like in rc.

Maybe for completeness, we should mention zsh‘s $pipestatus and bash‘s $PIPESTATUS arrays that contain the exit status of the components of the last pipeline.

And also for completeness, when it comes to shell functions and sourced files, by default functions return with the exit status of the last command run, but can also set a return status explicitly with the return builtin. And we see some differences here:

  • bash and mksh (since R41, a regression^Wchange apparently introduced intentionally) will truncate the number (positive or negative) to 8 bits. So for instance return 1234 will set $? to 210, return -- -1 will set $? to 255.
  • zsh and pdksh (and derivatives other than mksh) allow any signed 32 bit decimal integer (-231 to 231-1) (and truncate the number to 32bits).
  • ash and yash allow any positive integer from 0 to 231-1 and return an error for any number out of that.
  • ksh93 for return 0 to return 320 set $? as is, but for anything else, truncate to 8 bits. Beware as already mentioned that returning a number between 256 and 320 could cause ksh to kill itself upon exit.
  • rc and es allow returning anything even lists.

Also note that some shells also use special values of $?/$status to report some error conditions that are not the exit status of a process, like 127 or 126 for command not found or not executable (or syntax error in a sourced file)…

When a process exits, it returns an integer value to the operating system. On most unix variants, this value is taken modulo 256: everything but the low-order bits is ignored. The status of a child process is returned to its parent through a 16-bit integer in which

  • bits 0–6 (the 7 low-order bits) are the signal number that was used to kill the process, or 0 if the process exited normally;
  • bit 7 is set if the process was killed by a signal and dumped core;
  • bits 8–15 are the process’s exit code if the process exited normally, or 0 if the process was killed by a signal.

The status is returned by the wait system call or one of its siblings. POSIX does not specify the exact encoding of the exit status and signal number; it only provides

  • a way to tell whether the exit status corresponds to a signal or to a normal exit;
  • a way to access the exit code, if the process exited normally;
  • a way to access the signal number, if the process was killed by a signal.

Strictly speaking, there’s no exit code when a process is killed by a signal: what there is instead is an exit status.

In a shell script, the exit status of a command is reported via the special variable $?. This variable encodes the exit status in an ambiguous way:

  • If the process exited normally then $? is its exit status.
  • If the process was killed by a signal then $? is 128 plus the signal number on most systems. POSIX only mandates that $? is greater than 128 in this case; ksh93 adds 256 instead of 128. I’ve never seen a unix variant that did anything other than add a constant to the signal number.

Thus in a shell script you cannot tell conclusively whether a command was killed by a signal or exited with a status code greater than 128, except with ksh93. It is very rare for programs to exit with status codes greater than 128, in part because programmers avoid it due to the $? ambiguity.

SIGINT is signal 2 on most unix variants, thus $? is 128+2=130 for a process that was killed by SIGINT. You’ll see 129 for SIGHUP, 137 for SIGKILL, etc.

That depends on your shell. From the bash(1) man page, SHELL GRAMMAR section, Simple Commands subsection:

The return value of a simple command is […] 128+n if the command is terminated by signal n.

Since SIGINT on your system is signal number 2, the return value is 130 when it is run under Bash.

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...