Processes

exec#

Abandon this program and run another in the same process.

exec replaces the current process image with another program. It does not return on success — execution continues in the new program, with the same process id, open filehandles subject to the close-on-exec flag, and whatever environment was in effect. It returns false (and sets $!) only when the target could not be launched and it was executed directly rather than through the shell. Reach for system when you want the child to run and control to come back.

Synopsis#

exec LIST
exec PROGRAM LIST
exec { PROGRAM } LIST

What you get back#

Nothing, on success — the process has been replaced and this Perl interpreter is gone. On failure exec returns false and leaves the reason in $!. Failure is only detectable for the direct-exec path; when the argument is handed to the shell, a missing program is reported by the shell itself and exec still counts as having “succeeded” in launching /bin/sh.

exec '/usr/bin/vi', $file
    or die "couldn't exec vi: $!";

Because exec normally does not return, Perl emits a warning under use warnings if it appears in void context followed by another statement that is not die, warn, or exit. Either handle the failure inline with or die, or wrap the call in a block to silence the warning:

exec('foo') or die "couldn't exec foo: $!";
{ exec('foo') }; die "couldn't exec foo: $!";

Shell or no shell — the single-string rule#

exec picks between two launch mechanisms based on the shape of LIST:

  • Two or more argumentsexec calls execvp(3) directly with the list. No shell is involved, no metacharacter interpretation, no word splitting.

  • Exactly one argumentexec scans that single string for shell metacharacters. If any are found, the whole string is passed to /bin/sh -c for parsing. If none are found, the string is split on whitespace and handed to execvp directly.

The rule matters for both correctness and security. A one-argument string containing a space but no metacharacters is split into words and executed directly — which is often what you want. A one-argument string containing |, >, $, ;, glob characters, or quotes goes through the shell, and the shell will happily expand wildcards, interpret variables, and split on $IFS.

exec '/bin/echo', 'Your arguments are:', @ARGV;  # direct execvp
exec "sort $outfile | uniq";                     # pipeline → shell
exec "ls /tmp";                                  # no metachars → split + execvp

To force the no-shell path even with a single logical command, use the indirect-object form described next.

Indirect-object form — set argv[0], skip the shell#

Placing a block or scalar before LIST (with no comma) tells exec two things at once: run this program, and pass those arguments as argv. The first element of LIST becomes argv[0] — the name the child sees for itself — which need not match the program path.

exec { '/bin/echo' } 'echo', 'hi';   # argv[0] is "echo"
my $shell = '/bin/csh';
exec $shell '-sh';                   # login-shell convention: argv[0] = "-sh"
exec { '/bin/csh' } '-sh';           # same, more direct

The indirect-object form always treats LIST as a multi-valued list, even if it has only one element. That disables the metacharacter scan entirely:

my @args = ('echo surprise');
exec @args;              # one element → shell scan → /bin/sh -c 'echo surprise'
exec { $args[0] } @args; # always direct execvp, "echo surprise" is argv[0]

In the second form there is no program on disk literally named echo surprise, so execvp fails, exec returns false, and $! holds the reason. That’s the safe behaviour: the caller gets to decide what to do, rather than the shell running something unexpected.

Global state it touches#

  • $! — set on failure to the errno from the underlying execvp(3) call. Only meaningful when exec actually returns.

  • $? — left unchanged by exec itself, but a failed shell-mediated attempt may leave the shell’s exit status visible to a wrapping system.

  • The process’s open filehandles, environment (%ENV), current working directory, umask, signal dispositions, and resource limits all carry into the replacement program — exec replaces the program, not the process.

Examples#

Run a program with an explicit argument list, direct execvp:

exec '/usr/bin/git', 'status', '--short'
    or die "exec git: $!";

Hand a command line to the shell deliberately, using metacharacters:

exec "find . -name '*.tmp' -print0 | xargs -0 rm"
    or die "exec pipeline: $!";

Set a distinctive argv[0] so the child’s ps line identifies its role:

exec { '/usr/local/bin/worker' } 'worker[queue=email]', @flags
    or die "exec worker: $!";

Fork-and-exec — the classic pattern for launching a child without replacing yourself:

my $pid = fork // die "fork: $!";
if ($pid == 0) {
    exec '/usr/bin/gzip', '-9', $file
        or die "exec gzip: $!";
}
waitpid $pid, 0;

Guarantee the no-shell path when the argument list was assembled from data:

my @cmd = build_command();           # may contain one element or many
exec { $cmd[0] } @cmd
    or die "exec $cmd[0]: $!";

Edge cases#

  • Never returns on success. Any code after a bare exec is reachable only on failure. Under use warnings, a following statement other than die, warn, or exit triggers a warning — Perl’s hint that you probably meant system.

  • Single-argument trap. exec $cmd where $cmd came from user input is a shell-injection vector if $cmd contains metacharacters. Use the indirect-object form or pass a pre-split list.

  • Output buffering. Perl attempts to flush handles opened for output before handing control to the new program, but this is not guaranteed on every platform. If you have pending output on an unflushed handle, set $| on the selected handle — or call the autoflush method on each IO::Handle — before exec.

  • END blocks and DESTROY. exec does not run END blocks, and it does not invoke DESTROY on your objects. The process image is replaced; Perl-level cleanup never happens. Anything that needs a clean shutdown (temp files, lockfile removal, buffered log flushes) must happen before exec.

  • Close-on-exec. Filehandles with the FD_CLOEXEC flag set are closed by the kernel during execvp; others stay open. Control this with fcntl or the $^F system filehandle threshold.

  • Shell-mediated failure is invisible. When exec routes through /bin/sh -c, a missing target program is reported by the shell and exec itself has already been replaced by /bin/sh. Your Perl code will not see a failure return.

  • One-arg list with no metacharacters is still split on whitespace and passed to execvp — so exec "ls /tmp" is a direct exec of /bin/ls with one argument, not a shell call.

  • Empty LIST is a runtime error; there is no program to run.

Differences from upstream#

Fully compatible with upstream Perl 5.42.

See also#

  • system — run a program and wait for it; what you almost certainly wanted if exec is at the top of the file

  • fork — pair with exec in the child to launch a program without replacing the parent

  • wait — collect a child produced by fork + exec

  • die — terminate the current program with an error; the idiomatic partner of exec ... or die

  • $! — error reason set when direct-exec fails

  • $| — autoflush flag; set before exec so no buffered output is lost when the process image is replaced