Control flow

exit#

Terminate the program with a status value.

exit evaluates EXPR, runs end-of-program cleanup (END blocks and object destructors), and then exits the process with that value as its status code. With no argument it exits 0 — success.

Synopsis#

exit EXPR
exit

What you get back#

Nothing. exit does not return to its caller. It is a statement in the control-flow sense even though the grammar lets you write it inside an expression. Code after exit in the same block is unreachable:

exit 1;
print "never\n";            # dead code

Status values and the 8-bit truncation#

The operating system stores an exit status in 8 bits. Whatever integer you pass to exit is taken modulo 256 by the kernel before the parent process sees it. exit 256 looks like exit 0 to the shell. exit -1 becomes 255. Pass only values in 0..255 and you avoid the surprise.

The only universally portable values are 0 for success and 1 for a generic error. Other numbers have conventions — the sysexits.h range 64..78 on Unix, specific codes expected by sendmail filters, and so on — but those are agreements between your program and a specific caller, not guarantees of the language. Exiting 69 (EX_UNAVAILABLE) tells a sendmail incoming-mail filter to defer the message; the same 69 from a script run by cron means nothing in particular.

exit 0;                     # success
exit 1;                     # generic failure
exit 2;                     # conventional "misuse" (grep, diff)
exit 77;                    # conventional "skip" (Automake test)

Cleanup: END blocks and destructors#

exit is not an immediate syscall. Before the process goes away, Perl:

  1. Runs every END block in reverse order of compilation (LIFO).

  2. Calls DESTROY on any object whose refcount drops to zero as global destruction tears down pads, the symbol table, and package globals.

Both kinds of cleanup can observe and change $?. The value you passed to exit is placed in $? on entry to the END/DESTROY phase; whatever $? holds when the last cleanup returns is the status the process exits with.

END { $? = 0 if $? == 2; }  # rewrite "misuse" to success

exit 2;                     # process actually exits 0

This is occasionally useful and regularly astonishing. If a module you did not write installs an END block that touches $?, your carefully chosen status code can arrive at the parent shell altered.

When you need a hard, unconditional exit that bypasses END and DESTROY entirely, call POSIX::_exit:

use POSIX ();
POSIX::_exit(3);            # no END, no DESTROY, status = 3

Reach for _exit in child processes after fork where the parent owns the cleanup, and in signal handlers where running arbitrary Perl code is unsafe.

exit is not die#

exit is the wrong tool for reporting an error from a library or a subroutine. A caller higher up the stack may want to catch the failure, log it, and continue — an eval block or a Try::Tiny wrapper cannot recover from exit. The process is gone.

sub load_config {
    my ($path) = @_;
    open my $fh, "<", $path
        or die "open $path: $!";   # correct — trappable
    ...
}

# NOT:
# open my $fh, "<", $path or exit 1;

Use die for error conditions. Reserve exit for the top level of the program where you have decided, as the application, that the run is over.

exit inside eval#

exit is not caught by eval. Unlike die, which unwinds to the nearest eval and sets $@, exit walks straight past every eval frame on the stack, runs END/DESTROY, and terminates.

eval { exit 5 };            # process exits 5; the eval block
print "after eval\n";       # never runs

If you are writing a plugin host or a test harness that must survive misbehaved user code calling exit, there is no language-level catch. The conventional workaround is to fork, let the child call exit, and inspect the child’s status in the parent:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    run_user_code();        # may call exit
    exit 0;
}
waitpid $pid, 0;
my $status = $? >> 8;

Examples#

Early return from the program after a prompt:

my $ans = <STDIN>;
exit 0 if $ans =~ /^[Xx]/;

Exit with a status derived from a child process — extract the high byte of $? so the shell sees the same code the child produced:

system @cmd;
exit $? >> 8 if $?;

Rewrite a status in an END block — whatever $? holds at the end of cleanup is the exit status delivered to the parent:

END {
    $? = 0 if $? == 2 && $ENV{IGNORE_MISUSE};
}

Bypass cleanup in a forked child so the parent’s destructors do not run twice:

use POSIX ();
my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec @cmd;
    POSIX::_exit(127);      # exec failed; do NOT run parent's END
}

Edge cases#

  • exit with no argument is exit 0. Many scripts rely on falling off the end of the file to get an implicit 0; writing exit; makes the intent explicit.

  • Non-integer argument: EXPR is evaluated in scalar context and converted to an integer. exit "3x" exits 3; exit "hello" exits 0 with a warning under use warnings.

  • Negative values are taken modulo 256 by the kernel. exit -1 appears to the shell as 255. Pass only 0..255 to avoid the coercion.

  • Values above 255 lose their high bits. exit 256 looks like success; exit 300 looks like exit 44. If you need to convey a larger value, write it to a file or to STDERR and exit with a small status code.

  • END blocks can cancel your status but not the exit itself. An END block cannot stop the process from terminating — it can only change $?, and therefore the status seen by the parent.

  • Global destruction order is not the reverse of construction. During the final sweep Perl walks package globals in an implementation-defined order. Do not rely on one DESTROY running before another; if ordering matters, tear the objects down explicitly before calling exit.

  • Inside a BEGIN block, exit terminates the compilation phase and exits the process. END blocks already registered run; later BEGIN/END/code in the file does not.

  • Threads: in a program with multiple Perl interpreter threads, exit terminates the whole process, not just the calling thread. Use threads->exit for per-thread exit.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • die — raise a trappable exception; the right choice for reporting errors from a subroutine

  • warn — emit a diagnostic without terminating; pairs with die the way print pairs with exit

  • END blocks — compile-time-registered cleanup code run in LIFO order on any exit, including exit EXPR

  • DESTROY methods — per-object cleanup called during global destruction after END blocks have run

  • $? — child-process / final exit status; visible to and mutable by END blocks and destructors

  • POSIX::_exit — immediate termination that bypasses END and DESTROY; use in forked children and signal handlers