Recent from talks
Nothing was collected or created yet.
Exec (system call)
View on WikipediaThis article needs additional citations for verification. (February 2024) |
In computing, exec is a functionality of an operating system that runs an executable file in the context of an already existing process, replacing the previous executable. This act is also referred to as an overlay. It is specially important in Unix-like systems, although it also exists elsewhere. As no new process is created, the process identifier (PID) does not change, but the machine code, data, heap, and stack of the process are replaced by those of the new program.
The exec() call or some variant is available for many programming languages including compiled languages and some scripting languages. In command interpreters, the exec built-in command replaces the shell process with the specified program.[1]
Nomenclature
[edit]Interfaces to exec and its implementations vary. Depending on programming language it may be accessible via one or more functions, and depending on operating system it may be represented with one or more actual system calls. For this reason, exec is sometimes described as a collection of functions.
In C, there is no single, plain exec() function.
High-level programming languages usually provide one call named exec().[citation needed]
In POSIX systems, other Unix-like systems, and other multitasking systems
[edit]C language prototypes
[edit]The POSIX standard declares a family of exec functions in the unistd.h header file. The same functions are declared in process.h for DOS (see below), OS/2, and Microsoft Windows.
int execl(char const* path, char const* arg0, ...);
int execle(char const* path, char const* arg0, ..., char const* envp[]);
int execlp(char const* file, char const* arg0, ...);
int execv(char const* path, char const* argv[]);
int execve(char const* path, char const* argv[], char const* envp[]);
int execvp(char const* file, char const* argv[]);
int execvpe(const char* file, char* const argv[], char* const envp[]);
int fexecve(int fd, char* const argv[], char* const envp[]);
Some implementations provide these functions named with a leading underscore (e.g. _execl).[2]
The base of each is exec, followed by one or more letters:
- e – Environment variables are passed as an array of pointers to null-terminated strings of form
name=value. The final element of the array must be a null pointer.[3] - l – Command-line arguments are passed as individual pointers to null-terminated strings. The last argument must be a null pointer.
- p – Uses the PATH environment variable to find the file named in the file argument to be executed.
- v – Command-line arguments are passed as an array of pointers to null-terminated strings. The final element of the array must be a null pointer.[3]
- f (prefix) – A file descriptor is passed instead. The file descriptor must be opened with O_RDONLY or O_PATH and the caller must have permission to execute its file.[4]
In functions where no environment variables can be passed (execl(), execlp(), execv(), execvp()), the new process image inherits the current environment variables.
First command-line argument
[edit]The first argument arg0 is often the name of the executable file and may be the same value as the path argument. However, this is purely convention and there is no guarantee of this behavior, nor is it standardized. For instance, in Java, the first argument is not the path to the executable, but instead the first argument for the program.[5]
Effects
[edit]A file descriptor open when an exec() call is made remains open in the new process image, unless fcntl() was called with FD_CLOEXEC or opened with O_CLOEXEC (the latter was introduced in POSIX.1-2001). This aspect is used to specify the standard streams of the new program.
A successful overlay destroys the previous memory address space of the process. All of its memory areas that were not shared are reclaimed by the operating system. Consequently, all its data that were not passed to the new program, or otherwise saved, are lost.
Return value
[edit]A successful call replaces the current process image, so it cannot return anything to the program that made the call. Processes do have an exit status, but that value is collected by the parent process.
If the call fails, the return value is always -1, and errno is set to an appropriate value.[6]
In DOS
[edit]DOS is not a multitasking operating system, but replacing the previous executable image is essential due to harsh primary memory limitations and lack of virtual memory. The same API is used for overlaying programs in DOS and it has effects similar to ones on POSIX systems.
MS-DOS exec() functions always load the new program into memory as if the "maximum allocation" in the program's executable file header is set to default value of 0xFFFF. The EXEHDR utility can be used to change the maximum allocation field of a program. However, if this is done and the program is invoked with one of the exec() functions, the program might behave differently from a program invoked directly from the operating-system command line or with one of the spawn() functions (see below).
In shells
[edit]Many Unix shells also offer a builtin exec command that replaces the shell process with the specified program.[1][7] Wrapper scripts often use this command to run a program (either directly or through an interpreter or virtual machine) after setting environment variables or other configuration. By using exec, the resources used by the shell program do not need to stay in use after the program is started.[8]
The command can also perform a redirection. In some shells, it is possible to use it for redirection only, without making an actual overlay.
In other systems
[edit]OS/360 and successors include a system call XCTL (transfer control) that performs a similar function to exec.[9]
Versus spawning
[edit]The traditional Unix system does not have the functionality to create a new process running a new executable program in one step. Other systems may use spawn as the main tool for running executables. Its result is equivalent to the fork–exec sequence of Unix-like systems. POSIX supports the posix_spawn routines as an optional extension.[10]
See also
[edit]- Chain loading, overlaying in system programming
- exit (system call), to terminate a process
- fork (system call), to make a new process (but with the same executable)
- clone(), a way to create new threads
References
[edit]- ^ a b – System Interfaces Reference, The Single UNIX Specification, Version 5 from The Open Group
- ^ Whitney, Tyler. "_exec, _wexec Functions". learn.microsoft.com. Retrieved 2025-05-26.
- ^ a b – System Interfaces Reference, The Single UNIX Specification, Version 5 from The Open Group
- ^ – System Interfaces Reference, The Single UNIX Specification, Version 5 from The Open Group
- ^ "Java - Your Application Launcher - Dev.java". Dev.java: The Destination for Java Developers. Retrieved 2025-05-26.
- ^ – System Interfaces Reference, The Single UNIX Specification, Version 5 from The Open Group
- ^ Sharma, Sagar (2023-05-28). "Using exec Command in Bash Shell Scripts [4 Examples]". Linux Handbook. Retrieved 2025-05-26.
- ^ "Shell Wrappers". Linux Documentation Project. 2014-03-10. Retrieved 2021-02-18.
- ^ "XCTL". www.ibm.com. Retrieved 2025-05-26.
- ^ – System Interfaces Reference, The Single UNIX Specification, Version 5 from The Open Group
Exec (system call)
View on Grokipediaerrno to indicate the error, such as EACCES for permission denial or ENOEXEC for invalid executable format.[1][2]
The exec family includes several variants to accommodate different ways of specifying the executable path, arguments, and environment: execl and execv use variable arguments or arrays without path searching; execlp and execvp additionally search the PATH environment variable if the filename lacks slashes; and execle and execve allow specifying a custom environment array instead of inheriting the current one.[1] All variants ultimately invoke the underlying execve system call, which takes the filename, an argument vector (argv), and an environment vector (envp), with the first argv[0] typically matching the filename for program identification.[2] The total size of arguments and environment is limited by {ARG_MAX}, a system-defined constant, to prevent excessive memory usage during loading.[1]
When executed, an exec call terminates all threads in a multi-threaded process except possibly the calling one, reinitializes signal handlers to default for certain signals, and closes file descriptors marked with FD_CLOEXEC, ensuring a clean transition to the new image while inheriting core process state like the process ID and open files.[1][2] This behavior makes exec essential for implementing commands like shells invoking programs via fork followed by exec, and it underpins higher-level interfaces such as the C library's system() function, though exec itself is a low-level primitive for precise control over execution.[1] In Linux, the functions are thread-safe with minor caveats for environment handling in path-searching variants, and they conform to POSIX.1-2008 standards except for extensions like execvpe.[2]
Introduction
Definition and Purpose
The exec family of functions serves as a system call or library interface in Unix-like operating systems that replaces the current process image with a new one derived from a specified executable file. This overlay operation terminates the original program execution while loading and starting the new program in its place, without returning control to the caller upon success.[3] The primary purpose of exec is to enable the efficient execution of a different program within the existing process context, avoiding the overhead of creating an entirely new process through mechanisms like fork. By reusing the current process's identifier, parent-child relationships, and certain inherited attributes—such as open file descriptors (except those marked close-on-exec), the current working directory, and signal masks—exec conserves system resources in multitasking environments, where process creation can be costly in terms of memory and CPU time. This contrasts with full process creation, which duplicates the entire process state and requires additional cleanup.[3][2] A common use case involves loading a binary executable from a file path into the current process space, such as when a shell replaces itself with a user-specified command after parsing arguments. Variants like execl or execvp provide flexibility in passing arguments and searching paths but operate on the same core principle of process image replacement.[3]Historical Development
The exec system call originated with the first edition of Unix, released on November 3, 1971, by Ken Thompson and Dennis Ritchie at Bell Laboratories. Developed initially on a PDP-7 and ported to the PDP-11, it formed a core component of Unix's process model and file system, allowing a running process to replace its own executable image with a new one from a file. This innovation, implemented alongside early primitives like fork, addressed the need for efficient program loading in a multi-user environment, evolving from rudimentary command loading in pre-Unix prototypes to a dedicated system call that supported argument passing and environment inheritance. The basic exec appeared in the First Edition, with execl and execv introduced in the Second Edition (1972) for list- and vector-style arguments, respectively; path-searching variants like execlp and execvp followed in the Third Edition (1973).[4][5][6] By the Sixth Edition in May 1975, the exec family had evolved to support these varying invocation needs. This version, running on PDP-11 systems with 18-bit addressing, was the first widely licensed outside Bell Labs, disseminating the mechanism to universities and fostering further refinements in process control. By the 1980s, Berkeley Software Distribution (BSD) variants integrated and extended exec; for instance, 4.2BSD (1983) preserved its semantics while adapting it to virtual memory and TCP/IP networking, enhancing portability in academic and commercial settings.[7][8] The 1988 IEEE POSIX.1 standard (Std 1003.1) solidified exec's role by specifying a portable interface, particularly execve for passing arguments and environments, to ensure interoperability across Unix-like systems amid growing vendor fragmentation. This standardization influenced subsequent implementations, such as the Linux kernel's initial 0.01 release in September 1991, which incorporated POSIX-compliant exec variants from the outset to support Unix software porting. In the 1990s, Bell Labs' Plan 9 operating system (first distributed in 1992) adapted exec to its resource-centric model, combining it with rfork for lightweight process creation and execution over a distributed 9P protocol, emphasizing file-like access to services.[9][10] Exec's design profoundly shaped operating system architecture by enabling the pipe and shell scripting paradigms, where fork-exec sequences chain programs modularly—output from one becoming input to the next—promoting composable, text-based workflows central to Unix's enduring philosophy.[5]Nomenclature and Interfaces
Naming Conventions
The term "exec" originates from "execute," denoting the core operation of loading and running a program file within a process, thereby distinguishing the underlying system call from higher-level user-space wrapper functions that simplify invocation.[1] In POSIX-compliant systems, the nomenclature centers on the core system callexecve(), which serves as the primitive interface for replacing the current process image with a new one specified by an executable file, argument vector, and environment.[1] For programmer convenience, the standard defines a family of library functions sharing the "exec" prefix, such as execl(), execv(), execlp(), execle(), execvp(), which ultimately invoke execve() after handling argument formatting and optional features like path resolution.[1]
These variants employ systematic suffixes to indicate argument passing methods and additional behaviors: the suffix 'l' signifies a variable-length list of arguments provided directly in the function call, while 'v' denotes an array (vector) of null-terminated pointers to arguments; 'p' enables automatic searching of the PATH environment variable for the executable if no absolute path is given; and 'e' permits explicit passing of a custom environment array instead of inheriting the current process's environment.[1] This suffix-based scheme allows developers to select the most suitable interface without altering the fundamental execution semantics.
Beyond POSIX environments, similar naming patterns appear in non-Unix systems to maintain compatibility with C libraries, such as the DOS variants prefixed with an underscore (e.g., _execl(), _execv(), _execlp(), _execvp()), which mirror the suffix conventions for arguments, path search, and environment but operate within DOS's single-process model without forking.[11] This consistency helps avoid confusion with shell builtins like the Unix exec command, which repurposes the term for replacing the shell process itself rather than invoking the system call directly.
Function Variants in C
The exec family of functions in C provides several variants for invoking the exec system call, each designed to handle arguments and environment specifications in different ways for flexibility in programming. These functions are defined in the<unistd.h> header and are part of the POSIX standard, allowing programs to replace the current process image with a new one from an executable file.[3]
The core variant, execve, serves as the underlying system call interface and offers the most explicit control over arguments and environment. Its prototype is:
#include <unistd.h>
int execve(const char *path, char *const argv[], char *const envp[]);
#include <unistd.h>
int execve(const char *path, char *const argv[], char *const envp[]);
path points to the pathname of the executable file; argv is a NULL-terminated array of pointers to null-terminated strings representing the argument list, where argv[0] conventionally points to a filename string (often the basename of path); and envp is a NULL-terminated array of pointers to null-terminated strings for the new process's environment, or NULL to inherit the current process's environment from the global environ variable. All variants ultimately invoke the execve system call, but they differ in how they package the inputs for convenience.[12]
Other variants simplify common use cases. For instance, execl uses a variable argument list for arguments, making it suitable for cases with a small, known number of arguments:
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
path and arg0 (which serves as argv[0]) are additional null-terminated strings, terminated by a null pointer (char *)0; it inherits the environment without an explicit envp parameter. In contrast, execv takes an argv array like execve but also inherits the environment:
int execv(const char *path, char *const argv[]);
int execv(const char *path, char *const argv[]);
execle variant combines variable arguments with an explicit environment:
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
int execle(const char *path, const char *arg0, ... /*, (char *)0, char *const envp[] */);
execvp and execlp add path searching functionality, using the PATH environment variable to locate the executable if the provided file lacks a slash:
int execvp(const char *file, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
fexecve, uses a file descriptor instead of a pathname for the executable:
int fexecve(int fd, char *const argv[], char *const envp[]);
int fexecve(int fd, char *const argv[], char *const envp[]);
errno to indicate the specific error, but do not return on success as the process image is replaced. They ensure portability across POSIX-compliant systems, though exact behaviors may vary slightly in non-POSIX environments.[13][14][3]
Behavior in POSIX and Unix-like Systems
Specification Overview
The exec family of functions, as defined in POSIX.1-2008 and subsequent revisions up to IEEE Std 1003.1-2024, replaces the current process image with a new process image constructed from a regular, executable file, effectively overlaying the calling process without returning control to the caller upon success. The new process image is entered at the start of the new executable's main program, typicallyint main(int argc, char *argv[]), with the argument list and environment provided as specified by the calling function. Functions such as execlp() and execvp() perform a search for the executable using the directories listed in the PATH environment variable if the filename does not contain a slash; the search behavior is implementation-defined if PATH is unset or empty.[15]
The supported executable file formats are implementation-defined; examples include ELF on Linux and other modern Unix-like systems or the historical a.out format. For files that are executable but in an unrecognized format (yielding ENOEXEC), execlp() and execvp() shall attempt to execute the file using a command interpreter (typically /bin/sh) as a shell script. Before overlaying the process, the system must validate that the file has execute permission for the calling process; failure due to insufficient permissions results in an EACCES error, while an invalid or unrecognized format yields an ENOEXEC error. Additionally, the total size of arguments and environment is limited by {ARG_MAX}, ensuring resource constraints are enforced for compliance.[15]
Unix extensions to the POSIX exec family include execvpe(), which originated in System V Release 4 (SVR4) and is available in systems like Linux (via GNU libc) and BSD variants such as FreeBSD and NetBSD for secure PATH searching with an explicit environment; however, it is not part of the POSIX standard.[16] In Linux implementations, secure execution is enforced via the AT_SECURE auxiliary vector entry in execve(), set when executing set-user-ID or set-group-ID files, which clears potentially exploitable environment variables like LD_PRELOAD to prevent privilege escalation.[17]
The POSIX exec specifications evolved from the initial IEEE Std 1003.1-1988, which established the core interfaces for Unix-like systems, through revisions like POSIX.1-2008 (incorporating realtime and threads extensions) to the current IEEE Std 1003.1-2024, promoting consistent behavior and portability across operating systems including Linux, macOS, and FreeBSD.
Execution Mechanics
When an exec function is invoked in a POSIX-compliant system, the implementation validates the target executable file and, if successful, replaces the current process image with the new one without creating a separate process. First, the system checks permissions on the file and path components, returningEACCES if denied, and determines the file format, failing with ENOEXEC if invalid or unrecognized (though execlp() and execvp() may attempt shell interpretation in such cases).[15][17]
Upon successful validation, the executable's code, data, and other segments are loaded into the process's address space, overwriting the original program. The original code, data, and stack are discarded, and the provided arguments (argv) and environment (envp) are set up for the new image. Control is transferred to the executable's entry point. This replacement is atomic: if loading fails, the original process image remains intact, and control returns to the caller with an error. For example, in the Linux kernel, this involves structures like linux_binprm for format detection and functions like bprm_mm_init() for memory setup and copy_strings() for argument placement.[15][18]
Several key process attributes are preserved during this replacement to maintain continuity. The process ID (PID), parent process ID, open file descriptors (unless marked with FD_CLOEXEC), current working directory, signal mask, pending signals, and resource limits (such as RLIMIT_STACK or RLIMIT_DATA) are all inherited unchanged from the calling process.[15] However, the environment and arguments are updated to those provided by the exec invocation, and signal dispositions are reset: signals with handlers are set to their default actions (SIG_DFL), while ignored signals (SIG_IGN, except SIGCHLD which is reset to SIG_DFL if previously ignored) remain ignored.[15][19]
In a multi-threaded process, a successful exec terminates all threads except the calling thread, which continues as the only thread in the new process image.[15]
In many Unix-like systems, files beginning with a shebang (#!) line are treated as scripts: the kernel or implementation parses the line to invoke the specified interpreter with the script as an argument. For execlp() and execvp(), this fallback applies when the format is unrecognized but the file is executable. Specific limits, such as a maximum shebang length of 128 bytes or recursion depth to avoid loops (returning ELOOP if exceeded), are implementation-defined; for instance, Linux enforces these in its binfmt_script handler.[15][18][17]
Return Values and Error Handling
Upon successful execution, the exec family of functions replaces the current process image with a new one, transferring control to the loaded program without returning to the caller; the process's memory, stack, and execution context are overwritten, and the original code is not resumed.[15] This behavior ensures that the calling program effectively ceases to exist in its prior form, with the new executable inheriting the process's open file descriptors, user and group IDs, and other attributes unless modified by prior calls likesetuid or setgid.[15]
In the event of failure, the exec functions return -1 to the calling process and set the global errno variable to specify the reason for the error, allowing the original process to continue execution and handle the condition appropriately.[15] No automatic recovery is possible after a failed exec invocation, as the attempt to load the new image has already terminated without altering the process state; the caller must explicitly manage the error, such as by logging it, exiting, or retrying with adjusted parameters.[15]
The POSIX standard defines specific error conditions under which exec functions must fail, as well as optional conditions where they may fail depending on the implementation. The following table summarizes these errno values and their descriptions:
| errno | Description |
|---|---|
| E2BIG | The total size of the argument and environment lists exceeds the system limit {ARG_MAX}.[15] |
| EACCES | Search permission is denied on a component of the path prefix, or the file lacks execute permission, or the file is not a regular file.[15] |
| EINVAL | The file has a recognized but unsupported executable format.[15] |
| ELOOP | A loop exists in the symbolic links resolved during path name evaluation.[15] |
| ENAMETOOLONG | A component of the path exceeds {NAME_MAX} characters, or the path exceeds {PATH_MAX} characters.[15] |
| ENOENT | A component of the path does not exist, or the path is an empty string.[15] |
| ENOTDIR | A component of the path prefix is not a directory.[15] |
| ENOEXEC | The file has appropriate execute permissions but does not have a recognized executable format (applies to execl, execle, and execv variants, but not execlp or execvp which search PATH).[15] |
- ELOOP if more than
{SYMLOOP_MAX}symbolic links are encountered during resolution.[15] - ENAMETOOLONG if the length of a pathname resolved through symbolic links exceeds
{PATH_MAX}.[15] - ENOMEM if insufficient memory is available to create the new process image.[15]
- ETXTBSY if the executable file is currently open for writing by another process.[15]
- EISDIR if an attempt is made to execute a directory, though this may fall under EACCES in strict POSIX conformance.[15]
perror()—which prints a message corresponding to the current errno to standard error—or strerror(), which returns a string description of errno, should be employed to provide informative feedback. For example, after a failed execve(), a call to perror("execve failed") would output an error message like "execve failed: No such file or directory" if errno is ENOENT.
Implementations in Non-POSIX Environments
DOS and Legacy Systems
In MS-DOS environments, the_exec family of functions, provided through libraries like those in Turbo C and Borland C, enables the loading and execution of new programs such as .COM or .EXE files directly into the current process space. These functions replace the calling program entirely upon success, without returning control to the original code, and operate within the constraints of MS-DOS's single-tasking model, where no true multitasking occurs and the new program overlays the existing one in memory. Limited to the system's conventional memory of up to 640 KB, these operations must ensure sufficient free space for the target executable, as MS-DOS does not support dynamic memory allocation beyond this boundary.[20]
Variants of the _exec functions mirror Unix-like interfaces but adapt to DOS conventions, including execl, execv, execlp, and execvp, which differ in argument passing—either as a variable-length list (l suffix) or an array (v suffix)—and whether to search the PATH environment variable (p suffix). Arguments are constructed as null-terminated strings in an array (with the first element duplicating the program pathname) and limited to a total length of under 128 bytes, excluding terminators; upon execution, MS-DOS places these in the Program Segment Prefix (PSP) structure of the new program, specifically at offset 80h with the length byte preceding it. Related _spawn functions offer modes like P_OVERLAY, which behaves similarly to _exec by overlaying the parent without creating a separate process, alongside P_WAIT for synchronous execution. These were commonly used in development tools such as Turbo C and Borland compilers for MS-DOS 2.0 and later.[20]
Variants with an 'e' suffix (e.g., execle, execve) allow specifying a custom environment array, while others inherit the parent's environment, referenced through a pointer in the PSP at offset 2Ch. Errors are reported by returning -1 and setting errno, with DOS-specific details in _doserrno, such as code 2 for "file not found" (ENOENT) or insufficient memory (ENOMEM); common issues include argument lists exceeding limits or invalid executable formats. MS-DOS lacks Unix-style signals, so no preservation or handling of interrupts occurs during execution transitions.[20]
In legacy extensions like Windows 3.x, the DOS box via Virtual DOS Machines (VDMs) emulates the MS-DOS environment for running DOS applications in a virtualized, multitasking context, providing isolation from the Windows shell.[21]
Windows and Other OS Variants
In Windows operating systems, there is no direct system call equivalent to the Unix exec that overlays the current process image with a new executable. Instead, the CreateProcess API function is used to create a new process and its primary thread, which executes the specified module while inheriting the security context of the calling process.[22] Unlike Unix exec variants, CreateProcess always spawns a separate process with its own address space and resources, rather than replacing the existing process.[23] Error conditions in Windows process creation are reported via the GetLastError function, which retrieves a system-specific error code for the calling thread, differing from the Unix errno global variable. POSIX compatibility layers such as Cygwin and MinGW emulate Unix exec behavior on Windows by mapping calls like execlp to underlying Windows APIs. In Cygwin, the POSIX exec family of functions is implemented through the emulation layer, which invokes CreateProcess to launch the target executable while preserving POSIX semantics, including argument passing and environment inheritance.[24] Similarly, MinGW's execlp relies on the C runtime's _spawnvp function, which creates a new process using CreateProcess and searches the PATH for the executable.[25] These emulations allow Unix-like development on Windows but introduce overhead due to the creation of distinct processes rather than direct overlay. In other non-POSIX environments, exec-like functionality varies by system design. OS/2 employs the DosExecPgm API to execute a program as a child process, with inheritance of environment and resources from the parent.[26] OpenVMS provides the RUN_PROGRAM run-time library routine, which exit the current image and activate a new one within the same process context, effectively overlaying the executable while passing parameters and handling rundown of the prior image.[27] Embedded real-time operating systems (RTOS) often implement custom exec mechanisms tailored to resource constraints, such as direct image loading without full process creation. Adaptations in Unix-like systems outside core POSIX introduce additional restrictions. Android, built on Linux, enforces SELinux policies on execve, requiring domain transitions to authorize execution and confining processes to specific security contexts to prevent unauthorized escalations.[28] macOS, based on BSD, adheres to POSIX exec semantics but layers the App Sandbox, which limits execve operations based on entitlements, denying access to unauthorized executables or paths to enhance isolation.[29] These mechanisms contrast with pure POSIX by prioritizing security confinement over unrestricted overlay.Usage in Command Shells
Shell-Specific Exec Commands
In the Bourne shell and POSIX-compliant shells such as sh, the exec builtin command replaces the current shell process with the specified command without creating a new process or subshell.[30] When invoked with a command and arguments, it overlays the new program onto the existing shell process image, effectively terminating the shell and passing control to the executed program; standard input and output remain open unless explicitly redirected, in which case the redirections apply to the shell before replacement.[30] If no command is provided, exec performs any specified redirections on the current shell's file descriptors and returns control to the shell without altering the process.[30] Bash and Zsh extend the POSIX exec with additional options for more flexible invocation. In Bash, the -l option executes the command as a login shell, sourcing login profiles, while -a allows specifying an alternative argv for the executed program; the -c option clears the environment before execution.[31] Zsh provides similar extensions, including -l for login shell behavior, -a to set the argv, and -c to clear the environment, ensuring compatibility with POSIX while adding these conveniences for scripting and interactive use.[32] Redirections in these shells, when used with exec, permanently modify the current shell's file descriptors, applying to all subsequent commands until the shell exits or further changes occur.[31] The primary effects of the exec builtin across shells include freeing the memory associated with the original shell process by overlaying it, which is particularly useful for long-running scripts where the final command needs to persist without the overhead of the shell environment, such as in daemon initialization or as the last line of a script to optimize resource usage.[30] Upon successful replacement, the shell does not return; instead, the exit status of the executed program becomes that of the original shell process.[30] This behavior leverages the underlying exec system call to replace the process image without forking, ensuring efficient transition to the new program.[30] In C shell (csh) and its extension tcsh, the exec builtin operates similarly by replacing the current shell process with the specified command, but it integrates with the shell's job control features. Without a command, exec typically requires arguments and may apply redirections if specified, but does not re-execute the shell.[33] These variants lack the extended options of Bash or Zsh but maintain replacement semantics similar to POSIX, making them suitable for environments emphasizing job control over advanced argument manipulation.Script and Pipeline Integration
In shell scripts, theexec command serves as a built-in utility that replaces the current shell process with a specified program, eliminating the need for an additional fork and exec sequence. This is particularly useful at the end of a script after performing setup tasks, such as environment configuration or argument validation, where the script's primary purpose is to launch a long-running application. For instance, a script might conclude with exec /bin/myprog "$@" to invoke myprog directly in place of the shell, inheriting the script's environment and arguments while avoiding resource overhead from maintaining the parent shell process.[30]
This replacement mechanism enhances efficiency in resource-constrained environments or high-throughput scripts by reducing process creation costs, as the original shell's process image is overlaid without spawning a child process. POSIX-compliant shells like sh support this behavior explicitly, ensuring the executed program takes over the shell's process ID, file descriptors, and signal handlers unless otherwise redirected. In practice, this approach is common in wrapper scripts that bootstrap applications, such as those in system initialization or daemon management.[30]
For pipelines, exec integrates with the Unix pipe model (|) in non-interactive shells by facilitating I/O redirection across command chains, where it can replace the shell after establishing input/output streams for efficiency in scripted workflows. Without a command argument, exec applies redirections to the current shell, allowing subsequent pipeline commands to inherit modified file descriptors, such as redirecting stderr to a log file before piping output through filters like grep or awk. This enables streamlined data processing in scripts, such as exec 2> error.log; cat input.txt | sort | uniq, where the redirection persists for the pipeline without repeated forking. However, exec with a command cannot be embedded mid-pipeline, as it terminates the shell context immediately.[30]
Advanced usage in shells like Bash extends exec to manipulate additional file descriptors beyond the standard 0 (stdin), 1 (stdout), and 2 (stderr), such as opening a custom descriptor for reading configuration files: exec 3< /etc/config. This descriptor remains open for use in subsequent commands, functions, or even trap handlers, enabling persistent I/O access in complex scripts—for example, a trap on EXIT that reads from descriptor 3 to perform cleanup logging. Integration with shell functions allows exec to replace the function's execution environment similarly to script-level use, while traps benefit from inherited redirections for signal-responsive actions like dumping state on interruption.
The exec command adheres to POSIX sh standards for core functionality, ensuring portability across Unix-like systems, but variations exist in extended shells. In KornShell (ksh), exec supports enhanced job control, allowing seamless replacement within job-managed contexts without disrupting background processes, unlike basic POSIX sh. Bash mirrors ksh behavior closely for descriptor management and redirections. In contrast, Fish shell provides an exec command that replaces the shell but prohibits its use inside pipelines due to syntax restrictions, lacking a direct equivalent for mid-chain replacement while maintaining compatibility for simple process substitution.[30][34][35]
