Fact-checked by Grok 2 weeks ago

Bash

Bash is the Bourne-Again SHell (Bash), a Unix shell and command language interpreter that serves as the default command-line interface for many Unix-like operating systems, including GNU/Linux distributions. Developed by the Free Software Foundation as part of the GNU Project, Bash was first released in 1989 and functions both interactively for user commands and non-interactively for executing scripts. It is largely compatible with the original Bourne shell (sh), while incorporating features from the Korn shell (ksh) and C shell (csh), and adheres to the POSIX Shell and Tools standard (IEEE 1003.1) for portability across systems. Bash's core functionality revolves around interpreting commands, managing processes, and enabling scripting for automation tasks. In interactive mode, it supports advanced user conveniences such as job control for managing multiple processes, command-line editing with customizable key bindings, command history for reusing inputs, and aliases for shortening frequent commands. For non-interactive use, Bash reads and executes commands from files or strings, making it essential for scripting that handles variables, flow control structures like loops and conditionals, quoting mechanisms, and user-defined functions. These capabilities allow Bash to support complex programming constructs, including command substitution, arithmetic evaluation, and handling, positioning it as a powerful tool for and . Historically, Bash emerged as a alternative to proprietary Unix shells, with its development beginning in the late under Brian Fox and continuing through contributions from Chet Ramey. It has evolved through multiple versions, with the current stable release being version 5.3, released on July 5, 2025, ensuring ongoing enhancements for security, performance, and compatibility. Widely adopted due to its open-source nature under General Public License, Bash runs on various platforms beyond Unix, including Windows via subsystems like , and remains a foundational component in environments for its balance of simplicity and extensibility.

History

Origins

Bash, or , was originally developed by Brian Fox as part of the to provide a implementation of a , specifically as a replacement for the proprietary (sh). The , initiated by in 1983, aimed to create a complete operating system composed entirely of , and a command-line interpreter was essential for this effort, as existing shells like the were not freely redistributable under the GNU General Public License. Fox, hired by the in 1987, began coding in January 1988 to address these needs. The initial release of Bash occurred on June 8, 1989, as version 0.99, with the first public 1.0 following later that year. Early development focused on ensuring full backward compatibility with scripts while extending its capabilities to meet emerging standards and user demands. Key goals included compliance for portability across systems and the addition of advanced features such as command history for recalling previous inputs and job control for managing multiple processes. These enhancements made Bash more interactive and powerful than its predecessor, positioning it as a versatile tool for both scripting and everyday command-line use within the GNU ecosystem. Fox served as the primary maintainer until 1992, during which time he oversaw the initial stabilization and distribution of Bash. In mid-1992, Ramey, who had been contributing bug fixes and improvements since the early releases, assumed maintenance responsibilities, ensuring continued development under Project. This transition marked the beginning of Bash's evolution into a widely adopted standard , though the foundational work from 1988 to 1992 established its core as a alternative to Unix shells.

Development Milestones

Bash development has been led by Chet Ramey since 1992, who serves as the primary maintainer and has overseen numerous enhancements to the shell's functionality and compliance with standards. A major milestone came with the release of Bash 2.0 in 1996, which introduced programmable command completion, allowing users to define custom completion functions for commands, along with support for indexed arrays of unlimited length. Bash 3.0, released in 2004, brought improvements to array handling, including indirect array references via ${!name[*]}, as well as recursive globbing with ** and the =~ operator for matching in the [[ test construct. The 2009 release of Bash 4.0 added associative arrays declared with declare -A, coprocesses via the coproc for bidirectional communication with commands, and the mapfile builtin for efficiently reading lines into arrays. Bash 5.0 in 2019 enhanced capabilities, including better support for extended regular expressions in [[ =~ ]], and introduced new shell options like localvar_inherit for controlling variable inheritance in functions. Subsequent releases focused on refinement and . Bash 5.1, released in 2020, included improvements to mode for stricter conformance to the standard, along with a new secure via the SRANDOM . Bash 5.2 in 2022 addressed several vulnerabilities, including fixes for potential overflows and command injection issues, while adding options like patsub_replacement for safer substitutions. Post-2020 updates also featured enhancements to loadable builtins, enabling dynamic loading of custom commands without recompiling the , and improvements to the , such as better handling of with negative indices for more flexible navigation. The most recent milestone is Bash 5.3, released on July 5, 2025, which includes enhancements to the mapfile builtin for more robust handling of delimited input and improved support through expanded locale-aware string operations and matching. This version also introduces the GLOBSORT variable for customizable sorting of pathname expansions and a new command substitution syntax ${ command; } that executes without forking a subshell. Bash has been the default shell in most GNU/Linux distributions since the 1990s, owing to its POSIX compatibility and integration with the GNU Project. It has been ported to Windows through environments like (since the mid-1990s) and (since 2016), and remains available on macOS via third-party packages despite the shift to zsh as the default in 2019.

Invocation and Startup

Shell Modes

Bash operates in various modes that determine its behavior, input handling, and feature availability. The primary distinction is between interactive and non-interactive modes. In interactive mode, Bash reads commands directly from the user's terminal, providing features such as job control, command-line editing, and command history to facilitate user interaction. It waits for input at a prompt and handles signals like SIGINT appropriately, ignoring SIGTERM to maintain the session. This mode is invoked when Bash is started without specifying a script file or the -c option, such as by simply running bash in a terminal. In contrast, non-interactive mode executes commands from a file or a string without terminal interaction, lacking the , job , and features of interactive mode. It processes commands sequentially and exits upon completion, responding to signals like SIGTERM by terminating. Invocation occurs via a (e.g., bash script.sh) or the -c option (e.g., bash -c "command"), making it suitable for automated . Login shells represent another key mode, typically started at the beginning of a user session, such as during system . These shells are invoked explicitly with the --login option (e.g., bash --login) or as the initial shell process, enabling specific initialization for full sessions. Non-login shells, invoked without --login (e.g., in graphical terminal emulators), handle subsequent interactive sessions without the full login setup. The distinction affects initialization but not core execution. Restricted mode, accessed via rbash or bash --restricted, limits user capabilities for security purposes, such as preventing directory changes, PATH modifications, command redirection, or variable assignments that could alter execution. It enforces a fixed , disallowing features like sourcing files or using absolute pathnames in commands, making it useful for controlled access scenarios. Additional modes include mode, invoked with --posix, which enforces strict compliance with the Shell and Utilities standard by disabling Bash-specific extensions and aligning behaviors like reserved words and subshell handling. The sh mode emulates the traditional (sh), limiting features to ensure compatibility, often invoked implicitly when running as sh or combined with settings. Tracing mode, enabled via bash -x for scripts or set -x within a session, traces execution by printing each command and its arguments before running, aiding in without altering functionality.

Startup Files and Environment

When Bash is invoked as an interactive login shell, it first reads and executes commands from the system-wide startup file /etc/profile, followed by the first readable user-specific file among ~/.bash_profile, ~/.bash_login, or ~/.profile, in that order. For interactive non-login shells, Bash reads and executes ~/.bashrc directly. A common practice is for ~/.bash_profile to source ~/.bashrc using the . (source) command, ensuring consistent initialization across login and non-login interactive sessions. Key environment variables are typically configured in these startup files to establish the shell's operating context. The PATH variable defines a colon-separated list of directories where Bash searches for executable commands, such as /usr/local/bin:/usr/bin:/bin. The HOME variable specifies the user's home directory, defaulting to paths like /home/username and serving as the base for tilde expansion and certain file locations. The PS1 variable sets the primary command prompt string, often customized with escape sequences like \u@\h:\w\$ to display the username, hostname, and current working directory, and it undergoes parameter expansion in interactive shells. For non-interactive shells, such as those invoked to run scripts, Bash executes commands from the file specified by the BASH_ENV if it is set, allowing custom initialization without relying on interactive startup files. Login shells, being interactive by definition, do not process BASH_ENV and instead follow the profile-based hierarchy. In restricted mode (invoked as rbash), startup files are read normally before restrictions take effect, but the shell then prohibits modifications to variables like PATH, BASH_ENV, HOME, or SHELL, and limits file access by disallowing pathnames with slashes in commands like . () or redirection operators. Startup files also commonly configure locale settings to influence character handling, sorting, and internationalization, including support for Unicode. The LC_ALL variable overrides other locale categories (such as LANG or LC_CTYPE) and affects pattern matching in globbing (e.g., ensuring proper range expressions like [a-z] in UTF-8 locales), string comparisons in [[ tests, and Readline library behavior for meta-key handling in non-ASCII environments. For instance, setting LC_ALL=en_US.UTF-8 in /etc/profile or ~/.bashrc enables full Unicode support for input, output, and collation, preventing issues with multibyte characters in prompts or filenames.

Syntax

Basic Elements

Bash syntax is built upon fundamental components including commands, words, and operators, which form the foundational structure for executing instructions in the shell. These elements are parsed from input lines, where the shell interprets sequences of characters into executable units without initially performing expansions or substitutions. A command in Bash is either a simple command or a compound command. A simple command consists of a sequence of optional variable assignments, followed by blank-separated words and redirections, and is terminated by a control operator. For example, echo hello world is a simple command where echo is the command name and hello and world are arguments. Compound commands, in contrast, are shell constructs such as conditionals or loops that begin with reserved words like if or for and end with corresponding terminators, such as fi for an if statement. These allow grouping of multiple simple commands into structured blocks. Words serve as the basic tokens in syntax, defined as sequences of characters treated as single units by the , separated by whitespace such as spaces or tabs. Each word can represent a command name, , or part of an , forming the building blocks of commands. For instance, in ls -l /home, , -l, and /home are distinct words. Expansions, such as or command , operate on these words to generate their final form during execution. Operators in Bash include redirection, pipe, logical, and arithmetic types, which modify or connect commands. Redirection operators like > (for output to a file) and < (for input from a file) associate file descriptors with commands, as in command > output.txt. The pipe operator | directs the output of one command as input to another, exemplified by ls | grep file. Logical operators && and || control execution flow based on the exit status of preceding commands, executing the next only if the condition is met (success for &&, failure for ||). Arithmetic operators are evaluated within constructs like $((expression)) or the let builtin, supporting operations such as $((2 + 3)) which yields 5. Bash distinguishes between keywords (also called reserved words) and builtins. Keywords, such as if, for, {, and }, have special syntactic roles and cannot be redefined or used as command names; they introduce or terminate compound commands, like { commands; } for executing a block in the current shell environment. Builtins, such as cd or echo, are commands implemented internally by the shell itself, executed directly without invoking external programs. This separation ensures that keywords maintain their structural integrity during parsing. Parsing rules in Bash begin with tokenizing input into words and operators, adhering to specific separation conventions. Commands are delimited by control operators including semicolons (;), ampersands (&), logical operators (&&, ||), or newlines, allowing multiple commands per line or . For example, command1; command2 executes sequentially, while newlines implicitly separate commands in interactive mode. This initial phase precedes any expansions, establishing the command structure before further processing.

Expansions and Quoting

In , expansions transform words in commands and scripts by replacing them with their interpreted values, enabling dynamic content generation and substitution. These expansions occur after the parses the command line into words and operators but before execution, allowing constructs like variables and patterns to influence the final command. The primary types include brace expansion, which generates multiple strings from comma-separated lists or sequences within braces (e.g., echo {a,b}c expands to ac bc); expansion, which substitutes ~ with home directories (e.g., ~user becomes /home/user); parameter expansion, which replaces variables or parameters with their values using $parameter or advanced forms like ${parameter:-default} for unset handling; arithmetic expansion, which evaluates expressions within $(( )) (e.g., $((2 + 3)) yields 5); command substitution, which inserts the output of a command via $(command) or the deprecated `command`; and pathname expansion (globbing), which matches patterns like *, ?, and [abc] against filenames (e.g., *.txt expands to matching files). The expansions follow a specific order to ensure consistent processing: first expansion, then tilde expansion, followed by parameter and variable expansion, arithmetic expansion, and command substitution (performed left-to-right within each step), then (on systems supporting it, using <(command) or >(command) for file-like handling), word splitting, pathname expansion, and finally quote removal. This sequence prevents later expansions from altering earlier results, such as expansion occurring before pathname expansion to avoid unintended globbing. Word splitting then divides the results of the previous expansions into fields using the Internal Separator (IFS), which defaults to , , and ; for example, after parameter expansion, unquoted results like $var where var="a b" become separate words a and b. Quoting can suppress word splitting for specific parts, as double quotes preserve expansions but prevent splitting on IFS. Quoting mechanisms control which expansions occur by removing special meanings from characters. Single quotes ('...') prevent all expansions, treating content literally (e.g., echo '$var' outputs $var unchanged); double quotes ("...") allow , , and command expansions but inhibit word splitting and pathname expansion (e.g., echo "$var" outputs the value without splitting); the (\) escapes the immediate next character, disabling its special treatment (e.g., echo \$var outputs $var); and ANSI-C ($'...') interprets escapes like \n for . These rules apply after initial , with quote removal as the final step, ensuring quoted portions avoid further processing. For instance, in "$(echo hi)", command occurs, but the result avoids splitting. Bash handles Unicode via UTF-8 in a locale-dependent manner, with multibyte character support introduced in version 2.05a (2001) through the for input and display. Since Bash 4.0 (2009), case-modifying expansions like ${var^^} support multibyte characters; Bash 4.4 (2016) improved multibyte handling in read -N for incomplete sequences; Bash 5.3 (2025) enhanced backslash-quoting of multibyte characters. In Bash 5.x series (starting 2019), further refinements address UTF-8 in globbing via fnmatch() for , fix parsing issues in command substitutions and here-documents, and optimize performance in UTF-8 s by reducing strlen() calls and improving surrogate character . These updates ensure more reliable processing of international text in expansions, though full behavior still relies on the system's locale settings like LC_CTYPE=UTF-8.

Redirections and Substitutions

Bash provides mechanisms for input/output (I/O) redirections and various forms of substitutions to manipulate data flow and command execution within scripts and interactive sessions. Redirections allow commands to read from or write to files, strings, or other processes instead of the default standard input (stdin), standard output (stdout), and standard error (stderr). Substitutions, on the other hand, enable the embedding of command outputs or computed values directly into the shell's command line or scripts. These features enhance scripting flexibility by facilitating data piping, file handling, and dynamic expression evaluation.

Redirections

Redirections in redirect the file descriptors of commands, which by default are 0 for stdin, 1 for stdout, and 2 for stderr. Basic file redirections include the less-than operator < to read input from a , as in sort < data.txt, which sorts the contents of data.txt. The greater-than operator > truncates and writes output to a , for example [echo](/page/Echo) "Hello" > output.txt, overwriting any existing content. For appending without truncation, >> is used, such as [echo](/page/Echo) "World" >> output.txt, which adds to the end of the file. These operators can specify file descriptors, like 2> errors.log to redirect stderr. Here-documents, introduced with <<, allow multi-line input until a specified delimiter, providing a way to embed scripts or data directly. For instance:
cat << EOF
This is a here-document.
It ends at EOF.
EOF
This outputs the enclosed text, with the optional <<- variant stripping leading tabs for indentation. Here-strings, using <<<, redirect a single string as input, as in read var <<< "value", which assigns "value" to the variable var via stdin. Redirection duplication copies file descriptors using >& for output or <& for input. A common example is command > [file](/page/File) 2>&1, which sends both stdout and stderr to [file](/page/File); note that the order matters, as redirections are processed left-to-right, so 2>&1 > [file](/page/File) would only redirect stdout to [file](/page/File) while stderr goes to the original stdout. Appending variants like &>> [file](/page/File) combine stdout and stderr appending in one operator, available in Bash 4.1 and later. The noclobber option, enabled via set -o noclobber, prevents overwriting existing files with >, causing it to fail silently; to override, use >|, as in echo "force" >| existing.txt. Process substitution treats command output as temporary files: <(command) for input, like diff <(sort file1) <(sort file2), and >(command) for output, such as tar cf >(gzip) files/, which pipes the tar output to gzip. These are Bash extensions not present in POSIX sh.

Substitutions

Command substitution executes a command and replaces it with its stdout, stripping trailing newlines. The preferred modern syntax is $(command), as in files=$(ls) to capture directory listing into files; the legacy backquote form `command` is deprecated due to escaping complexities but still supported for . Within double quotes, no word splitting or filename expansion occurs on the result. Bash processes command substitution after parameter expansion but before word splitting. Arithmetic expansion, using $((expression)), evaluates integer arithmetic and substitutes the result. It supports operators including / for , * for , and % for , as in echo $((10 * 3 / 2)) yielding 15 or remainder=$((7 % 3)) setting remainder to 1. The expression undergoes and command substitution before evaluation, making it useful for dynamic calculations like echo $(( ${width} + 10 )). As defined in , arithmetic expansion evaluates integer expressions within $(( )), with Bash providing additional operators and the let builtin for compatibility. Subshells, delimited by parentheses (command), group commands in an isolated , inheriting the parent's and exported but localizing changes like assignments or directory modifications. For example, ([cd](/page/.cd) /tmp; [pwd](/page/Pwd)) prints /tmp without altering the parent's directory, ensuring no persistent side effects. Signal traps are reset to inherited values in subshells, and open files are shared until modified. This prevents unintended modifications in complex pipelines. Coprocesses, introduced in Bash 4.0, enable two-way communication via |& or the coproc builtin. For instance, coproc SORT { sort; } creates file descriptors COPROC{{grok:render&&&type=render_inline_citation&&&citation_id=0&&&citation_type=wikipedia}} for reading and COPROC{{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}} for writing to the sort process, with ${COPROC_PID} holding its . This allows bidirectional data exchange, such as reading sorted input incrementally. Parsing of redirections and s occurs after expansions like and command substitution but before the command executes, with redirections applied in left-to-right . In subshells, this isolation nuance means variables set via substitutions do not leak to the parent, a behavior that enhances reliability but requires explicit for persistence. in substitutions follows rules from word expansions to preserve literal content.

Features

Variables and Data Structures

In Bash, variables are named parameters that store values, which can be assigned using the syntax name=value, where name is a valid identifier consisting of letters, digits, and underscores, starting with a letter or underscore. If no value is specified, the variable is set to the null string. Assignment undergoes expansions such as tilde, parameter, command, arithmetic, and quote removal, but not word splitting or filename expansion. Variables can have attributes assigned via the declare builtin, such as -i for integer attributes, which causes the value to be evaluated as an arithmetic expression on assignment. The export attribute makes variables part of the environment, passing them to child processes, while local restricts visibility to the current function and its children. Unsetting a variable with unset removes it from the environment. Bash supports two types of arrays: indexed and associative. Indexed arrays are declared implicitly by assignment or explicitly with declare -a name, and elements are assigned as name[subscript]=value, where subscripts start at 0. Compound assignment allows initializing multiple elements, e.g., name=(value1 value2), splitting on whitespace or metacharacters. Associative arrays, introduced in Bash 4.0, require declare -A name and use arbitrary strings as keys, assigned as name[key]=value. Elements are accessed via ${name[subscript]} or ${name[key]}; all elements with ${name[@]} (separate words) or ${name[*]} (IFS-separated); indices/keys with ${!name[@]}; and length with ${#name[@]} or ${#name}. The += operator appends to strings or arrays. Since Bash 4.3, negative subscripts in indexed arrays count from the end (e.g., -1 for the last element). Bash 5.3 introduces the array_expand_once shopt option, which suppresses multiple evaluations of array expansions in command substitutions, replacing the earlier assoc_expand_once for improved efficiency and consistency. Special parameters provide predefined values without explicit declaration. Positional parameters &#36;1 through &#36;9 hold command-line arguments to the script or function, with &#36;0 as the script or name; additional arguments shift into &#36;10 and beyond. $# gives the number of positional parameters, $? the of the last command (0 for success), $$ the shell's process ID, and $! the of the most recent background job. $@ expands to all positional parameters as separate words (preserving quoting when double-quoted), while $* joins them into a single word using the first character of IFS (space by default). Parameter expansion manipulates variable values directly in the shell. For example, ${[parameter](/page/Parameter):-default} substitutes the default if the is unset or , otherwise the 's value. ${[parameter](/page/Parameter)#word} removes the shortest prefix matching word (a ) from the 's value. settings, particularly LC_COLLATE, influence in such expansions by determining character collation order, affecting behaviors like range expressions in patterns (e.g., [a-z] may vary by ).

Control Structures

Bash provides a variety of control structures to enable conditional execution and repetition of commands, forming the basis for scripting logic. These constructs build upon the shell's syntax for commands and expressions, allowing scripts to make decisions based on conditions or iterate over data. The primary conditional and looping mechanisms are integrated into compound commands, which execute as a single unit without forking additional processes unless specified.

Conditionals

Bash supports conditional execution through the if statement, which evaluates a test command and executes a block of code if the exit status is zero (success). The syntax is if test-commands; then consequent-commands; [elif more-test-commands; then more-consequents;] [else alternate-consequents;] fi, where multiple elif clauses allow sequential testing, and else handles the default case. The return status of the if construct is the exit status of the last command executed or zero if no condition succeeds. For example, to check if a file exists:
if [ -f "/path/to/file" ]; then
    echo "File exists."
fi
This uses the test builtin (or its synonym [ ]), a fundamental conditional expression evaluator inherited from the . The test command supports file inquiries like -f (regular file), -d (directory), and -e (exists); numeric comparisons such as -eq (equal), -ne (not equal), -lt (less than), -gt (greater than); and string tests including -z (zero length) and -n (non-zero length). It requires careful quoting to avoid word splitting, and its determines . For more advanced testing, Bash introduces the [[ ]] compound command, an extension of test that performs word splitting and pathname expansion only on the right-hand side of operators, reducing the need for quoting. It supports string comparisons with == (equal, including ) and != (not equal), as well as file operators identical to test. Additionally, [[ ]] enables lexicographic string comparisons using < and >. The construct returns zero if the expression is true. An example comparing numbers:
if [[ 5 -gt 3 ]]; then
    echo "5 is greater than 3."
fi
The [[ ]] also integrates matching via the =~ operator, where the right-hand side is treated as an extended (ERE) without needing anchors for full matches. Matches populate the BASH_REMATCH array with captured groups, and the nocasematch option (via shopt) enables case-insensitive matching. For instance:
if [[ "hello world" =~ ^hello ]]; then
    echo "Matches starting with 'hello'."
fi
This feature, unavailable in the basic test, allows precise pattern-based conditions. The case statement provides pattern-based branching for multi-way decisions, more efficient than chained if statements for string matching. Its syntax is case word in [patterns) commands ;;]... esac, where word (after expansions) is matched against shell patterns in order, executing the first matching block terminated by ;;. Additional terminators include ;& (fall through to next pattern without executing) and ;;& (test next pattern after executing). A default * pattern catches unmatched cases. Example for command-line argument handling:
case &#36;1 in
    start) echo "Starting service." ;;
    stop)  echo "Stopping service." ;;
    *)     echo "Unknown option." ;;
esac
The return status is zero if no patterns match or the last command's status otherwise. Patterns undergo expansions like tilde and parameter substitution but not word splitting.

Loops

Looping in Bash enables repetition based on lists, conditions, or user input. The for loop iterates over a list of words, setting a variable to each in turn. Basic syntax: for name [in words]; do commands; done, where words (if provided) are expanded and split; omission defaults to positional parameters ("$@"). Commands execute for each iteration, with the loop's status being the last command's exit status or zero if none run. Example summing numbers:
sum=0
for i in 1 2 3; do
    ((sum += i))
done
echo $sum  # Outputs 6
An variant, for ((expr1; expr2; expr3)); do commands; done, initializes with expr1, loops while expr2 is non-zero, and increments with expr3 after each body execution, akin to C-style loops. Expressions use arithmetic evaluation rules. The while loop repeats as long as a succeeds: while test-commands; do consequent-commands; done. It executes consequent-commands if test-commands returns zero, continuing until failure. The inverse until loop, until test-commands; do consequent-commands; done, runs while the test fails (non-zero status). Both share the same return status logic as for. These often pair with test or [[ ]] for conditions, such as reading input:
while read line; do
    echo "Read: $line"
done
To control flow, break n exits the innermost loop (or nth level), and continue n skips to the next . The select construct creates interactive menus: select name [in words]; do commands; done. It displays a numbered list of words (defaulting to "$@"), prompts for input via PS3, sets name and REPLY to the choice, and executes commands. If invalid, it reprints the menu. Useful for simple user selection, it returns the status of the last command or zero. Example:
select fruit in apple banana cherry; do
    echo "You chose: $fruit"
    break
done
Output might show: 1) apple 2) [banana](/page/Banana) 3) cherry #?

Compound Commands

Beyond basic conditionals and loops, Bash offers grouping and evaluation constructs. Command grouping with braces, { list; }, executes a as a unit, allowing shared redirections without subshells. Spaces are required after { and before }, and it ends with ; or . For example:
{ echo "Starting"; sleep 1; echo "Done"; } > output.log
This redirects all output to a file. The status is that of the last command. Arithmetic evaluation uses double parentheses, ((expression)), treating contents as an arithmetic context with C-like operators (e.g., ++, &&). It expands parameters and performs integer math, returning zero if the result is non-zero or one otherwise—ideal for loop conditions without explicit test. Example incrementing a counter:
((count++))
if (( count > 10 )); then
    echo "Limit reached."
fi
Variables reference without $ inside (( )). The [[ ]] compound, detailed earlier, serves as both a test and grouping mechanism for complex expressions, avoiding utility calls like /usr/bin/test. Coprocesses integrate with these structures by allowing asynchronous command execution within loops or conditionals, using coproc [NAME] { commands; } to create a two-way . File descriptors in the NAME array enable I/O redirection in control flows, such as reading coprocess output in a for . The coprocess runs the grouped commands asynchronously.

Job Control and Processes

Bash implements job control, a mechanism that allows users to manage multiple processes (jobs) within an interactive shell session by suspending, resuming, and directing them to run in the foreground or background. This feature relies on the underlying operating system's terminal driver and signals, such as SIGTSTP for suspension, to enable selective stopping and continuation of process execution. Job control is enabled by default in interactive non-login shells when the terminal supports it, placing each pipeline of commands in a separate process group to facilitate independent management. In Bash, a job consists of a pipeline of one or more commands, identified by a job number (starting from 1) and the process group ID (PGID) of its leader process. Foreground jobs have exclusive access to the controlling terminal, allowing them to read from and write to it while receiving keyboard-generated signals like SIGINT (from Ctrl+C). Background jobs, however, run asynchronously and are insulated from most keyboard signals; attempts by background jobs to read from the terminal may result in suspension via SIGTTIN or SIGTTOU. To initiate a background job, the & operator is appended to a command, as in sleep 100 &, which outputs a job specification like {{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}} 12345 indicating job number 1 and PID 12345. Suspension of a foreground job occurs with Ctrl+Z (generating SIGTSTP), immediately halting execution, while Ctrl+Y (SIGTSTP delayed) suspends only upon the next terminal input. Jobs can be resumed using job specifications such as %n (job n), %% or %+ (current job), %- (previous job), %string (job starting with string), or %?string (job containing string). Bash provides several built-in commands for job management. The jobs builtin lists active jobs with their status (Running or Stopped), job number, and command, optionally filtered by options like -l (include PIDs), -n (only jobs whose status has changed), -p (PIDs only), -r (running jobs), or -s (stopped jobs); for example, jobs -l might output {{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}}+ 12345 Stopped sleep 100. The fg builtin resumes the specified job in the foreground, waiting for its completion or suspension, while bg resumes it in the background, allowing the shell prompt to return immediately; both default to the current job if no specification is given, as in fg %1 or bg. The disown builtin removes jobs from the shell's table to prevent upon shell exit, with options -h (mark for no SIGHUP without removal), -a (all jobs), or -r (running jobs only). The wait builtin suspends shell execution until specified jobs or processes complete, returning their , and supports -n (wait for any job) or -p varname (store PID in variable). The kill builtin sends signals to jobs or processes, often used for termination (e.g., kill %1), and the suspend builtin halts the shell itself (useful in subshells), requiring -f for login shells. Job control behavior is influenced by shell options and variables. The set -m (monitor) option explicitly enables job control, ensuring processes run in separate groups and background completions are reported. The set -b (notify) option causes immediate reporting of background job terminations instead of waiting for the next prompt. The auto_resume variable, when set to exact, substring, or prefix, allows ambiguous single-word commands to implicitly resume matching stopped jobs without explicitly using fg or bg; for instance, with auto_resume=substring, typing sleep could resume a stopped sleep 100 job. Additionally, the shopt -s checkjobs option prompts for confirmation before exiting if running or stopped jobs exist. Upon job completion or removal, Bash updates the jobs table and may notify the user of status changes.

Scripting and Programming

Scripts and Functions

Bash scripts are text files containing a sequence of commands that the shell interprets and executes, allowing automation of tasks on Unix-like systems. To designate a script for execution by , the first line typically includes a directive, such as #!/bin/[bash](/page/Shell) or the more portable #!/usr/bin/env [bash](/page/Shell), which invokes the interpreter regardless of its . Once created, a script gains executable permissions via the [chmod](/page/Chmod) +x filename command, enabling direct invocation as ./filename from the command line. Alternatively, scripts can be sourced into the current using the . () operator or the source builtin, which executes the commands in the present session without spawning a new process, useful for setting variables or defining functions. Scripts commonly receive arguments passed at invocation, accessible within the script as positional parameters: &#36;1 for the first argument, &#36;2 for the second, and so on, up to &#36;9; for all arguments as separate words, ${@} preserves quoting and spacing, while $* treats them as a single string. For example, a script named greet.sh with content echo "Hello, &#36;1!" invoked as ./greet.sh world outputs "Hello, world!". Shift the parameters with shift to process remaining arguments iteratively. Functions in encapsulate reusable blocks, defined using the syntax name() { commands; } or function name { commands; }, where the body consists of compound commands executed upon invocation like a regular command. The returns the of its last command by default, or a specific (0-255) via the return builtin, with 0 indicating success and non-zero values denoting errors. Variables declared as within a using local var or declare -l var remain scoped to that function and its children, preventing unintended modifications to the ; employs dynamic scoping, so variables can access and alter the caller's variables unless explicitly localized. Recursion in functions is supported but limited by the FUNCNEST shell option; if set to a positive integer, it caps the maximum nesting depth, while the default (unset or 0) imposes no artificial limit, constrained only by system stack resources. For instance, a recursive function might be defined as:
bash
factorial() {
    if (( &#36;1 <= 1 )); then
        echo 1
    else
        local result
        result=$(( &#36;1 * $(factorial $((&#36;1 - 1))) ))
        echo $result
    fi
}
Aliases provide a shorthand for command substitution, created with alias name='expansion', but they merely replace text before execution and lack support for arguments, conditionals, or complex logic, making functions preferable for reusable, parameterized code. For advanced extensions, Bash supports loadable builtins—dynamically linked modules compiled as shared objects—loaded via enable -f /path/to/module.so builtin_name, allowing custom commands integrated directly into the shell without forking external processes. Portability of Bash scripts across Unix-like systems requires caution, as Bash extends POSIX standards; use #!/usr/bin/env sh for POSIX-compliant shells when avoiding Bashisms, test with minimal shells like dash, and eschew features like arrays or [[ ]] tests unless Bash-specific. For Bash-exclusive scripts, the #!/usr/bin/env bash shebang ensures locating the interpreter via PATH. Modern scripting patterns emphasize robustness through strict mode via set -euo pipefail at the script's start, which exits on errors (-e), treats unset variables as errors (-u), and propagates pipeline failures (pipefail), reducing silent bugs. Associative arrays, enhanced in Bash 5.x for better performance, enable key-value storage with declare -A map; map[key]=value, ideal for configuration handling, while readarray efficiently loads lines into indexed arrays for data processing. Bash 5.3, released in July 2025, introduced further enhancements including the array_expand_once shopt option for controlling array subscripts and new options for builtins like read (-E for readline integration). These patterns promote safer, more maintainable code, as seen in contemporary automation tools.

Built-in Commands

Bash built-in commands are internal functions implemented directly within the Bash interpreter, allowing for efficient execution without invoking external programs. Unlike external commands, which require a search through the PATH environment variable and process forking, built-ins execute instantaneously and have direct access to the shell's internal state, such as variables and options. This design enhances performance, particularly in scripts where speed and resource efficiency are critical. Core built-in commands provide essential utilities for navigation, output, input, and conditional testing. The cd command changes the current working directory, supporting options like -L to follow symbolic links or -P to use physical paths, and it updates the $PWD variable accordingly. The echo builtin outputs its arguments separated by spaces, appending a newline by default; the -n option suppresses the newline, while -e enables interpretation of backslash escapes like \n for newlines. For more precise formatting, printf uses a format string to output arguments, supporting specifiers such as %s for strings, %d for integers, and %b for strings with backslash escape interpretation; it does not add a newline unless specified. The read builtin reads a line from standard input, assigning words to variables; options include -a to read into an array, -d to specify a delimiter other than newline, and -t for a read timeout. Conditional evaluation is handled by test (or its synonym [) , which returns true or false based on file, string, or numeric tests, such as -f file to check if a file exists. Utility built-ins include : (a no-op that always succeeds), true (exits with status 0), and false (exits with status 1), useful for scripting control flow without side effects. Advanced built-ins manage shell variables, key bindings, and command availability. declare and its synonym typeset declare or display variables with attributes, such as -i for integers, -a for arrays, or -x to export to the environment; they can also modify existing variables. The export builtin marks variables for inheritance by child processes, optionally assigning values, while unset removes variables or array elements, with options to target specific attributes. For interactive use, bind configures Readline key bindings, allowing commands like bind '"\C-x": backward-kill-line' to map Ctrl-X to a function. The enable and disable built-ins toggle the availability of other built-ins and POSIX special built-ins, using -n to disable or -p to print status. The help builtin provides documentation for built-ins, displaying brief descriptions with -d, manpage-style output with -m, or just the synopsis with -s; invoking help without arguments lists all available built-ins. This contrasts with external commands, which rely on man pages and lack direct shell integration, making built-ins preferable for tasks requiring variable access or minimal overhead. Recent versions have introduced enhancements to built-ins for improved functionality. In Bash 5.2, unset gained support for subscripts like @ or * to remove specific keys from associative arrays or all elements from indexed arrays without deleting the array itself. Additionally, printf added the %Q specifier, which quotes its argument while applying precision to the unquoted form. These updates build on Bash's POSIX compliance while extending capabilities for advanced scripting. For example, to demonstrate read with an array:
read -a words <<< "hello world"
echo "${words[0]}"  # Outputs: hello
This assigns the input line to the words array, showcasing direct shell variable interaction unavailable in external equivalents.

Debugging Techniques

Bash provides several built-in options for tracing and inspecting script execution to aid in debugging. The set -x option, known as xtrace, causes the shell to print each command and its arguments to standard error just before execution, allowing developers to follow the flow and values during runtime. Similarly, set -v, or verbose mode, prints each input line as it is read from the script or standard input before any processing occurs, which helps identify issues in script parsing or sourcing. These options can be enabled at the script's start or toggled dynamically, and they are often combined for comprehensive tracing. The PS4 variable customizes the format of the trace output produced by set -x, defaulting to a simple "+ " prefix but allowing additions like line numbers or timestamps for more informative logs. For more advanced introspection, the trap 'action' DEBUG command installs a hook that executes the specified action before each simple command, enabling custom debugging logic such as logging variables or conditional breakpoints. This trap integrates with the extdebug shell option, enabled via shopt -s extdebug, which extends debugging capabilities by providing access to additional variables like BASH_LINENO—an array tracking line numbers in the current execution stack—and FUNCNAME, which lists function names in the call stack, facilitating better stack traces during error analysis. These features allow for programmatic inspection without external tools, though they require careful management to avoid infinite recursion in the trap handler. Bash 5.3 added improved error reporting with line numbers for unterminated compound commands, enhancing debugging. An external tool, bashdb, offers a full-featured debugger modeled after gdb, supporting breakpoints, variable inspection, backtraces, and step-through execution for Bash scripts. To use it, scripts are run with bashdb script.sh, providing an interactive environment to halt at specific lines or functions and examine the state, which is particularly useful for complex scripts where built-in tracing falls short. Error handling in Bash relies on exit codes, accessible via the $? special parameter, which holds the status (0 for success, 1-255 for failure) of the most recent command. Developers can check this with constructs like if [ &#36;? -ne 0 ]; then echo "Error occurred"; fi, or use the logical OR operator || for immediate fallback actions, such as command || { echo "Failed"; exit 1; }, ensuring scripts propagate errors appropriately. The trap ERR hook can also catch non-zero exits globally, automating responses like cleanup or logging. Common pitfalls in Bash scripting often stem from unquoted variables, which undergo word splitting and pathname expansion, leading to unexpected behavior or security issues; for instance, if a variable contains spaces or wildcards, echo $var might split into multiple arguments or expand to unintended files. Always quote variables as "$var" to preserve their literal value and prevent such expansions, a practice that mitigates many runtime errors during debugging.

Standards and Compatibility

POSIX Compliance

Bash implements the POSIX Shell and Utilities specification as defined in IEEE Std 1003.1, aiming for conformance as a command interpreter and scripting language. When invoked in POSIX mode using the --posix command-line option or the set -o posix builtin, Bash enforces stricter adherence to the standard by altering default behaviors to match the POSIX sh utility requirements. This mode sets the POSIXLY_CORRECT environment variable, uses POSIX-specific startup files like $ENV, and enables features such as alias expansion in non-interactive shells, while disabling many Bash-specific extensions (Bashisms) to ensure portability. For example, arrays and advanced associative arrays, which are not part of the POSIX specification, are unavailable in this mode. In POSIX mode, Bash supports core compliant features including basic commands for execution and search (e.g., simple command invocation and path resolution), shell variables with assignment, export, and scoping rules, and standard control structures such as if, for, while, case, and function definitions. Parameter expansions like ${parameter}, ${parameter:-word}, ${parameter#word}, and ${parameter%word} follow POSIX semantics for substitution, default values, and pattern removal or matching. These elements allow for portable scripting across compliant systems, though some Bash defaults outside POSIX mode may differ, such as in redirection or time command handling. To test and ensure compliance, scripts can be run with bash --posix or invoked as sh (assuming Bash is linked to /bin/sh), which automatically enters POSIX mode after startup files. For finer control in portable scripting, options like shopt -u compat31 can unset compatibility behaviors from older Bash versions that might conflict with strict POSIX rules, such as certain quoting or pattern handling quirks. This approach promotes interoperability without relying on non-standard extensions. Bash's POSIX support has evolved significantly since version 4.0, with enhancements to alignment in areas like command substitution parsing and error handling. Version 4 and later introduced compatibility levels via shopt (e.g., compat40), allowing selective reversion for legacy scripts while advancing overall standard conformance. In Bash 5.3, released in 2025, further updates addressed edge cases for full POSIX compliance, including expanded umask builtin features and forced job notifications in POSIX mode. These improvements ensure Bash remains a robust choice for standards-based environments.

Differences from Other Shells

Bash extends the Bourne shell (sh) by incorporating additional features such as command history management via the history and fc builtins, support for one-dimensional arrays with specific expansion syntax, and enhanced function definitions that allow local variables using the local builtin. Unlike the Bourne shell, Bash also provides job control capabilities, including the disown builtin for managing background processes and coprocess support through coproc. In comparison to the C shell (csh), Bash offers superior scripting capabilities due to its POSIX-compliant syntax and avoidance of csh's C-like constructs that can lead to subtle errors in scripts, making Bash the preferred choice for portable and maintainable code. While csh emphasizes interactive use with features like a distinctive % prompt for history substitution, Bash prioritizes robust programming constructs without relying on such interactive-specific elements. Bash differs from Zsh in that it lacks Zsh's built-in advanced tab completion, syntax highlighting, and theme support, requiring external tools like for similar enhancements, though Bash remains more portable across Unix-like systems due to its stricter adherence to traditional standards. Relative to Fish, Bash adheres to POSIX standards for broader script compatibility, whereas Fish diverges with a non-POSIX syntax that prioritizes out-of-the-box user-friendliness through features like autosuggestions and web-configured completions, potentially complicating integration with legacy Bourne-style scripts. To ensure compatibility, Bash supports emulation modes such as invoking it with the --posix option to enforce POSIX compliance, mimicking Bourne shell behavior in areas like quoting and subshell inheritance, and incorporating Korn shell (ksh) elements like extended globbing patterns without a dedicated mode. When executed as sh, Bash automatically enters a restricted compatibility mode, reading the ENV variable for interactive shells and aligning with POSIX sh builtins. For external commands, Bash performs standard PATH lookups, similar to other shells, but avoids searching PATH for certain startup files like BASH_ENV in non-interactive contexts to prevent security risks. Bash's widespread adoption as the default shell in most Linux distributions stems from its status as the official shell of the GNU Project, providing a free, GPL-licensed implementation that aligns with the GNU/Linux ecosystem's emphasis on open-source standards and backward compatibility.

Security

Vulnerabilities

One of the most significant vulnerabilities in Bash history is Shellshock, identified as CVE-2014-6271 and disclosed in September 2014. This flaw stems from how Bash parses and processes function definitions embedded in environment variables, allowing attackers to append and execute arbitrary commands after the function definition. Specifically, Bash versions from 1.14 through 4.3 are affected, as the shell would execute trailing code in environment variables without proper sanitization during invocation, such as when launching subprocesses via CGI scripts or other applications that inherit the environment. The vulnerability was patched in Bash 4.3 with the first official patch release, though follow-up issues (e.g., CVE-2014-7169 and CVE-2014-7186) required additional fixes to fully mitigate variations of the exploit. Beyond Shellshock, Bash has been susceptible to other security issues arising from its scripting features. Race conditions can occur in file redirections due to time-of-check-to-time-of-use (TOCTTOU) discrepancies in shell command execution, where concurrent processes might manipulate files between the check and use phases, potentially leading to unintended data access or corruption in multi-threaded or parallel script environments. Similarly, the use of eval in CGI scripts written in Bash introduces risks of command injection if user-supplied input from HTTP headers or query parameters is not properly sanitized, allowing attackers to inject and execute malicious shell commands. Running Bash scripts as root exacerbates risks like PATH hijacking, where an unset or modifiable PATH environment variable enables attackers to substitute malicious binaries in the current directory for legitimate commands, leading to privilege escalation. More recent vulnerabilities include a heap-based buffer overflow in the valid_parameter_transform function in Bash versions 5.1 through 5.1.7, identified as , which could result in memory corruption or arbitrary code execution under specific conditions. This issue was addressed in Bash 5.1.8. Fixes for signal handling bugs—such as improper checking for terminating signals after executing command strings supplied via the -c option—were addressed in Bash 5.2 and later releases, preventing potential race conditions in signal processing that could disrupt shell stability or enable denial-of-service attacks. The impact of these vulnerabilities has been profound, particularly for Shellshock, which affected an estimated hundreds of millions of Unix-like systems, including Linux servers, macOS devices, and network appliances worldwide. Due to Bash's ubiquity as the default shell in many distributions, the flaw prompted massive internet-wide scanning campaigns by attackers seeking exploitable systems, resulting in widespread attempts to compromise web servers via CGI interfaces and leading to real-world breaches, botnet integrations, and urgent global patching efforts.

Best Practices

To ensure robust and secure Bash scripts, input validation is essential to prevent command injection, word splitting, and other exploits from untrusted data. Always quote variables when expanding them to avoid unintended word splitting and globbing; for instance, using "$var" instead of $var treats the value as a single argument, mitigating risks like arbitrary code execution if the variable contains spaces or special characters. For conditional tests, prefer the [[ ... ]] compound command over the single [ ... ] test builtin, as [[ suppresses word splitting and pathname expansion on its operands, supports extended pattern matching (e.g., [[ &#36;string == pattern* ]]), and performs arithmetic evaluation more reliably, reducing errors in comparisons. Avoid the eval builtin for executing dynamically generated strings, as it can lead to code injection if the input is untrusted; instead, use safer alternatives like functions, arrays, or command substitution ($(command)). Enable set -u (nounset) to treat unset variables as errors during expansion, preventing silent failures, and set -e (errexit) to exit immediately on command failures (with exceptions in conditionals), ensuring scripts fail fast and avoid cascading errors. For secure scripting overall, invoke Bash in restricted mode with rbash or bash -r when executing untrusted input, which disables features like changing directories (cd), modifying variables such as PATH or SHELL, using commands with absolute paths containing slashes, or certain redirections, thereby limiting potential escapes or modifications in controlled environments. Set the noclobber option (set -C) to prevent output redirections (e.g., > file) from overwriting existing files unless explicitly overridden with >|, protecting against accidental data loss in scripts handling sensitive outputs. Audit and explicitly set the PATH environment variable to a safe, minimal value (e.g., PATH=/bin:/usr/bin) at the script's start to avoid executing malicious binaries from untrusted directories, and prefer full paths for commands (e.g., /bin/ls instead of ls) to bypass PATH resolution entirely and ensure the intended executable runs. Performance in Bash scripts benefits from minimizing overhead in repetitive operations. Avoid creating subshells within loops, as each invocation of command substitution ($(command)) or pipelines in a loop forks a new process, leading to significant slowdowns; instead, refactor to use current-shell execution, such as reading files directly with $(<file) rather than $(cat file) or accumulating results in variables before a single external call. Prioritize Bash builtins (e.g., printf, read) over external utilities (e.g., echo, cat) for common tasks, as builtins execute without forking processes, improving speed in high-volume operations like data processing loops. For asynchronous communication in Bash version 4.0 and later, use the coproc reserved word to establish secure two-way pipes with background processes (e.g., coproc NAME { command; }), which avoids insecure temporary files or named pipes while enabling efficient, controlled data exchange without repeated subshell overhead. In CGI scripts or those running as root, additional precautions are critical due to exposure to web inputs or elevated privileges. Sanitize the environment by using #!/bin/bash -p in the shebang to disable sourcing of startup files and ignore untrusted environment variables, preventing attacks like function injection; follow this by clearing non-essential variables (e.g., via unset IFS and setting IFS=$' \t\n') and explicitly defining a secure PATH. Limit privileges by dropping them early (e.g., using setuid to a non-root user after necessary operations) and validating all inputs against whitelists to block injection, ensuring the script operates with minimal necessary access.