Hubbry Logo
Bash (Unix shell)Bash (Unix shell)Main
Open search
Bash (Unix shell)
Community hub
Bash (Unix shell)
logo
8 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Bash (Unix shell)
Bash (Unix shell)
from Wikipedia

Original authorBrian Fox
DeveloperChet Ramey
Initial release8 June 1989; 36 years ago (8 June 1989)
Stable release
5.3[1] Edit this on Wikidata / 3 July 2025
Repository
Written inC
Operating system
PlatformGNU
Available inMultilingual (gettext)
TypeShell (computing), Unix shell, command language
License
Websitewww.gnu.org/software/bash/

Bash (short for "Bourne Again SHell") is an interactive command interpreter and programming language developed for Unix-like operating systems.

It is designed as a 100% free software alternative for the Bourne shell, `sh`, and other proprietary Unix shells.[7] Bash has gained widespread adoption and is commonly used as the default login shell for numerous Linux distributions.[8]

Created in 1989 by Brian Fox for the GNU Project, it is supported by the Free Software Foundation.[9]

It also supports the execution of commands from files, known as shell scripts, facilitating automation.

The Bash command syntax is a superset of the Bourne shell's syntax, from which all basic features of the Bash syntax were copied. As a result, Bash can execute the vast majority of Bourne shell scripts without modification. Some other ideas were borrowed from the C shell, its successor tcsh, and the Korn Shell. It is available on nearly all modern operating systems, making it a versatile tool in various computing environments.

Definitions

[edit]

ASCII, strings and numbers

[edit]

The input language to the shell shall be first recognized at the character level.

— "POSIX 1003.1-2024, 2.10.1 Shell Grammar Lexical Conventions". The Open Group Base Specifications Issue 8, IEEE Std 1003.1-2024. The Open Group. Retrieved 25 August 2025.

$ printf '<newline>:  <%b>\n' $'\n'
<newline>:  <
>
$ printf '<tab>:      <%b>\n' $'\t'
<tab>:      <	>
$ printf '<space>:    <%s>\n' " "
<space>:    < >
$ printf '<NUL>:      <%b>\n' $'\0'
<NUL>:      <>

Any series of characters is called a "string," or sometimes a "string literal." In Unix-like operating systems, all characters, printable and non-printing, except for a few such as the null character and forward slash /, can be used in filenames. In addition, all strings are case-sensitive.[10]

Bash, like many other programming languages, uses zero-based numbering.

Control+key combinations

[edit]

The Control+key functionality is provided by GNU Readline and is available in interactive mode only. Certain keypress combinations allow a user to operate Bash to use tab completion and to search the command history.

  • Tab ↹ - Activate tab completion
  • - Scroll up (ie, backward) in the command history
  • - Scroll down (ie, forward) in the command history
  • Ctrl+r - Search the command history

Some keypress combinations also allow a user to operate the terminal emulator in order to move the cursor within the terminal window and to control the emulator program. By default, these keypress combinations in Bash mirror those of Emacs.[11]

Default keybindings for control codes include:

  • Ctrl+f - Move the cursor one character to the right
  • Ctrl+b - Move the cursor one character to the left
  • Alt+f - Move the cursor one word to the right
  • Alt+b - Move the cursor one word to the left
  • Ctrl+a - Move the cursor to the beginning of the current commandline
  • Ctrl+c - Cancels the current command and presents a new prompt
  • Ctrl+d - Closes the current Bash instance, possibly also closing the terminal-emulator
  • Ctrl+e - Move the cursor to the end of the current commandline
  • Ctrl+q - Wake the terminal; buffered keypresses are then processed
  • Ctrl+s - Put the terminal to sleep
  • Ctrl+w - Remove one word to the left of the cursor
  • Ctrl+z - Stop a foregrounded process

Vi keybindings are also available and can be enabled by running set -o vi.[12][13]

Syntax

[edit]

When Bash reads a full command line, the complete string is broken down into tokens. "Tokens" are identified using, and separated from each other using metacharacters.

As of Bash 5.3, the 10 metacharacters are the space, tab, and newline, as well as the following characters: |&;()<>

"Blanks" are composed entirely of unquoted metacharacters, "operators" each contain at least one unquoted metacharacter and "words" may not include any unquoted metacharacters.

In practice, Bash breaks down full command strings into tokens or groups of tokens that do contain metacharacters and tokens or groups of tokens that do not contain any metacharacters -- called "words." From there it further breaks words down into more specific, meaningful pieces like command names, variable assignment statements, etc.

The two blanks are space and tab.

Operators

[edit]

Control operators perform a control function. They can be either a newline or one of the following: ||, &&, &, ;, ;;, ;&, ;;&, |, |&, (, or ).


Redirection operators redirect the input or output streams. They include <, >, &>, <<, and <<<.

Words

[edit]

A word is a sequence of (non-meta-) characters treated as a single unit by the shell. A reserved word is a kind of a word that has a special meaning to the shell.[14] A name is a kind of a word separate from reserved words. Names consist solely of letters, underscores and numbers; which begins with either a letter or an underscore; which, however, may not begin with a number. Names also called identifiers, may be used for naming variables and functions.

Sixteen of the twenty-two "reserved words," which may be characters or words are as follows:

‘!’ [[ { ]] } case in esac for do done if then elif else fi ...

Names may only contain the characters ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.

In the following example of a full command string, metacharacters have a comma placed above them, ,, reserved words have a caret placed beneath them, ^, and other tokens have a backtick placed also beneath them, `.

$ #,  ,    ,   ,,    ,       ,,  ,
$   if echo foo; then bar=abc; fi
$ # ^^ ```` ```  ^^^^ ```````  ^^

Subshells

[edit]

A "subshell" is an additional instance of the shell which has been initialized by a current instance of the shell. When a "parent" shell creates a subshell, or a "child" shell, an exact copy of the parent's environment information is re-created and becomes the environment of the subshell.

In Bash, in non-arithmetic contexts, one can force the use of a subshell by enclosing a full command string in single parentheses.

$ echo foo
foo
$ ( echo foo )
foo
$

For this simple case, the preceding two commands are equivalent, however, use of subshells can have certain unexpected side effects. There are numerous different forms of syntax which can cause the initialization of a subshell.[clarification needed]

Expansion

[edit]

Data structures

[edit]

Bash offers variables and arrays as data structures, and though there are numerous kinds of each of these available, the data structures are relatively simple compared to other languages like C or Java.[15] All data is stored in memory as a string.

Beginning a word with a dollar character signifies that the word is the name of a variable or array. Surrounding the dollar / variable name syntax in double quotes is always advised. This practice shields the value(s) held by the parameter(s) from unwanted side effects.[clarification needed]

Wrapping the variable name in curly brackets {} is recommended for readablility and consistency between variables and arrays. When writing variables, curly braces are optional and square brackets would be a syntax error. The parameter names are always on the left side of the equals sign and values are always on the right.

Variables

[edit]

A variable is assigned to using the syntax name=value

To use a variable, the syntax $name is used, or ${name}, which expands to the value assigned to the variable.

The latter syntax must be used for certain names to prevent unwanted side effects. For example, $10 will be parsed as ${1}0, so using ${10} means it will be parsed as intended.

Positional parameters, usually passed to a bash script, are denoted by the variables numbered starting from $0. Special parameters are signified by punctuation characters.[15] For example, $@ expands to a list of the first through last positional parameters, "individually requoted, separated by spaces."

Environment variables are signified by all capital letters. Environment variables include UNIX variables like LESS_SIGUSR1, and Bourne shell variables such as $HOME.[15] Scripting variables are signified by all lower case letters or CamelCase.

Arrays

[edit]

Arrays are data structures which hold multiple values.[16] Arrays have a set of square brackets placed at the end of the variable name and inside the curly braces. When writing arrays, curly braces and square brackets are required.

An array is assigned using the syntax name=( one or more elements ). It is expanded using ${quux[@]} or ${quux[*]} or ${quux[1]}, depending on the use case.

Each kind of parameter is distinguished by a specific naming convention.[15]

Since Bash 4.0[17], Bash also supports associative arrays.

In this article, examples of variables from this section include ${foo}, PID, PWD, EUID, $$, ${quux} and ${zork} .

Execution

[edit]

"Execution" of a given program occurs when a user (or some other program) asks the operating system to act upon the instructions contained in the given program.

By default, Bash reads user code one line at a time, interprets any newline or semi-colon character ; as the end of the current command, and executes commands in sequence. If an interactive command extends beyond the width of the terminal emulator, it's usually possible to keep typing and the command will wrap around. To extend a command beyond a newline onto an additional line, it's necessary that the final character of the first line be an unescaped backslash, \, which signals "line continuation." Bash always finishes parsing and executing one full commandline before moving on to and beginning with the parsing of the next commandline.

$ foo=aa bar=bb quux=cc zork=dd; set -o xtrace
$ : "${foo}"; : "${bar}"
+ : aa
+ : bb
$ : "${quux}" \
> : "${zork}"
+ : cc : dd
$

The first word of a commandline is known as the "command position." Under UNIX coventionality, the first word of the commandline is always some kind of a command, and the rest of the words in the commandline string are either options for the command, arguments for the options, or some kind of input upon which the command will operate. "Options" are also called "flags," "switches," or, more formally, "operators." When Bash attempts to locate a command for execution, the directories it searches are those listed in the $PATH variable and the current working directory.[18]

$ # [COMMAND POSITION] [OPTION] [ARGUMENTS] 
$ # ,--^   ,------------^   ,----^
$ declare -p USER BASH_VERSION
declare -x USER="liveuser"
declare -- BASH_VERSION="5.2.37(1)-release"
$

Users and PS1

[edit]

A user account can be created for either a human or a programmatic user. In Unix-like OS's, there are two kinds of users: "privileged" and "regular." A privileged user, such as "root" or the operating system kernel, is allowed to do anything whatsoever on the machine. Unprivileged users are limited in various ways.

When an interactive shell session waits for user input, by default it prints a particular string of characters to the screen. In Bash, the value of this waiting-string is held in the shell variable $PS1. For regular users, a common default value for $PS1 is the dollar character, $.[a] For the superuser, a common default value is hashtag (#)

$ sudo --login --user root
[sudo] password for liveuser:
# vim /home/liveuser/names.txt
# exit
$ grep -e bob ./names.txt
grep: ./names.txt: Permission denied

Modes

[edit]

Programming paradigm

[edit]

Although most users think of the shell as an interactive command interpreter, it is really a programming language in which each statement runs a command. Because it must satisfy both the interactive and programming aspects of command execution, it is a strange language, shaped as much by history as by design.

Bash was written in C. A modular style can be approximated through good style and careful design.[19] It is often used in an imperative or procedural style.

Interactive and non-interactive modes

[edit]

As a command processor, Bash can operate in two modes: interactive or non-interactive. In interactive mode, commands are usually read from a terminal emulator. In non-interactive mode, which facilitates automation, commands are usually read from named files known today as shell scripts. When executed as a standalone command at the command-line interface (CLI), by default Bash opens a new shell in interactive mode.

Scripts

[edit]

Shell scripts are text files that contain code, often commands, intended to be read and acted upon by some particular interpreter in a batch process in a non-interactive mode and without any further user interaction. Interpreted scripts are programs that do not require their source code to be compiled: all of the relevant source code is contained within the script. There are many programs which can serve as a script interpreter: Perl, AWK, etc. Interpreted scripts are most often written for Unix shells.

The first two characters of the first line of any (executable) shell script begins with a something called a shebang: literally the characters hashtag (#) and bang (!) side by side.

$ cat ./example.sh
#! /bin/env bash
echo foo
exit

$

If a script is intended to be run by a user as a stand-alone program on the commandline, then it is referred to as an "executable." By convention, the filenames of executable unix shell scripts are identified the suffix .sh. The "execute" bit can be enabled on a shell script with the utility chmod:

$ ls -l ./example.sh
-rw-r--r--.1 liveuser liveuser 32 Aug  3 22:33 example.sh
$ ./example.sh
bash: ./example.sh: Permission denied
$ chmod 0744 ./example.sh
$ ls -l ./example.sh
-rwxr--r--.1 liveuser liveuser 32 Aug  3 22:33 example.sh
$ ./example.sh
foo
$

The source builtin

[edit]

With the source, or synonymous . command, Bash reads and executes shell commands from any text file by name.[20]

Login and non-login shells

[edit]

Bash can be executed as a login shell, or "session leader," in both interactive and non-interactive modes via the --login option. "Logging in" requires user authentication. For this reason, only one login shell exists per user session. In GNU/Linux, a user's login shell is identified in the /etc/passwd file.

$ awk -F ':' '$1 ~ /root/' /etc/passwd
root:x:0:0:Super User:/root:/bin/bash

When a human user initiates a login session, this procedure often occurs in a graphical user interface (GUI). When a user opens a terminal emulator, the emulator executes a non-login instance of the user's login shell.

Logging out of a shell session from within a terminal emulator can be accomplished with the exit command or, by default in Bash, pressing Ctrl+d.

Startup source files

[edit]

When Bash starts, it uses source to execute commands in a variety of dotfiles (see lists below).[21] These dotfiles, unlike shell scripts, typically have neither the execute permission enabled nor a hash-bang. By default Bash will source a somewhat different set of files, and in a different sequence, depending on:[22]

  • How bash is called
    • interactively, non-interactively, invoked with name sh
  • Which options are used
    • --login, --rcfile, --norc, --posix
  • Which environment variables are defined
    • BASH_ENV, ENV , and
  • Which files exist
    • /etc/profile
    • ~/.bash_profile
    • ~/.bash_login
    • ~/.profile
    • ~/.bash_logout, and
    • ~/.bashrc among others.

Of course, any startup file can also execute commands from any other file. Startup files can affect shell behavior, terminal emulators, the X window system and the window manager.

POSIX mode

[edit]

The POSIX IEEE 1003.1 standard specifies a common set of definitions that any shell system application (bash, dash, zsh, etc.) may conform to. Any shell user script (./myscript.sh) written in conformance with POSIX guidelines should be executable by any shell system application that has implemented the POSIX specification. As a result, there can be a reasonable expectation that POSIX-compliant scripts can be executed with success on any Unix or Unix-like operating systems which implements the POSIX standard (Linux, OpenBSD, Oracle Linux, HP-UX, etc.). These scripts are considered "portable" as they are and without any further modifications. The portion of POSIX that applies to shells and command line utilities is a subset of a larger group of POSIX standards that further specify how terminals and terminal emulators aught to function in order to also be considered portable.

When Bash is operating in POSIX mode, fewer features are available but the resulting code can be executed on a greater variety of operating systems.

To enable POSIX mode at the initialization of an interactive shell, Bash can be executed as either sh, bash --posix or bash -o posix.[23] To cause a script to be initialized in POSIX mode, one would use the either the hashbang #! /bin/env sh or the less portable #!/bin/sh. When an instance of Bash is operating in POSIX mode, the environment variable $POSIXLY_CORRECT is defined, and the value of the environment variable $SHELLOPTS includes the string posix.

$ declare -p POSIXLY_CORRECT
bash: declare: POSIXLY_CORRECT: not found
$ sh
$ declare -p POSIXLY_CORRECT
declare -- POSIXLY_CORRECT="y"
$

The full list of features available in Bash which are not specified by POSIX is considerable.[24] Here is a partial list:

  • Any arrays other than the array of positional parameters, $@, are not POSIX
  • The double bracket extended test construct, [[...]], is not POSIX
    • [...] and test are POSIX
  • One of the double-parentheses arithmetic-evaluation syntaxes, ((...)), is not POSIX
    • $((...)) is POSIX
  • Brace expansion, kernel{,-headers}, is not POSIX
  • Dynamic scoping of parameters and the local builtin are not POSIX
  • Process substitution, <(...), is not POSIX
  • Certain string-manipulation operations in Parameter Expansions are not POSIX
  • Most Bash builtin commands are not POSIX
    • The command enable -s prints the list of Bourne Special Builtins, which are POSIX
      $ enable -s | wc --lines
      16
      $ enable | wc --lines
      61
      
    • The enable builtin itself is not POSIX
    • In Bash, in non-POSIX mode, the . and source builtins are synonymous
      • The . (i.e., 'dot') builtin is POSIX, however
      • The source builtin is not POSIX
  • The $EPOCHSECONDS and $EPOCHREALTIME shell variables are not POSIX

System commands which are available in modern Unix-like operating systems, and which are also specified by POSIX may have fewer option flags or fewer relevant environment variables available under POSIX.

Because of these and other differences, modern (version 5) Bash shell scripts are rarely runnable "as-is" under the Bourne or legacy Korn shell interpreters. Scripting with portability in mind is becoming less common as GNU/Linux becomes more widespread.[23][25]

Code that is valid syntax in Bash and yet is not specified by POSIX is called a "bashism." The program checkbashisms can be used to make sure that a script can be executed in Debian Linux without any portability errors.[26] Vidar Holen's shellcheck is another static linter written in Haskell which can parse script syntax for compatibility with any or all of bash, dash, ksh, and Bourne sh.[27] The syntax requirements for each shell are each a little different. For example, Debian's policy allows some extensions in their scripts (as they are in the dash shell),[25] while a script intending to support pre-POSIX Bourne shells, like autoconf's configure, are even more limited in the features they can use.[28]

Other modes

[edit]

Restricted mode

[edit]

A restricted shell is used to set up an environment more controlled than the standard shell. A restricted shell behaves identically to bash with the exception that numerous actions are disallowed or not performed, including:

  • Changing directories with the cd builtin.
  • Setting or unsetting the values of the $SHELL, $PATH, $HISTFILE, $ENV, or $BASH_ENV variables.
  • Specifying command names containing slashes on the CLI.
  • Using absolute pathnames as arguments to the ., history, or hash -p commands
  • Specifying a path search with . -p or command -p
  • Importing function definitions and parsing the value of $SHELLOPTS from the shell environment at startup.
  • Redirecting output using the >, >, <>, >&, &>, and >> redirection operators.
  • Using the exec builtin to replace the shell with another command.
  • Altering shell builtins

Once restricted mode is enabled, it cannot be disabled. These restrictions are enforced after any startup files are read, and it does not apply to shell scripts. Restricted mode is rarely used.

Privileged mode

[edit]
In Bash, "privileged mode" is a rarely used option inherited [citation needed] from the SVR4.2 UNIX System V shell (circa 1992).[29] It can be enabled with set -p and disabled with set +p.[30] When privileged mode is enabled, the $SHELLOPTS shell variables includes the string, "privileged."

Extended debugging mode

[edit]
Enabled via bash --debugger at invocation or via shopt -s extdebug during either interactive or non-interactive modes. It uses a separate program called bashdb.[31] extdebug is not available in POSIX mode. See documentation for more information. See also § Debugging.

Compatibility modes

[edit]

Bash-4.0 introduced the concept of a shell compatibility level, specified as a set of options to the shopt builtin (compat31, compat32, compat40, compat41, and so on). There is only one current compatibility level – each option is mutually exclusive. The compatibility level is intended to allow users to select behavior from previous versions that is incompatible with newer versions while they migrate scripts to use current features and behavior. It's intended to be a temporary solution.[32]

— Bash Reference Manual, 6.12 Shell Compatibility Mode

Observability

[edit]

The xtrace option

[edit]

When xtrace is enabled, simple debugging content is printed to the terminal. It can be enabled with set -o xtrace or set -x, and disabled with set +o xtrace, set +x or set -. These options are also accepted at the commandline and at hash-bangs: #!/bin/bash -x, etc.

$ bash -x
$ echo $((  2 + 2  ))
+ echo 4
4
$ set -- 1 2 3
$ printf '<%s>\n' "$@"
+ printf '<%s>\n' 1 2 3
<1>
<2>
<3>
$

The xtrace shell setting is specified by POSIX. See also § Debugging.

The verbose option

[edit]

The verbose option prints strings to the terminal as they are read, and before any expansions are performed. Rarely used.[33]

Comments

[edit]

Comments can be a valuable way of clarifying information or explaining a script or source file to someone else who might not be familiar with the scripter's intentions or context.

Standard comments in Bash are denoted with a hash character: #. Any text to the right of the hash to the end of the line will be ignored. Inline comments are allowed, but hash comments will not print during debugging. See also: § xtrace.

Comments denoted with a colon character, :, originated with the Thompson shell. Any arguments to the right of colon : builtin are ignored. Inline comments are not possible, but colon comments will print during debugging and any parameters will have been expanded. [34]

$ # Define foo
$ foo=bar # An inline hash comment occurs on the same line as a command
$ set -x
$ # A regular comment (no output)
$ : "${foo}"
+ : bar
$

Exit codes

[edit]

When bash executes commands, exit status codes, also called "return codes," are produced which can offer some insight into the manner in which a program ceased running. The value of the most recently captured exit code is held within the shell parameter, 'question mark:' $?. In non-arithmetic contexts, (i.e., most of the time) the numerical or "Boolean" value of "true" is zero (0), and the value of "false" is one (1).

When a system command has executed, the intended meaning of its exit status can most often be found in its man page; usually a zero indicates success and a nonzero exit status indicates some kind of failure condition or partial success. ping is a well known command with three meaningful exit codes: 0, 1, and 2.

In Bash, within arithmetic contexts, the numerical truth values are reversed: "true" is one and "false" is zero. An arithmetic context can usually be identified by the syntax ((...)) or $((...)). If an arithmetic statement evaluates to the integer zero, then the statement is considered "true," and the exit code is one. If the statement evaluates to any number other than zero the arithmetic statement is "false" and the exit code is zero.

Not all Linux/UNIX commands provide meaningful exit codes beyond zero and one, and there is no standard system for definitions of exit codes in Linux.

$ true; echo "$?" # Exit code means "true"
0
$ false; echo "$?"; echo # Exit code means "false"
1
$
$ bash -c 'exit 99'; printf 'exit-code: %d\n\n' "$?"
exit-code: 99
$
$ ((  1 - 1  )); printf '%d\n' "$?" # This exit code means "true"
1
$ ((  1 + 1  )); printf '%d\n' "$?" # ...and this exit code means "false"
0

Job control

[edit]

The Bash shell has two modes of execution for commands: batch (asynchronous), and concurrent (synchronous). To execute commands in batch mode (i.e., in sequence) they must be separated by the character ;, or on separate lines:

$ command1; command2
$ command3
$

In this example, when command1 is finished, command2 is executed, and when command2 has completed, command3 will execute. A background execution of command1 can occur using symbol & at the end of an execution command, and process will be executed in background while immediately returning control to the shell and allowing continued execution of commands.

$ command1 &
$

Or to have a concurrent execution of command1 and command2, they must be executed in the Bash shell in the following way:

$ command1 & command2
$

In this case command1 is executed in the background, & symbol, returning immediate control to the shell that executes command2 in the foreground. A process can be stopped and control returned to bash by typing Ctrl+z while the process is running in the foreground.[35] A list of all processes, both in the background and stopped, can be achieved by running jobs:

$ jobs
[1]-  Running                  command1 &
$

In the output, the number in brackets refers to the job id. The plus sign signifies the default process for bg and fg. The text "Running" and "Stopped" refer to the process state. The last string is the command that started the process.

The state of a process can be changed using various commands. The fg command brings a process to the foreground, while bg sets a stopped process running in the background. bg and fg can take a job id as their first argument, to specify the process to act on. Without one, they use the default process, identified by a plus sign in the output of jobs. The kill command can be used to end a process prematurely, by sending it a signal. The job id must be specified after a percent sign:

$ sleep 100 &
[1] 4904
$ kill %1
$ jobs
[1]+  Terminated                    sleep 100
$

Job control, also known as "Monitor mode," is enabled by default in interactive shells, and can be disabled with set +m.

Signals

[edit]

Signaling is a means of inter-process communication (IPC). Sometimes a commandline process may seem to freeze in the middle of execution. In these instances it may become necessary to identify which process may be blocked and to manually end the offending process.

At an interactive terminal, it is usually sufficient to press Ctrl-c to end the current foreground process and return control back to the user prompt, or to press Ctrl-z to suspend it. Occasionally attempting to suspend a process will succeed when attempts to cancel a process appear unresponsive. In other cases it may be necessary to use the kill program to send an IPC signal. In this example, we use the kill command from a second terminal screen to terminate the process with PID 4331.

$ tty # Terminal one
/dev/pts/0
$ whoami
liveuser
$ sleep 1000 # Command hangs
$ tty # Terminal two
/dev/pts/1
$ whoami
liveuser
$ ps aux | grep -e sleep -e PID
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START    TIME COMMAND       
liveuser    4331  0.0  0.0 230336  2312 pts/1    S+   11:19    0:00 sleep 1000
liveuser    4333  0.0  0.0 231248  2516 pts/0    S+   11:19    0:00 grep --color=auto -e sleep -e PID
$ kill 4331
$ ps aux | grep -e sleep -e PID # The sleep process has ended
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START    TIME COMMAND       
liveuser    4333  0.0  0.0 231248  2516 pts/0    S+   11:19    0:00 grep --color=auto -e sleep -e PID
$
$ tty # Terminal one again
/dev/pts/0
$ whoami
liveuser
$ sleep 1000
Terminated
$

In Unix-like operating systems, a user is allowed to instruct the kernel to send a signal to a process that is owned by the user. A regular user may not send a signal to a privileged process. Signals can be sent to a process using the kill builtin or using the system binary of the same name.

$ whoami
liveuser
$ ps aux | awk '$2 ~ /\<1\>/' # Let\s view some info on the kernel process, process 1.
root           1  0.0  0.2  37140 20440 ?        Ss   04:44    0:18 /usr/lib/systemd/systemd --switched-root --system --deserialize=53 rhgb
$ kill -s SIGKILL 1
bash: kill: (1) - Operation not permitted
$ type -a kill
kill is a shell builtin
kill is /usr/bin/kill
$ /usr/bin/kill -s SIGKILL 1
kill: sending signal to 1 failed: Operation not permitted
$

The most commonly used signals can be viewed with kill -L | head -n 4. Each IPC signal is associated with a signal number, but exit codes and signal codes are two different things. While sending a process an IPC signal of 9 (a "KILL" signal) will almost certainly terminate the process immediately, it will most likely not result in the process returning an exit code of 9.

By default in Bash, builtin kill sends a TERM ("terminate") signal. It's common for commandline utilities to respond to a SIGTERM by shutting down and exiting cleanly. (TERM and SIGTERM are the same, the SIG- prefix to all signal names can be omitted.) The Ctrl-c keypress sequence in Bash sends a SIGINT, interrupt signal, to the foreground process. The Ctrl-z keypress sequence sends the SIGSTOP, stop signal.[36] When a process receives a SIGKILL, the process terminates immediately and messily. It is recommended to use SIGKILL only as a last resort.[37] The SIGKILL signal cannot be blocked or handled.

Processes can "catch" and "handle" IPC signals they receive. A user can use the kill builtin to "send" an IPC signal to another process. That target process can set up a mechanism, some plan beforehand, for how to repsond whenever any particular signal might be received, or "caught." The way a target program responds is referred to as how the program "handles" receiving the signal. In the man pages one can see how some system commands will print out certain information to the terminal when they receive a SIGHUP: for example, the dd command.[38]

When bash is interactive, in the absence of any traps, it ignores SIGTERM (so that kill 0 does not kill an interactive shell), and catches and handles SIGINT (so that the wait builtin is interruptible). When bash receives SIGINT, it breaks out of any executing loops. In all cases, bash ignores SIGQUIT. If job control is in effect, bash ignores SIGTTIN, SIGTTOU, and SIGTSTP.[39]

—  bash(1)

By default Bash shell scripts receive and respond to any and all IPC signals sent to them, however, Bash scripts can utilize the trap builtin to catch and handle signals.[40]

$ cat ./trap-example.sh 
#! /usr/bin/env bash
trap umask EXIT
echo bar
exit 0
$ chmod 0700 trap-example.sh
$ ./trap-example.sh 
bar
0077
$

There are a few signals which are only available from within Bash as GNU extensions: ERR, EXIT, RETURN and DEBUG. These signals can be useful in debugging, and can only be sent and handled by shell builtins. See also § Debugging.

Values of parameters

[edit]

There are many different implementations of echo. Some have the -e option, and some don't.[41] The list of options is not uniform across implementations, though echo and printf are both specified by POSIX. If a scripter wishes to know the precise value of a string contained by a variable, then the most consistent way of doing so is to use printf.

For any string containing any character (besides null?) including digits, the format specifier is %s.[citation needed]

$ foo=abc bar=123
$ printf '<%s>\n' "${foo}" "${bar}"
<abc>
<123>
$

For digits only, the format specifier is %d.

$ printf '<%d>\n' "${foo}" "${bar}"
bash: printf: abc: invalid number
<0>
<123>
$

With printf, a newline is never included in the output unless the scripter includes a newline in the format string. In the example below, where a newline has been omitted from the format string, the value of PS1 is printed on the same line as the output of the previous command.

$ printf '<%s>' "${foo}" "${bar}"
<abc><123>$

Another very consistent method is to use declare -p. The output of declare -p can be reused as input. However, not all variables and parameters can be printed using declare -p, for example, the values of the Special Parameters. The Special Parameter hashtag, "$#", reports how many Positional Parameters are currently defined.

$ declare -p foo bar
declare -- foo="abc"
declare -- bar="123"
$ declare -p "$#"
bash: declare: 0: not found
$

For a full string of input at an interactive shell...

$ declare -p #

...the hashtag would be interpreted by Bash as an inline comment. With the comment and all text to the right of it removed, the command that Bash would execute would be declare -p. This command would, according to help declare, "display the values and attributes of each NAME," i.e., each variable, and, "if no NAMEs are given, display the values and attributes and values of all variables," which can be over 100 lines of output.

On the other hand, printf cannot display variables' attributes. See also § Debugging.

$ readonly foo
$ declare -p foo
declare -r foo="abc"
$ printf '<%s>' "${foo}"
<abc>
$

Environment

[edit]

Configurable execution environment(s):[42]

  • Shell and session startup files such as ~/.bashrc and ~/.profile (i.e., dotfiles);
  • Settings (set built-in) and shell options (shopt built-in) which alter shell behavior;

Shell and session startup Files (a.k.a., "dot files")

When Bash starts, it executes the commands in a variety of dot files.[21] Unlike Bash shell scripts, dot files typically have neither the execute permission enabled nor an interpreter directive like #!/bin/bash.

  • Legacy-compatible Bash startup example

The example ~/.bash_profile below is compatible with the Bourne shell and gives semantics similar to csh for the ~/.bashrc and ~/.bash_login. The [ -r filename ] && cmd is a short-circuit evaluation that tests if filename exists and is readable, skipping the part after the && if it is not.

[ -r ~/.profile ] && ~/.profile             # set up environment, once, Bourne-sh syntax only
if [ -n "$PS1" ]; then                      # are we interactive?
   [ -r ~/.bashrc     ] && ~/.bashrc        # tty/prompt/function setup for interactive shells
   [ -r ~/.bash_login ] && ~/.bash_login    # any at-login tasks for login shell only
fi                                          # End of "if" block
  • Operating system issues in Bash startup

Some versions of Unix and Linux contain Bash system startup scripts, generally under the /etc directory. Bash executes these files as part of its standard initialization, but other startup files can read them in a different order than the documented Bash startup sequence. The default content of the root user's files may also have issues, as well as the skeleton files the system provides to new user accounts upon setup. The startup scripts that launch the X window system may also do surprising things with the user's Bash startup scripts in an attempt to set up user-environment variables before launching the window manager. These issues can often be addressed using a ~/.xsession or ~/.xprofile file to read the ~/.profile — which provides the environment variables that Bash shell windows spawned from the window manager need, such as xterm or Gnome Terminal.

Standard streams

[edit]

Standard streams - STDIN, STDOUT and STDERR

Commands

[edit]

System commands

[edit]

Aliases

[edit]

Aliases allow a string to be substituted for a word that is in a position in the input where it can be the first word of a simple command. Aliases have names and corresponding values that are set and unset using the alias and unalias builtin commands.

— GNU Bash Reference Manual, Ch 6.6 Aliases[43][44][45][46][47]

Keywords and reversed words

[edit]
  • function
    • Bash function declarations which include this particular keyword are not compatible with Bourne/Korn/POSIX scripts, however, Bash does accepts the function declaration syntax used by Bourne, Korn and POSIX-compliant shells.

Functions

[edit]

Shell functions are a way to group commands for later execution using a single name for the group. They are executed just like a "regular" simple command. When the name of a shell function is used as a simple command name, the shell executes the list of commands associated with that function name. Shell functions are executed in the current shell context; there is no new process created to interpret them.

—  GNU Bash Reference Manual, Ch 3.3 Shell Functions[48][49][50][51][52][53]

Builtin commands

[edit]
  • Various Built-In Commands:
    • POSIX Special builtins:[54]
      • cd, pwd, etc.
    • set[55]
      • Xtrace: [ set -x | set -o xtrace ]. The shell's primary means of debugging. Both xtrace and verbose can be turned off at the same time with the command set -.
      • Verbose: [ set -v | set -o verbose ]. Prints a command to the terminal as Bash reads it. Bash reads constructs all at once, such as compound commands which include if-fi and case-esac blocks. If a set -v is included within a compound command, then "verbose" will be enabled the next time Bash reads code as input, i.e., after the end of the currently executing construct.[56]
      • Both xtrace and verbose can be turned off at the same time with the command set -.
    • shopt[57]
      • expand-aliases: On by default in interactive shells. Some developers discourage its use in scripts.

PATH and system commands

[edit]

When the shell looks for external commands, it relies on the Bourne shell variable $PATH. $PATH contains a list of directories separated by colons, :. Beginning with the leftmost directory and selecting directories in a left to right pattern, each directory is searched until a match is found. In Linux, so that a user can locate additional commands, it's common practice for distribution administrators and package developers to alter the value of an end user's $PATH by including source files in /etc/profile.d and other locations.

When looking for the command, chmod, for instance, after considering internal commands and finding nothing, Bash will search the directories in $PATH and will select the absolute path of the first executable found that has a basename which matches the search string.[18]

If there is more than one command echo available in the directories listed in $PATH, during the process of parsing and executing a commandline, by default only the first command found will be selected. $PATH lookups are slow. The shell speeds up the commandline execution process by remembering command locations in a hash table. To perform a full $PATH search without any interference from the hash table, remove the current table with hash -r and search for all kinds of commands with type -a.

$ # Force a full path search
$ PATH=${PATH}:${HOME}
$ printf 'echo script_file: "$@"\n' > ./echo
$ chmod 0700 ./echo
$ hash -r; type -a echo
echo is a shell builtin
echo is /usr/bin/echo
echo is /home/liveuser/echo
$

In order to execute a commandline with a command found later in the $PATH string, you can specify an absolute path or you can anchor path resolution relative to the current working directory.

$ /home/liveuser/echo foo
script_file: foo
$ ./echo bar
script_file: bar
$

For security reasons it is advisable to make sure the directories in PATH are not world-writeable, or are writeable only by root and trusted users.

Command lookup

[edit]
  • Command position: after expansions, the first word of the full text of the command line.
  • Command name lookup is performed, in the following order:
  • The resulting string is executed as a command.

Control structures

[edit]

Subshells

[edit]

Subshells: (...);

Pipelines

[edit]

{{Blockquote | However, by using a pipeline, they can engage in multiple cycles of computation at the same time, substantially increasing their speed. In a pipelined control unit, different instructions simultaneously go through the process but at different points. While one instruction is being fetched, a second is being decoded, and so forth. Unix-style pipelines: |.

Logical operators

[edit]
  • AND (&&)
  • OR (||)
  • NOT (!)

Bash supplies "conditional execution" command separators that make execution of a command contingent on the exit code set by a precedent command. For example:

$ cd "$SOMEWHERE" && ./do_something || echo "An error occurred" >&2

Where ./do_something is only executed if the cd (change directory) command was "successful" (returned an exit status of zero) and the echo command would only be executed if either the cd or the ./do_something command return an "error" (non-zero exit status).

Iteration

[edit]

ITERATION: Sometimes programs are repeated indefinitely or until a specific outcome is reached. Each execution of the instructions is an "iteration."[58]

  • while, until, and select loop compound commands;
  • Arithmetic C-style and list-enumerating for loop compound commands; and
  • continue, break, return, and exit flow control commands;

Compound commands

[edit]

compound: something formed by a union of elements or parts.[59]

— Merriam-Webster's Collegiate Dictionary

Bash also supports if ... fi and case ... esac forms of conditional command evaluation.[c]

Testing

[edit]

Built in commands for testing file attributes, comparing string and integer values, etc.:

  • Traditional test command,
  • Traditional single bracket test: [,
  • Modern double bracket test: [[...]], which includes advanced features:
  • ((...)) numeric evaluation and testing; this includes almost all "C" language operators for arithmetic and numeric comparison;

For all commands the exit status is stored in the special variable $?.

Regular Expressions

[edit]

Bash 3.0 supports in-process regular expression matching using a syntax reminiscent of Perl.[61] Regexp matching is limited to strings on the right side of the =~ operator in the [[..]] extended test construct.[62]

[[ $line =~ [[:space:]]*(a)?b ]] means values for line like ‘aab’, ‘ aaaaaab’, ‘xaby’, and ‘ ab’ will all match, as will a line containing a ‘b’ anywhere in its value.

Coprocesses

[edit]

A coprocess is a shell command preceded by the coproc reserved word. A coprocess is executed asynchronously in a subshell, as if the command had been terminated with the ‘&’ control operator, with a two-way pipe established between the executing shell and the coprocess.[63]

— Bash Reference Manual, 3.2.6 Coprocesses

Data manipulation

[edit]

Word Splitting

[edit]

Split into words (i.e., word splitting)

Quoting

[edit]

When in doubt -- Quote![64]

— Mastering Linux Shell Scripting, by Andrew Mallett


Bash has certain quoting rules: uses of

  • single quotes '...'
  • double quotes "..."
  • backslashes \, and
  • ANSI-C quoting $'...'.

See also § Locales, $"..."

See also backticks `...`: § Deprecated syntax.

Unicode

[edit]

Support for Unicode in echo -e and ANSI-C quoting.

Brace Expansion

[edit]
$ echo kernel{,-headers}
kernel kernel-headers

Brace expansion, also called alternation, is a feature copied from the C shell. It generates a set of alternative combinations.[65] Generated results need not exist as files. The results of each expanded string are not sorted and left to right order is preserved:

$ echo a{p,c,d,b}e
ape ace ade abe
$ echo {a,b,c}{d,e,f}
ad ae af bd be bf cd ce cf

Users should not use brace expansions in portable shell scripts, because the Bourne shell does not produce the same output.

$ # bash shell
$/bin/bash -c 'echo a{p,c,d,b}e'
ape ace ade abe
$ # A traditional shell does not produce the same output
$ /bin/sh -c 'echo a{p,c,d,b}e'
a{p,c,d,b}e

When brace expansion is combined with wildcards, the braces are expanded first, and then the resulting wildcards are substituted normally. Hence, a listing of JPEG and PNG images in the current directory could be obtained using:

ls *.{jpg,jpeg,png}    # expands to *.jpg *.jpeg *.png – after which,
                       # the wildcards are processed
echo *.{png,jp{e,}g}   # echo just shows the expansions –
                       # and braces in braces are possible.

In addition to alternation, brace expansion can be used for sequential ranges between two integers or characters separated by double dots. Newer versions of Bash allow a third integer to specify the increment.

$ echo {1..10}
1 2 3 4 5 6 7 8 9 10
$ echo {01..10}
01 02 03 04 05 06 07 08 09 10
$ echo file{1..4}.txt
file1.txt file2.txt file3.txt file4.txt
$ echo {a..e}
a b c d e
$ echo {1..10..3}
1 4 7 10
$ echo {a..j..3}
a d g j

When brace expansion is combined with variable expansion (a.k.a., parameter expansion and parameter substitution) the variable expansion is performed after the brace expansion, which in some cases may necessitate the use of the eval built-in, thus:

$ start=1; end=10
$ echo {$start..$end} # fails to expand due to the evaluation order
{1..10}
$ eval echo {$start..$end} # variable expansion occurs then resulting string is evaluated
1 2 3 4 5 6 7 8 9 10

Tilde Expansion

[edit]

Parameter and variable expansion

[edit]
  • Type
  • Shell parameters
  • Environment variables
  • User variables
  • Scope
  • Arrays
  • Parameter Expansion
    Expansion syntaxes which can perform some tasks more quickly than external utilities, including, among others:
    • Pattern Substitution
      • ${foo//x/y} for sed 's/x/y/g',
    • Remove Matching Prefix or Suffix Pattern
      • ${bar##[a-zA-Z0-9]*} for cut -c8-,
    • Enumerate Array Keys
      • ${!array[@]}, and
    • Display Error if Null or Unset
      • ${var:?error message},

Pathname expansion

[edit]

Pathname expansion, i.e., shell-style globbing and pattern matching using *, ?, [...].[f]

Locales

[edit]

Locale-specific translation via $"..." quoting syntax.[69]

Process redirections and parsing

[edit]

Command substitution

[edit]

Command substitution: $(...),

Process substitution

[edit]

Process substitution, <() or >(), when a system supports it:

Bash supports process substitution using the <(command) and >(command) syntax, which substitutes the output of (or input to) a command where a filename is normally used. (This is implemented through /proc/fd/ unnamed pipes on systems that support that, or via temporary named pipes where necessary).

Arithmetic expansion

[edit]

Arithmetic expansion, ((...)) or $((...)), including

Bash can perform integer calculations ("arithmetic evaluation") without spawning external processes. It uses the ((...)) command and the $((...)) variable syntax for this purpose.

Redirection

[edit]

Redirections of Standard Input, Standard Output and Standard Error data streams are performed, including

  • File writing, >, and appending, >,
  • Here documents, <<,
  • Here strings, <<<, which allow parameters to be used as input, and
  • A redirection operator, >, which can force overwriting of a file when a shell's noclobber setting is enabled;

Its syntax simplifies I/O redirection. For example, it can redirect standard output (stdout) and standard error (stderr) at the same time using the &> operator. This is simpler to type than the Bourne shell equivalent 'command > file 2>&1'. Bash supports here documents. Since version 2.05b Bash can redirect standard input (stdin) from a "here string" using the <<< operator.

Command parsing

[edit]
  • (A) Comments are ignored, from an unquoted # (hash) to the end of the same line;[70][71]
  • (B) Commands are parsed one line at a time:
    • Control structures are honored, and
    • Backslash \ escapes are also honored at the ends of lines;
  • (E) Redirections of Standard Input, Standard Output and Standard Error data streams are performed, including
    • File writing, >, and appending, >>,
    • Here documents, <<,
    • Here strings, <<<, which allow parameters to be used as input, and
    • A redirection operator, >, which can force overwriting of a file when a shell's noclobber setting is enabled;
  • (G) The resulting string is executed as a command.

Interactive-only features

[edit]

Command History

[edit]

Unlimited size command history.[72] This feature is available in interactive mode only.

Directory stack

[edit]

A directory stack (pushd and popd built-ins) feature is available in interactive mode only.

Programmable completion

[edit]

Also known as "tab completion" or "command-line completion", when a user presses the Tab, within an interactive command-shell Bash automatically uses any available completion scripts to suggest partly typed program names, filenames and variable names.[73] [4] The Bash command-line completion system is very flexible and customizable, and is often packaged with functions that complete arguments and filenames for specific programs and tasks.

Bash supports programmable completion via built-in complete, compopt, and compgen commands.[74] The feature has been available since the beta version of 2.04 released in 2000.[75] These commands enable complex and intelligent completion specification for commands (i.e., installed programs), functions, variables, and filenames.[76]

The complete and compopt two commands specify how arguments of some available commands or options are going to be listed in the readline input.As of version 5.1 completion of the command or the option is usually activated by the Tab keystroke after typing its name.[76] This feature is available in interactive mode only.

Prompts

[edit]

Configurable prompts. This feature is available in interactive mode only.

Documentation

[edit]

User Manual

[edit]

A user manual for Bash is provided by the GNU Project. It is sometimes considered to be a more user-friendly document than the man page. "You may also find information about Bash ...by looking at /usr/share/doc/bash, /usr/local/share/doc/bash, or similar directories on your system."[77] On GNU/Linux systems, if the info program is available then the GNU Manual version relevant for your installation should also be available at info bash.[78][79]

Man page

[edit]

The most recent technical manual, or 'man page', is intended to be the authoritative explanatory technical document for the understanding of how bash operates. On GNU/Linux systems, the version relevant for your installation is usually available through the man program at man bash.[78][39][80]

help builtin

[edit]

With recent versions of Bash, information on shell built-in commands can be found by executing help, help [name of builtin] or man builtins at a terminal prompt where bash is installed.

The printf command can be invoked via env to ensure that you run the program found via your shell's search path, and not a shell alias or built-in function: env printf --help.[81]

POSIX Specification

[edit]

For the purpose of allowing inter-operability among different shell programs running on different operating systems, the POSIX Specification influences how modern UNIX-like shells are written. Bash "is intended to be a conformant implementation of the IEEE POSIX "Shell and Utilities" portion of the IEEE POSIX specification (IEEE Standard 1003.1)."[82] The most recent publication of the standard (2024) is available online.[83]

As the standard upon which bash is based, the POSIX Standard, or IEEE Std 1003.1,[84] et seq, is especially informative.

Further resources

[edit]

"The project maintainer also has a Bash page which includes Frequently Asked Questions",[77][85][86] this FAQ is current as of bash version 5.1 and is no longer updated.

Informal avenues of support are available via IRC at libera.chat, in the #bash channel, and mailing lists are available at Bash - GNU Project - Free Software Foundation.

Security and vulnerabilities

[edit]

Root scripts

[edit]

Running any shell scripts as the root user has, for years, been widely criticized as poor security practice. One commonly given reason is that, when a script is executed as root, the negative effects of any bugs in a script would be magnified by root's elevated privileges.

One common example: a script contains the command, rm -rf ${dir}/, but the variable $dir is left undefined. In Linux, if the script was executed by a regular user, the shell would attempt to execute the command rm -rf / as a regular user, and the command would fail. However, if the script was executed by the root user, then the command would likely succeed and the filesystem would be erased.

It is recommended to use sudo on a per-command basis instead.

CGI scripts

[edit]

CGI scripts are a significant source of vulnerability.[87][88][89][clarification needed]

builtin eval

[edit]

"The eval command is extremely powerful and extremely easy to abuse."[90]

Input validation

[edit]

"Input validation is the process of ensuring data has undergone data cleansing to confirm it has data quality, that is, that it is both correct and useful."

Input validation is performed to ensure only properly formed data is entering the workflow in an information system, preventing malformed data from persisting in the database and triggering malfunction of various downstream components. Input validation should happen as early as possible in the data flow, preferably as soon as the data is received from the external party.[91]

— OWASP Input Validation Cheat Sheet

Shellshock

[edit]

In September 2014, a security bug was discovered[92] in the program. It was dubbed "Shellshock." Public disclosure quickly led to a range of attacks across the Internet.[93][94][95]

Exploitation of the vulnerability could enable arbitrary code execution in CGI scripts executable by certain versions of Bash. The bug involved how Bash passed function definitions to subshells through environment variables.[96] The bug had been present in the source code since August 1989 (version 1.03)[97] and was patched in September 2014 (version 4.3).

Patches to fix the bugs were made available soon after the bugs were identified. Upgrading to a current version is strongly advised.

It was assigned the Common Vulnerability identifiers CVE-2014-6271, CVE-2014-6277 and CVE-2014-7169, among others. Under CVSS Metrics 2.x and 3.x, the bug is regarded as "high" and "critical", respectively.

Deprecated syntax

[edit]
  • Backtick style command substitutions: `...` is deprecated in favor of
    • $(...);
  • Use of -a or -o in test/[/[[ commands,
    • for example, [ -r ./file -a ! -l ./file ] is deprecated in favor of
      • [ -r ./file ] && ! [ -l ./file ];
  • Use of the arithmetic syntax $[...] is deprecated in favor of
    • $((...)) or
    • ((...)), as appropriate;
  • Use of ^ as a pipeline is deprecated in favor of |;
  • Any uses of expr or let.

Debugging

[edit]

Table of Features

[edit]
Bash features which can be useful during debugging.[4][57][98]
Feature POSIX 2024 Description Bash ver.
Grammar type Formal name Syntax
Special Built-In Utility set / xtrace set -x Yes The shell's primary means of debugging.

It "writes to standard error a trace for each command after it expands the command and before it executes it."

?
Special Parameters Exit Status "$?" Yes "Expands to the shortest representation of the decimal exit status." ?
Parameter Expansions Indicate Null or Unset "${parameter:?[word]}" Yes "Where the expansion of [word], perhaps an error message or a line number, is written to standard error and the shell exits with a non-zero exit code." ?
Special Parameters PID of Invoked Shell "$$" Yes "Expands to the shortest representation of the decimal process ID of the invoked shell." ?
Special Built-In Utility set / verbose set -v Yes "Writes its input to standard error as it is read." ?
Special Built-In Utility set / pipefail set -o pipefail Yes "Derive the exit status of a pipeline from the exit statuses of all of the commands in the pipeline, not just the last (rightmost) command." ?
Special Built-In Utility set / nounset set -u Yes When enabled, will cause the shell to exit with an error message when it encounters an unset variable expansion.

Its use has a number of counter-intuitive pitfalls.

?
Special Built-In Utility set / errexit set -e Yes Errexit is a setting that, when enabled, will, under certain very specific conditions, cause the shell to exit without an error message whenever the shell receives a non-zero exit code.

Its use is somewhat controversial, to the extent that any somewhat obscure computer program can be controversial. Adherents claim that Errexit provides an assurance of verifiability in situations where shell scripts "must not fail." However, opponents claim that its use is unreliable, deceptively simple, highly counter-intuitive, rife with gotchas and pitfalls, and in essence "security theater." Numerous developers of Bash have strongly discouraged the use of this particular setting.

?
Special Built-In Utility trap / EXIT trap '[arg]' EXIT Yes "If a signal specifier is 0 or EXIT, [arg] is executed when the shell exits." If [arg] contains expansions, then [arg] should be in single quotes. ?
Utility printf printf '<%s>\n' "${var}" Yes A means of reliably printing the contents of a variable. ?
Bash Variables BASHPID "${BASHPID}" No "Expands to the process ID of the current bash process."[99] ?
Bash Variables BASH_ARGC "${BASH_ARGC[@]}" No "An array variable whose values are the number of parameters in each frame of the current bash execution call stack."[100] ?
Bash Variables BASH_ARGV "${BASH_ARGV[@]}" No "An array variable containing all of the parameters in the current bash execution call stack."[101] ?
Bash Variables BASH_LINENO "${BASH_LINENO[@]}" No "An array variable whose members are the line numbers in source files where each corresponding member of "${FUNCNAME[@]}" was invoked."[102] ?
Bash Variables BASH_REMATCH "${BASH_REMATCH[@]}" No "An array variable whose members are assigned by the =~ binary operator to the [[ conditional command."[103] ?
Bash Variables BASH_SOURCE "${BASH_SOURCE[@]}" No "An array variable whose members are the source filenames where the corresponding shell function names in the "${FUNCNAME[@]}" array variable are defined."[104] ?
Bash Variables BASH_XTRACEFD "${BASH_XTRACEFD}" No "If set to an integer corresponding to a valid file descriptor, Bash will write the trace output generated when set -x is enabled to that file descriptor."[105] ?
Bash Variables EPOCHREALTIME "${EPOCHREALTIME}" No "Each time this parameter is referenced, it expands to the number of seconds since the Unix Epoch (see time(3)) as a floating point value with micro-second granularity."[106] ?
Bash Variables FUNCNAME "${FUNCNAME[@]}" No "An array variable containing the names of all shell functions currently in the execution call stack."[107] ?
Bash Variables LINENO "${LINENO}" No "Each time this parameter is referenced, the shell substitutes a decimal number representing the current sequential line number (starting with 1) within a script or function."[108] ?
Bash Variables PIPESTATUS "${PIPESTATUS[@]}" No "An array variable containing a list of exit status values from the processes in the most-recently-executed foreground pipeline (which may contain only a single command)."[109] ?
Bash Variables PPID "${PPID}" No "The process ID of the shell's parent."[110] ?
Bash Variables PS4 "${PS4}" No "The value of this parameter is expanded as with PS1 and the value is printed before each command bash displays during an execution trace."[111] ?
Shell Builtin set / restricted set -r No Restricted mode is intended to improve the security of an individual shell instance from a malicious human with physical access to a machine.

As threat models have changed, it has become less commonly used now than it once was.

?
Shell Builtin shopt / extdebug shopt -s extdebug No "Behavior intended for use by debuggers." ?
Shell Builtin trap / DEBUG trap '[arg]' DEBUG No "If a sigspec is DEBUG, the command arg is executed before" certain kinds of commands. ?
Shell Builtin trap / ERR trap '[arg]' ERR No "If a sigspec is ERR, the command arg is executed whenever..." certain kinds of commands "return a non-zero exit status", subject to similar restrictions as with ErrExit. ?
Shell Builtin trap / RETURN trap '[arg]' RETURN No "If a sigspec is RETURN, the command arg is executed each time a shell function or a script executed with the . or source builtins finishes executing." ?
  • Shell features specified by POSIX:
    • Parameter Expansions:[112]
    • Special Parameters:[113][114]
    • Special Built-In Utility set:[115][116]
    • Special Built-In Utility trap:[116][117]
      • POSIX does specify certain uses of the trap builtin: ...
    • Utility printf: a means of reliably printing the contents of a variable:
  • Bash features not specified by POSIX:
    • Bash Variables:[118][119]
    • Shell Builtin set:[115][116]
    • Shell Builtin shopt:[120][116]
    • Shell Builtin trap:[117][116]
      • While POSIX does specify certain uses of the trap builtin, the following signal specs are Bash extensions: ...
  • Third party debugging utilities:
    • ShellCheck: Shell script analysis tool;[121][27]
    • devscripts-checkbashisms: Check whether a /bin/sh script contains any common bash-specific constructs;[122][26]
    • kcov: Code coverage tool without special compilation options;[123]
    • Bashdb: The Bash symbolic debugger.[31][124]

Examples

[edit]

With the "${var:?}" parameter expansion, an unset or null variable can halt a script.

$ cat ex.sh
#!/bin/bash
bar="foo is not defined"
echo "${foo:?$bar}"
echo this message doesn't print

$ ./ex.sh
./ex.sh: line 3: foo: foo is not defined
$

Reliably printing the contents of an array that contains spaces and newlines first in a portable syntax, and then the same thing in Bash. Note that POSIX doesn't have named array, only the list of arguments, "$@", which can be re-set by the set builtin.

$ # In POSIX shell:
$ set -- "a" " b" " 
>  c "
$ printf ',%s,\n' "$@"
,a,
, b,
,
 c,

Note that in Bash, the number of spaces before the newline is made clear.

$ # In Bash:
$ array=( "a" " b" " 
>  c " )
$ declare -p array
declare -a array=([0]="a" [1]=" b" [2]=$' \n c ')

Printing an error message when there's a problem.

$ cat error.sh
#!/bin/env bash
if ! lsblk | grep sdb
then
  echo Error, line "${LINENO}"
fi
$ ./error.sh
Error, line 130

Using xtrace. If errexit had been enabled, then echo quux would not have been executed.

$ cat test.sh
#!/bin/env bash
set -x
foo=bar; echo "${foo}"
false
echo quux
$ ./test.sh
+ foo=bar
+ echo bar
bar
+ false
+ echo quux
quux

Note: $BASHPID differs from $$ in certain circumstances, such as subshells that do not require bash to be reinitialized.

$ echo $(echo $BASHPID $$)   $$    $BASHPID
              25680    16920 16920 16920
#             |        |     |     |
#             |        |     |     \-- $BASHPID outside of the subshell
#             |        |     \-- $$ outside of the subshell
#             |        \-- $$ inside of the subshell
#             \-- $BASHPID inside of the subshell

Bug reporting

[edit]

An external command called bashbug reports Bash shell bugs. When the command is invoked, it brings up the user's default editor with a form to fill in. The form is mailed to the Bash maintainers (or optionally to other email addresses).[125][126]

History

[edit]

Shell script functionality originated with files called "runcoms" in reference to the 1963 macro processor of the same name. The suffix "rc" is short for "runcom."[127] The term "shell" was coined by Louis Pouzin in 1964 or 1965, and appeared in his 1965 paper, "The SHELL, A Global Tool for Calling and Chaining Procedures in the System," which describes many features later found in many UNIX shells.[128][129] The ASCII standard for character encoding was defined in 1969 in a document called Request for Comments (RFC) 20.[130]

Timeline

[edit]

Significant events in Bash history are listed below:

See also

[edit]

Unix shells

[edit]

Graphical interface to scripts

[edit]

There are many programs that allow you to create a graphical interface for shell scripts.

  • curses - curses is a terminal control library for Unix-like systems, enabling the construction of text user interfaces (TUI) applications.
  • dialog - is a utility that allows you to create dialog boxes in the console, using the curses and ncurses libraries.
  • gtkdialog - is the most functional utility for creating graphical applications on bash scripts.[166]
  • kdialog - is a KDE equivalent of zenity.[167]
  • whiptail - is an analogue of the dialog utility, it uses the newt library.[168]
  • xdialog - is a replacement for dialog that is designed to give programs launched from the terminal an X Window System interface.
  • yad - is a fork of zenity, with more features.[169]
  • zenity - is the most popular application for creating a graphical interface for scripts.[167][170]

Further reading

[edit]

Notes

[edit]

References

[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
Bash (Bourne Again SHell) is a and command language interpreter that serves as the default shell for operating system and many distributions. Developed by Brian Fox in 1989 as part of the GNU Project, it was created as a replacement for the original (sh), incorporating features from the Korn shell (ksh) and (csh) while maintaining compatibility with the standard (IEEE 1003.1). Released under the GNU General Public License, Bash is portable across nearly all versions of Unix and systems, including macOS (with zsh as the default shell since 2019) and Windows via subsystems like . As both an interactive login shell and a , Bash enables users to execute commands, manage files, and automate tasks through built-in features such as command-line editing, job control, command history, variable expansions, redirections, pipelines, and programmable completion. Its syntax supports flow control structures like loops and conditionals, making it suitable for writing complex scripts that interface with utilities. The latest stable version, 5.3, was released on July 5, 2025, with ongoing maintenance by Ramey. Widely adopted for its compliance and extensibility, Bash remains a of command-line interfaces in open-source environments, powering everything from simple command execution to advanced administration.

Fundamentals

Definition and Purpose

Bash, or the Bourne-Again SHell, is a and command language interpreter developed by the Project as a replacement for the original (). It serves as the default shell for the GNU operating system, providing an enhanced, POSIX-compatible environment for executing commands and scripts. The primary purpose of Bash is to interpret commands entered interactively by users or read from script files, enabling a (CLI) for system administration, task automation, and shell programming. It allows users to combine GNU utilities through scripting features, facilitating efficient control over processes, file operations, and system resources. In both interactive and non-interactive modes, Bash processes input from standard input or files, supporting synchronous or asynchronous command execution with redirection capabilities. Key characteristics of Bash include its compliance with the IEEE POSIX Shell and Utilities standard (IEEE 1003.1), augmented by extensions for advanced functionality, and its integration with the GNU Readline library for command-line editing and history management. Unlike the more limited , Bash offers greater extensibility through features borrowed from other shells like ksh and csh, making it suitable for complex scripting. Bash's basic workflow involves reading user or script input, splitting it into tokens (words and operators), performing expansions such as parameter and filename substitution, parsing the command structure, and executing the resulting commands while managing input/output streams and process exit statuses. Bash is the default shell on most Linux distributions, including and , due to its widespread adoption and compatibility. On macOS, it served as the default shell until 2019, when Apple transitioned to zsh starting with . This popularity underscores Bash's role as a versatile, extensible alternative to the POSIX-minimal sh, balancing standards adherence with practical enhancements for everyday use.

History and Development

Bash was developed in 1989 by Brian Fox as part of the GNU Project, aimed at providing a free and open-source implementation of the shell standard to replace the (sh), which was restricted by licensing constraints. The initial release occurred on June 8, 1989, marking the beginning of Bash as the default shell for the GNU operating system. Fox, the first employee of the , designed Bash to be compatible with sh while extending its capabilities for interactive use and scripting. The primary maintenance transitioned to Chet Ramey in 1992, who has served as the lead developer and maintainer since then, overseeing evolution through bug fixes, feature additions, and compliance updates as of 2025. Bash draws its core syntax from the but incorporates interactive features from the (csh), such as command history and editing, and advanced scripting elements from the Korn shell (ksh), including job control and arrays. This blend made Bash highly versatile, leading to its widespread adoption as the default login shell in distributions during the and as the standard shell in macOS from its early versions until in 2019, when it was replaced by zsh due to licensing and feature considerations. Bash's development is managed under the GNU Project through the Savannah hosting platform, where contributions are coordinated via mailing lists and , with releases focusing on compliance, security enhancements, and new functionalities like coprocesses introduced in version 4.0. Updates are distributed via official tarballs, incorporating community-reported fixes and standards alignment, ensuring portability across Unix-like systems.
VersionRelease DateMajor Additions
2.0December 23, 1996Improved readline integration for command-line editing, unlimited history.
3.0July 27, 2004Indexed arrays, programmable command completion.
4.0February 23, 2009Associative arrays, coprocesses, enhanced debugging.
5.0January 7, 2019New shell variables (EPOCHSECONDS, EPOCHREALTIME), nameref improvements.
5.1December 7, 2020PROMPT_COMMAND as array, SRANDOM variable, wait -p option.
5.2September 26, 2022Security fixes for vulnerabilities, minor scripting improvements.
5.3July 5, 2025New command substitution syntax, glob sorting options, enhanced error reporting.

Shell Environment

Startup and Configuration Files

When Bash starts, it reads specific configuration files to initialize the shell environment, with the sequence depending on whether the shell is a shell, an interactive non- shell, or non-interactive. These files allow system administrators and users to set environment variables, define aliases, and configure shell behavior. The process ensures that global settings are applied before user-specific customizations, promoting consistency across sessions. For interactive login shells, which occur when a user logs in via a terminal or remote session, Bash first sources the system-wide /etc/profile file if it exists and is readable. This file typically sets global environment variables such as PATH and umask. Next, Bash attempts to source one of the user-specific profile files in this order: ~/.bash_profile, ~/.bash_login, or ~/.profile, stopping at the first readable file. The ~/.bash_profile is preferred for Bash-specific settings, while ~/.profile provides compatibility with other POSIX shells. These files often export variables like PATH (e.g., export PATH="$PATH:/usr/local/bin") and set permissions with umask 022 to control default file creation modes. Interactive non-login shells, common in graphical terminal emulators, source different files to focus on session-specific configurations. Bash first checks for and sources /etc/bash.bashrc if it exists, a system-wide file for interactive settings on some distributions. Then, it sources the user-specific ~/.bashrc, which is ideal for defining aliases, shell functions, and prompt customizations without affecting login environments. To ensure consistency, ~/.bash_profile in login shells often includes a conditional statement to source ~/.bashrc, such as:

if [[ -n "&#36;PS1" ]]; then if [ -f ~/.bashrc ]; then . ~/.bashrc; fi fi

if [[ -n "&#36;PS1" ]]; then if [ -f ~/.bashrc ]; then . ~/.bashrc; fi fi

This uses the $PS1 variable, which is set for interactive shells, to detect and load interactive configurations only when appropriate. Non-interactive shells, such as those running scripts, do not source startup files by default to avoid unnecessary overhead. However, if the BASH_ENV is set to a , Bash sources that file before executing the script, allowing scripted environments to be customized. The sourcing order and file availability can vary across systems. On distributions like those using , graphical terminals typically launch non-login interactive shells, directly sourcing ~/.bashrc. In contrast, macOS Terminal.app defaults to login shells, prioritizing ~/.bash_profile and requiring explicit sourcing of ~/.bashrc for interactive features; this stems from macOS's BSD heritage influencing shell invocation. The /etc/bash.bashrc file, for instance, is commonly present on Debian-based systems but absent or unused on macOS. Bash invocation options allow overriding this behavior. The --login flag forces login shell processing, sourcing profile files regardless of context. Conversely, --noprofile skips /etc/profile and user profile files, while --norc prevents sourcing of /etc/bash.bashrc and ~/.bashrc, useful for clean script execution or debugging. If a file exists but cannot be read, Bash reports an (unless invoked with --norc or similar). These mechanisms enable precise control over environment initialization.

Environment Variables and Parameters

In Bash, shell parameters encompass both named variables, which store values assigned via simple statements like name=value, and special parameters that provide predefined information about the shell's state or execution context. Variables are created and modified through assignment, and their attributes—such as locality, immutability, or exportability—can be specified using built-in commands like declare, local, readonly, or export. These mechanisms allow precise control over data storage and accessibility within scripts and interactive sessions. Variable declaration in Bash supports scoping and inheritance attributes. By default, variables are global, meaning they are visible throughout the shell's execution environment unless overridden. The local keyword, used within functions, declares a variable that is confined to the function's scope and its child processes, preventing interference with outer variables of the same name through dynamic scoping. The readonly attribute renders a variable immutable, prohibiting subsequent assignments or unset operations once set, which is useful for defining constants like configuration flags. Exporting a variable with export or declare -x marks it for inheritance by child processes, ensuring it becomes part of the environment passed to executed commands or subshells. Special parameters in Bash provide read-only access to runtime information without explicit declaration. The parameter &#36;0 holds the name of the shell or the invoking script. Positional parameters &#36;1 through &#36;9 capture the first nine command-line arguments passed to the script or function, with higher numbers accessible via shift or indirect . The parameter $# reports the number of positional parameters, while $? yields the exit status (0 for success, non-zero for failure) of the most recent command. The parameters $@ and $* represent all positional parameters, with $@ treating them as separate words (especially when quoted) and $* as a single word; $PPID gives the process ID of the shell's parent, and $! the process ID of the most recent background job. These parameters are essential for scripting logic, such as error handling or argument processing. The shell's environment consists of exported variables inherited from the parent process upon invocation, forming an array of name=value pairs passed to child processes. Only exported variables are included in this inheritance; non-exported ones remain local to the current shell. The env utility allows viewing or modifying this environment before invoking a command, such as by setting temporary variables or clearing the environment entirely with env -i. Unsetting a variable with the unset command removes it from the current shell and, if exported, from the environment passed to future children; however, unsetting critical variables like PATH or HOME can disrupt command resolution or user directory access, respectively. Bash supports internationalization through locale-related environment variables, which influence behavior like message formatting and . The LANG variable sets the default locale category, while LC_* variables (e.g., LC_MESSAGES for message language, LC_COLLATE for sorting order) override specific aspects; LC_ALL takes precedence to set all categories uniformly. These variables, when exported, ensure child processes adhere to the desired locale settings for consistent output across diverse systems.

Standard Input, Output, and Error Streams

Bash employs three primary streams for handling input and output during command execution, adhering to Unix conventions: standard input (stdin), linked to 0; standard output (stdout), 1; and standard error (stderr), 2. Stdin provides the default source of data for commands requiring input, such as reading user prompts or file contents, while stdout captures normal program output, like results or logs, and stderr directs error messages and diagnostics to ensure they remain distinguishable from regular output. These streams enable seamless integration in pipelines, where the stdout of one command feeds into the stdin of the next. In interactive Bash sessions, stdin defaults to the keyboard or terminal device, permitting direct user input, whereas both stdout and stderr are routed to the terminal for immediate display, facilitating real-time feedback during command execution. This setup supports typical interactive workflows, such as entering commands and viewing responses on the console. For non-interactive executions, such as in scripts or background es, stdin is commonly derived from the script file itself, an argument-supplied source, or /dev/null if no explicit input is provided, while stdout may connect to a pipe, file, or inheriting , and stderr typically remains directed to the terminal unless redirected. This configuration allows scripts to process batch data without user intervention, with output potentially captured for further processing or logging. Basic manipulation of these streams is achieved through redirection operators; for example, command > file diverts stdout to a specified file (overwriting if it exists), and command 2> error.log sends stderr to a separate file for error isolation (full redirection syntax is covered in the Command Execution section). Here documents provide a mechanism to supply multi-line content directly to a command's stdin using the << operator, enabling inline data or scripts without external files. The syntax reads lines from the current input until encountering a delimiter, as in:

cat << EOF Line one of input. Line two with variables expanded if quoted differently. EOF

cat << EOF Line one of input. Line two with variables expanded if quoted differently. EOF

This expands to the content between the markers, with optional quoting of the delimiter to control variable and command substitution. File descriptor duplication allows reassignment or copying of streams within the shell environment using the exec builtin, such as exec 3>&1, which duplicates the current stdout (fd 1) to a new fd 3 for independent access later in the session without altering the original. This technique supports complex I/O routing while preserving default behaviors.

Syntax Basics

Tokens, Words, and Basic Syntax

In Bash, the fundamental units of syntax are , which consist of words and operators derived from the shell's input stream. A token is defined as a sequence of characters treated as a single unit by the shell, encompassing either a word or an operator. The shell reads input—whether from a terminal, script file, or command string—and breaks it into these tokens by identifying metacharacters that separate them. Metacharacters include space, tab, newline, and unquoted symbols such as |, &, ;, (, ), <, >, and others like *, ?, [, which have special meanings unless quoted. Words form the core of commands and arguments, representing sequences of non-metacharacter characters or quoted metacharacters that the shell treats as cohesive units. For instance, in the command ls -l, the shell tokenizes it into two words: ls (the command) and -l (an argument), separated by whitespace. Operators, on the other hand, are tokens containing one or more unquoted metacharacters that control command execution, such as the pipe | for connecting commands or > for output redirection. These operators enable constructs like pipelines (cmd1 | cmd2) and command lists separated by ;, &, or newlines, without requiring semicolons for individual commands. Basic syntax in Bash follows a structure where a simple command comprises an optional list of variable assignments, followed by words (the command name and its arguments), and optional redirections or other operators. Commands are executed sequentially unless modified by operators; for example, [echo](/page/Echo) hello > file.txt directs output to a file using the redirection operator. Reserved words, a subset of words with syntactic significance, include flow-control terms like if, then, [else](/page/If-Then-Else), [fi](/page/If-Then-Else), for, do, done, while, until, case, [esac](/page/If-Then-Else), and {, }, which must appear unquoted in specific grammatical contexts to trigger their special behavior, distinguishing them from ordinary commands or built-ins like alias. The process begins with initial tokenization, where the shell divides input into words and operators while discarding comments (lines or parts starting with #). This precedes further phases, including expansions (handled separately) and command execution, ensuring that metacharacters like * or ? are recognized only if unquoted during token formation. For a command like [cat](/page/Cat) file | [grep](/page/Grep) error, tokenization yields words [cat](/page/Cat), file, [grep](/page/Grep), error and the operator |, forming a structure.

Quoting and Escaping

In Bash, quoting mechanisms prevent the shell from interpreting metacharacters, expansions, and special constructs, thereby preserving the literal value of characters or words in commands. These include the backslash escape character, single quotes, double quotes, and ANSI-C quoting forms. Quoting is essential for handling spaces, variables, and special symbols without unintended splitting or substitution. Single quotes ('...') treat all enclosed characters literally, suppressing all forms of expansion, including parameter expansion, command substitution, arithmetic expansion, and process substitution. No special characters, such as the dollar sign ($), backtick (`), or backslash (\), retain their meaning inside single quotes. For instance, the command echo '$HOME is $PATH' outputs the literal string $HOME is $PATH rather than expanding the variables. To embed a single quote within a single-quoted string, the quote must be closed, the literal single quote escaped with a backslash, and then reopened, as in echo 'It'\''s a test', which outputs It's a test. Double quotes ("...") preserve the literal value of most characters while permitting limited expansions, such as parameter and variable expansion ($var), command substitution ($(command) or `command`), arithmetic expansion ($(())), and history expansion (!). However, they prevent word splitting and pathname expansion (globbing) on the expanded results, treating the content as a single word. Special characters like the backslash (\) inside double quotes escape only the dollar sign, backtick, double quote, backslash, and newline. For example, echo "The date is $(date)" expands the command substitution to output the current date, while echo "It's a test" treats the apostrophe literally without affecting the expansion. This makes double quotes suitable for mixed literal and expanded content, such as echo "User: $USER, home: $HOME". The (\) serves as an when unquoted, preserving the literal value of the immediately following character, including metacharacters like $, `, \, or spaces. Inside double quotes, it escapes only specific characters: $, `, ", \, and (which continues the line). Unquoted backslashes at the end of a line are removed after quoting rules are applied. For instance, [echo](/page/Echo) \$HOME outputs $HOME literally, and echo "Path: \$PATH" outputs Path: $PATH. Backslashes do not escape characters within single quotes, where they are treated literally. ANSI-C quoting provides advanced literal preservation with escape interpretation. The form $'...' treats the content as a single-quoted string but interprets backslash-escaped sequences according to the ANSI C standard, such as \n for newline, \t for tab, \r for carriage return, \\ for backslash, octal escapes (\nnn), hexadecimal (\xHH), and Unicode (\uHHHH or \UHHHHHHHH). For example, echo $'Hello\nWorld' outputs Hello followed by a newline and World. This form is useful for embedding control characters without external tools. Separately, $"..." enables locale-specific translation, expanding the string to its translated equivalent if a matching message catalog entry exists, while otherwise behaving like double quotes. For instance, echo $"Hello, world" might output a localized greeting based on the current locale. Nested quoting combines these mechanisms to handle complex strings. Double quotes often enclose single-quoted literals or escaped elements, as in echo "It'\''s $(date): $USER", which outputs It's [current date]: [username] by using single quotes for the apostrophe and allowing expansions for the date and user. Single quotes cannot directly nest within themselves but can be simulated via the escape technique mentioned earlier. A common pitfall arises from omitting quotes around variable expansions, which triggers word splitting on the results using the internal field separator ($IFS, defaulting to , tab, and ). For example, if files="a b c", the unquoted loop for f in $files; do [echo](/page/Echo) "$f"; done splits into three iterations (a, b, c), potentially causing errors with filenames containing . Quoting as for f in "$files"; do [echo](/page/Echo) "$f"; done treats the value as a single item, preserving and avoiding unintended splitting. This issue is particularly problematic in scripts handling user input or dynamic data.

Types of Expansions

Bash performs several types of expansions on the words in a command line after tokenization but before execution, transforming the input into the actual arguments passed to commands. The precise order of these expansions is crucial for predictable behavior, as they are applied sequentially from left to right within words. This sequence ensures that later expansions operate on the results of earlier ones, and it is defined in the GNU Bash Reference Manual as: brace expansion first, followed by tilde expansion, and variable expansion, arithmetic expansion, and command substitution (all in a left-to-right fashion); then word splitting; pathname expansion; and finally quote removal. Brace expansion generates multiple strings from a enclosed in curly braces, such as {a,b} producing a and b, or {1..3} yielding 1 2 3; it is a Bash-specific extension not present in the standard shell. Tilde expansion replaces ~ at the start of a word with the path, either the current user's (~) or a specified user's (~username), defaulting to the value of the $HOME if unset. and variable expansion substitutes the value of variables or parameters, using forms like $var, ${var}, or special parameters like $? for the of the last command. Arithmetic expansion evaluates integer expressions within $(( )), performing calculations such as $((2 + 3)) resulting in 5. Command substitution executes a command and replaces $(command) or the older `command` with its standard output, trimming trailing newlines. Following these initial expansions, word splitting divides the resulting words into fields using the Internal Field Separator (IFS), which defaults to space, tab, and newline, though unquoted expansions like $var trigger this while quoted ones do not. Pathname expansion, also known as globbing, matches patterns like *, ?, or [abc] against existing filenames in the current directory, replacing the pattern with a list of matching paths; if no matches are found, the original word is retained unless the nullglob option is set. Finally, quote removal strips unescaped double quotes, single quotes, and backslashes from the words after all other expansions, ensuring the final arguments are clean. Expansions occur after the shell has tokenized the input into words and operators but before any command execution, allowing the shell to interpret dynamic content like variables within the command line. Nested expansions are supported and processed from innermost to outermost; for example, echo $(echo ${[HOSTNAME](/page/Hostname)}) first expands ${HOSTNAME} to the machine's name during the inner command substitution, then uses that output in the outer one. To disable specific expansions, quoting prevents most types—such as double quotes around $var inhibiting word splitting and pathname expansion—while the set -f option (or set -o noglob) globally turns off pathname expansion without affecting other types. The set -u option can treat unset variables as errors during parameter expansion. The order of expansions in Bash closely aligns with the standardization in IEEE Std 1003.1-2017 (.1), which specifies , , arithmetic, command substitution, word splitting, pathname expansion, and quote removal, but Bash prepends brace expansion as an extension to enhance scripting flexibility. This POSIX foundation ensures portability across systems, though Bash's additions like brace expansion and extended support provide advanced features beyond the base standard.

Command Execution

Command Lookup and PATH

Bash performs command lookup by searching for the specified command name in a specific order to determine how to execute it. If the command name contains no slashes, Bash first checks for an alias matching the name. If no alias is found, it then looks for a shell function by that name. Next, it searches for a built-in command. If the command is not a built-in, Bash consults its internal for a cached full pathname of an executable file. If the command is not found in the hash table, Bash searches the directories listed in the PATH in sequence until it locates an executable file matching the name; the first match is executed. If no match is found after exhausting these steps, Bash typically reports an error, unless a command_not_found_handle function is defined to handle the case. The PATH is a colon-separated list of directory paths that Bash uses to locate external files when the command name does not include slashes. For example, a PATH value like /usr/local/bin:/usr/bin:/bin directs Bash to search first in /usr/local/bin, then /usr/bin, and finally /bin, executing the first found with the matching name. This mechanism allows users to run programs without specifying their full paths, but the order of directories determines execution priority. To optimize repeated lookups, Bash maintains an internal that caches the full pathnames of previously executed external commands. Upon successful execution of an external command, its full path is automatically added to the . Before searching PATH, Bash checks this table; if an entry exists, it uses the cached path directly, avoiding a full directory traversal. The hash built-in command manages this table: hash without arguments lists its contents, hash -p /full/path command associates a specific path with a command name, and hash -r clears all entries, forcing future lookups to re-search PATH. This caching improves performance in interactive sessions or scripts with frequent command invocations. Commands with slashes in their names bypass the standard lookup order and are treated as file paths. An absolute path, such as /bin/ls, specifies the exact location and is executed directly if the file exists and is . A relative path, like ./script or subdir/command, is resolved starting from the current . These paths do not consult aliases, functions, builtins, the , or PATH, providing a way to invoke scripts or binaries outside the standard search mechanism. Modifying PATH insecurely, such as prepending user-writable directories like the current directory (.), can introduce security risks. An attacker with write access to those directories could place a malicious with the same name as a command, causing Bash to execute the trojan instead of the legitimate program during lookup. This path interception technique has been documented as a persistence and vector in systems. To locate executables without executing them, users can employ the which and whereis utilities. The which command, often implemented as a Bash built-in or external program, searches PATH and returns the full path of the first matching executable; for instance, which ls might output /bin/ls. The whereis command, a separate utility, searches a predefined set of standard directories (including PATH, manual pages, and source paths) and reports locations of the binary, source files, and manual pages for the command, such as whereis gcc showing /usr/bin/gcc /usr/share/man/man1/gcc.1.gz. These tools aid in debugging PATH issues or verifying command installations.

Built-in Commands

Built-in commands in Bash are commands implemented directly within the shell's binary, rather than as standalone executable programs in the file system. This internal implementation allows them to execute more rapidly, as they avoid the overhead of forking a new process and performing an exec system call that external commands require. Additionally, built-ins have direct access to the shell's internal state, enabling operations that manipulate the environment or control flow in ways that would be inefficient or impossible with external utilities. Common built-in commands handle essential tasks such as directory navigation, input/output operations, conditional testing, and signal management. For instance, cd changes the current working directory and updates the shell's internal notion of the current path, while pwd prints the current working directory by accessing the shell's state directly. Output commands include echo, which writes its arguments to standard output with options for newline suppression (-n) and escape sequence interpretation (-e), and printf, which provides formatted output based on a format string, supporting Bash-specific specifiers like %q for quoted strings and %T for timestamps. Input is managed by read, which reads a line from standard input into shell variables, and mapfile (also known as readarray), which loads lines into an array. Conditional evaluation uses test (or its synonym [) for basic tests like file existence or string comparisons, and the Bash-specific [[ for extended tests including pattern matching and arithmetic without forking. Signal handling is provided by trap, which specifies commands to execute upon receipt of signals. Resource usage is reported by times, which displays the user and system times accumulated by the shell and its child processes. File inclusion is achieved with source (or its synonym .), which executes commands from a specified file in the current execution environment, a feature particularly useful for loading configuration or functions. In contrast to external commands, Bash built-ins like true (which always returns an exit status of 0) and false (which returns a non-zero status) execute instantaneously without process creation overhead, making them preferable in scripts for control flow where speed and shell integration matter. External versions of these, such as /bin/true, incur unnecessary costs and do not interact as seamlessly with the shell's state. Built-ins can be temporarily disabled using the enable -n command, which removes them from the shell's command lookup, allowing external commands with the same name to take precedence; they can be re-enabled with enable without arguments. To list all built-ins, the enable -p or help commands display them, providing a way to inspect available internals. The following table groups Bash's built-in commands by primary function, with brief descriptions; this is not an exhaustive list of all options but representative of core capabilities:
CategoryBuilt-in CommandsDescription
Directory Managementcd, pwdcd changes the working directory; pwd prints it.
Input/Outputecho, printf, read, mapfileecho and printf handle output; read and mapfile manage input to variables or arrays.
Conditionalstest, [, [[Evaluate expressions for file, string, or arithmetic conditions; [[ is Bash-enhanced.
Shell Controlsource (.), trap, times, true, falsesource includes files; trap manages signals; times reports usage; true/false set exit statuses.
Variable Managementdeclare, local, exportdeclare sets attributes; local scopes variables in functions; export makes them environment variables.
Job Controlbg, fg, jobsManage background jobs and foreground processes.
Otheralias, bind, history, typealias defines shortcuts; bind configures key bindings; history views command history; type identifies command types.
Bash-specific built-ins, such as mapfile for array input and enhanced printf formats, extend POSIX standards to provide more powerful scripting features directly within the shell.

Redirections and File Descriptors

In Bash, redirections allow commands to read input from and write output to files or other sources, manipulating the three standard file descriptors: standard input (file descriptor 0), standard output (file descriptor 1), and standard error (file descriptor 2). These operations enable flexible input/output (I/O) handling, such as saving command output to a file or providing input from a string. File descriptors are non-negative integers representing open files or streams, with Bash supporting descriptors beyond the standard three for advanced use cases. Basic redirection operators include >, which redirects standard output to a file, truncating the file if it exists; >>, which appends standard output to a file without truncation; <, which redirects standard input from a file; and 2>, which redirects standard error to a file, truncating it. For example, the command ls > output.txt writes the directory listing to output.txt, overwriting any existing content, while ls >> output.txt adds to the file. Similarly, command < input.txt reads from input.txt as input, and command 2> errors.txt captures error messages separately. To target specific file descriptors, Bash uses numbered forms like > filename for output or < filename for input, where n is the descriptor number (defaulting to 1 for > and 0 for < if omitted). Descriptors greater than 9 may conflict with shell internals, so lower numbers are preferred for custom use. The &> operator redirects both standard output and to a file, truncating it, while &>> appends both. File descriptor duplication merges or copies streams using forms like >&m to duplicate output descriptor n to m, or <&m for input. A common example is 2>&1, which merges into standard output, as in ls nonext 2>&1 > combined.txt, sending both to the file. Using - instead of a number, such as 2>&-, closes the descriptor. Bash also provides special files like /dev/stdout and /dev/stderr for explicit duplication. Here documents supply multi-line input using <<word, where the shell reads until it encounters a line matching word exactly (with variable and command expansion if word is unquoted). The <<-word variant strips leading tabs from the input lines and delimiter, aiding indented scripts, as in:

cat <<EOF Line 1 Line 2 (tabs stripped with <<-) EOF

cat <<EOF Line 1 Line 2 (tabs stripped with <<-) EOF

Process substitution, a Bash extension, treats command output as a temporary file: <(command) for input (e.g., diff <(sort file1) <(sort file2)) and >(command) for output (e.g., command >(grep filter)). This enables treating processes like files in redirections. For persistent redirections in the current shell, exec modifies descriptors without starting a new process, such as exec 3> logfile to open descriptor 3 for appending logs, or exec >outfile to redirect all subsequent output. Redirections are applied from left to right before the command executes, affecting the ; for instance, command >file 2>&1 redirects both streams to file, but command 2>&1 >file redirects only output to file (with already merged). This sequencing ensures predictable I/O behavior in complex pipelines.

Conditional Constructs

Bash provides conditional constructs to enable decision-making in scripts based on command exit statuses, file properties, string comparisons, or arithmetic evaluations. These constructs include the if statement for linear conditional branching, the case statement for multi-way branching using , and specialized test commands for evaluating conditions. The if statement evaluates a condition and executes commands accordingly. Its syntax is if test-commands; then consequent-commands; [elif more-test-commands; then more-consequents;] [else alternate-consequents;] fi, where test-commands are executed first; if they return a zero , the consequent-commands follow. If not, any elif clauses are checked sequentially, and if none succeed, the else block executes if present. The overall return status is that of the last executed command or zero if no condition is true. Conditions in if statements typically use the test command, invoked as [ expression ] or its Bash extension [[ expression ]]. The [ ] form performs basic tests with word splitting and filename expansion enabled, supporting unary file operators like -f file (true if file exists and is a regular file) and binary operators like = for string equality or -eq for numeric equality. For example, if [[ -f config.txt ]]; then echo "File exists"; fi checks for the existence of a regular file named config.txt. String comparisons use == or = for equality and != for inequality, as in if [[ "&#36;var" == "value" ]]; then ...; fi. The [[ ]] construct extends [ ] by disabling word splitting and glob expansion, allowing safer handling of variables, and adds features like with == (glob-style) and regex matching with =~. For instance, [[ $string =~ ^[0-9]+$ ]] tests if $string consists entirely of digits using a ; it returns 0 if true, 1 if false, and 2 if the regex is invalid. The BASH_REMATCH array captures matches from =~, with index 0 holding the full match. Numeric comparisons in [[ ]] use -eq, -ne, -lt, -le, -gt, and -ge. For arithmetic conditions, Bash uses the (( expression )) compound command as an alternative to test. It evaluates the arithmetic expression and returns 0 if the result is non-zero, or 1 if zero, enabling uses like if (( count > 0 )); then ...; fi. Conditions often rely on command exit codes, where 0 indicates and non-zero indicates . The $? variable holds the of the most recent command, as in command; if [[ &#36;? -eq 0 ]]; then ...; fi. For pipelines, the PIPESTATUS stores exit statuses of all commands in the most recently executed foreground , allowing checks like cmd1 | cmd2; if [[ &#36;{PIPESTATUS{{grok:render&&&type=render_inline_citation&&&citation_id=0&&&citation_type=wikipedia}}} -eq 0 && ${PIPESTATUS{{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}}} -eq 0 ]]; then ...; fi. Bash sets PIPESTATUS after pipelines, subshells, or certain compound commands. The case statement handles multiple conditions via . Its syntax is case word in [patterns [| patterns]...) commands ;; ]... esac, where word (typically a variable) is matched against glob patterns; the first matching case's commands execute, using | for alternatives. Patterns support globs like * for default matching, and execution stops at ;; (or continues with ;& or ;;& in Bash 4.0+). For example:

case "$animal" in horse|dog|cat) echo "four legs" ;; *) echo "unknown" ;; esac

case "$animal" in horse|dog|cat) echo "four legs" ;; *) echo "unknown" ;; esac

This outputs "four legs" if $animal matches "horse", "dog", or "cat". The return status is 0 if no patterns match, otherwise that of the last command in the executed case.

Looping Constructs

Bash provides several looping constructs to enable repetitive execution of commands in scripts, allowing automation of tasks such as processing lists of files or iterating over sequences of values. These include the traditional for iterating over word lists, the while and until loops for condition-based repetition, an arithmetic inspired by C syntax, and the select construct for creating interactive menus. Control over loop execution is managed through the break and continue builtins, which allow early termination or skipping of iterations. The standard iterates over a list of words, assigning each to a variable for use within the loop body. Its syntax is for name [in words ...]; do commands; done, where the loop executes the commands for each word in the supplied list, binding the current word to the variable name. If the in words clause is omitted, the loop uses the script's positional parameters. For example, to iterate over all files in the current directory using glob expansion, one might write:

for i in *; do [echo](/page/Echo) "$i" done

for i in *; do [echo](/page/Echo) "$i" done

This processes each matching filename, with word splitting applied to the list after expansions (as detailed in the Types of Expansions section). The loop's return status is that of the last command executed or zero if none ran. An alternative arithmetic , resembling C-style syntax, supports numerical iteration and is written as for ((expr1; expr2; expr3)); do commands; done. Here, expr1 initializes the loop (e.g., setting a counter), expr2 serves as the condition (evaluated as non-zero to continue), and expr3 increments or updates after each iteration. Expressions use shell arithmetic evaluation. For instance, for ((i=1; i<=5; i++)); do echo $i; done outputs numbers 1 through 5. The return status is the last command's status or non-zero if any expression is invalid. This form is useful for precise control in computational scripts. The while loop repeats commands based on a condition, with syntax while test-commands; do consequent-commands; done. It executes the consequent-commands as long as the test-commands return a zero exit status (true). The until loop inverts this logic: until test-commands; do consequent-commands; done runs while the test returns non-zero (false), stopping when true. Both return the status of the last consequent-command or zero if none executed. These are ideal for loops dependent on external states, such as reading input until end-of-file. The select construct facilitates interactive menu selection, using syntax select name [in words ...]; do commands; done. It displays a numbered list of words derived from the shell's PS3 prompt (defaulting to "? "), reads user input via the Reply variable, and executes commands with the selected word bound to name. The loop continues until a break is issued or input is invalid. This is commonly used for simple user-driven choices in scripts. The return status matches the last command's or zero if none ran. To manage flow within loops, the break builtin exits the enclosing loop (or the nth if specified: break ), while continue skips to the next iteration ( continue ). Both apply to for, while, until, and select loops, requiring n ≥ 1, and return zero unless n is invalid. For example, break 2 exits two enclosing loops. These provide essential control for handling exceptions or optimizations during iteration.

Functions and Aliases

Bash provides mechanisms for users to define reusable code blocks through functions and aliases, enhancing script modularity and interactive efficiency. Functions allow for complex logic with argument handling and variable scoping, while aliases offer simple text substitutions primarily for interactive use. These features enable customization without altering core shell behavior. Functions in Bash are defined using one of two syntaxes: name() compound-command or function name [()] compound-command, where the compound-command is typically a braced list of commands, such as { echo "Hello"; }. For example, the following defines a function named greet that outputs a message:

greet() { echo "Hello, world!" }

greet() { echo "Hello, world!" }

Invoking greet executes the body as if the commands were directly entered in the shell. Within a function, arguments passed during invocation become available as positional parameters: &#36;1 for the first argument, &#36;2 for the second, and so on, with $# indicating the total number. For instance, if greet "Alice" is called, &#36;1 inside the function holds "Alice". The function's exit status is determined by the last command executed or explicitly set using return n, where n is an integer from 0 to 255; a value of 0 indicates success. Variable scoping in functions supports locality through the local declaration, which creates variables visible only within the function and its subshells, preventing unintended modifications to the global environment. For example:

myfunc() { local temp=42 echo $temp # Outputs 42 }

myfunc() { local temp=42 echo $temp # Outputs 42 }

Without local, variables are dynamically scoped and shared with the calling environment. Functions can also be recursive, calling themselves, though depth is limited by the FUNCNEST shell option (default: unlimited, constrained by system stack size). To make a function available in child processes or subshells, use export -f name, which marks it for inheritance similar to environment variables. This is essential for scripts that spawn subprocesses needing the function. For example:

export -f greet bash -c 'greet' # Executes the function in a new shell ```[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions) Aliases, in contrast, provide straightforward command shortcuts via the `alias` builtin, using the syntax `alias name=value`, where `value` is the expanded text. A common example is `alias ll='ls -l'`, which substitutes `ll` with `ls -l` upon invocation. Aliases are expanded during the shell's tokenization phase, before other expansions, but only in interactive shells or when `shopt -s expand_aliases` is enabled in scripts. They support recursive expansion if the value ends with a space.[](https://www.gnu.org/software/bash/manual/bash.html#Aliases)[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Operation) Aliases can be removed with `unalias name` or all at once with `unalias -a`. Listing active aliases is done via `alias` (without arguments) or `alias -p` for a printable format. However, aliases are limited to simple textual replacements and do not handle arguments or multi-line logic, making them unsuitable for anything beyond interactive conveniences like abbreviating common commands.[](https://www.gnu.org/software/bash/manual/bash.html#Aliases) The primary distinction between functions and aliases lies in their capabilities: aliases suit quick, non-parameterized shortcuts in interactive sessions, whereas functions enable sophisticated scripting with arguments, control structures, and scoping for reusable code in both interactive and non-interactive contexts. For instance, an alias cannot process input like `&#36;1`, but a function can implement conditional behavior based on arguments. This separation ensures aliases remain lightweight while functions provide full shell programming power.[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions)[](https://www.gnu.org/software/bash/manual/bash.html#Aliases) | Feature | Aliases | Functions | |----------------------|----------------------------------|------------------------------------| | Definition Syntax | `alias name=value` | `name() { commands; }` or `function name { commands; }` | | Argument Handling | None | Positional parameters (`&#36;1`, etc.) | | Complexity | Simple text substitution | Supports logic, loops, conditionals | | Scoping | Global replacement | Local variables via `local` | | Exit Status Control | Inherits from substituted command | Explicit via `return n` (0-255) | | Export to Subshells | Not applicable | Via `export -f` | | Primary Use Case | Interactive shortcuts | Reusable script modules | ## Advanced Constructs ### Subshells and Process Management In Bash, a subshell is a child process created as a copy of the current shell, allowing commands to execute in an isolated environment. Subshells are invoked by enclosing a list of commands within parentheses, such as `(command1; command2)`, which forces the shell to spawn a new process for their execution. This mechanism ensures that changes made within the subshell, such as variable assignments, do not persist in the parent shell after completion. For example: ```bash ( export FOO=bar; echo $FOO ) # Outputs 'bar' inside subshell echo $FOO # Outputs nothing or previous value in parent shell

export -f greet bash -c 'greet' # Executes the function in a new shell ```[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions) Aliases, in contrast, provide straightforward command shortcuts via the `alias` builtin, using the syntax `alias name=value`, where `value` is the expanded text. A common example is `alias ll='ls -l'`, which substitutes `ll` with `ls -l` upon invocation. Aliases are expanded during the shell's tokenization phase, before other expansions, but only in interactive shells or when `shopt -s expand_aliases` is enabled in scripts. They support recursive expansion if the value ends with a space.[](https://www.gnu.org/software/bash/manual/bash.html#Aliases)[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Operation) Aliases can be removed with `unalias name` or all at once with `unalias -a`. Listing active aliases is done via `alias` (without arguments) or `alias -p` for a printable format. However, aliases are limited to simple textual replacements and do not handle arguments or multi-line logic, making them unsuitable for anything beyond interactive conveniences like abbreviating common commands.[](https://www.gnu.org/software/bash/manual/bash.html#Aliases) The primary distinction between functions and aliases lies in their capabilities: aliases suit quick, non-parameterized shortcuts in interactive sessions, whereas functions enable sophisticated scripting with arguments, control structures, and scoping for reusable code in both interactive and non-interactive contexts. For instance, an alias cannot process input like `&#36;1`, but a function can implement conditional behavior based on arguments. This separation ensures aliases remain lightweight while functions provide full shell programming power.[](https://www.gnu.org/software/bash/manual/bash.html#Shell-Functions)[](https://www.gnu.org/software/bash/manual/bash.html#Aliases) | Feature | Aliases | Functions | |----------------------|----------------------------------|------------------------------------| | Definition Syntax | `alias name=value` | `name() { commands; }` or `function name { commands; }` | | Argument Handling | None | Positional parameters (`&#36;1`, etc.) | | Complexity | Simple text substitution | Supports logic, loops, conditionals | | Scoping | Global replacement | Local variables via `local` | | Exit Status Control | Inherits from substituted command | Explicit via `return n` (0-255) | | Export to Subshells | Not applicable | Via `export -f` | | Primary Use Case | Interactive shortcuts | Reusable script modules | ## Advanced Constructs ### Subshells and Process Management In Bash, a subshell is a child process created as a copy of the current shell, allowing commands to execute in an isolated environment. Subshells are invoked by enclosing a list of commands within parentheses, such as `(command1; command2)`, which forces the shell to spawn a new process for their execution. This mechanism ensures that changes made within the subshell, such as variable assignments, do not persist in the parent shell after completion. For example: ```bash ( export FOO=bar; echo $FOO ) # Outputs 'bar' inside subshell echo $FOO # Outputs nothing or previous value in parent shell

Subshells inherit the parent's environment, including exported variables, open file descriptors, the current working directory, and the file creation mask, but modifications within the subshell remain local to that process. Additionally, subshells can arise implicitly in contexts like command substitutions (e.g., $(command)) or asynchronous command execution, providing isolation for potentially disruptive operations. However, starting with Bash 5.3, a new form of command substitution allows execution in the current shell environment without forking a subshell. The syntax is ${c command; }, where c is a space, tab, newline, or |, and the closing brace follows a command terminator such as a semicolon. This applies side effects, like variable assignments, directly to the parent environment and captures the command's output (with trailing newlines removed). A variant, ${| command; }, sets the output to the local REPLY variable without expanding it in the substitution, preserving trailing newlines and leaving standard output unchanged. Bash provides special parameters to access process identifiers, enabling scripts to track and manage processes. The parameter $$ expands to the process ID (PID) of the current shell, while $PPID yields the PID of the shell's parent process. The parameter $! returns the PID of the most recently started background process, facilitating reference to asynchronous tasks. These parameters are read-only and updated dynamically as processes are created or managed. Background execution allows commands to run asynchronously without blocking the shell, initiated by appending an ampersand (&) to the command, such as command &. This launches the command in a subshell, returning control to the shell immediately while the background process continues. The shell reports the PID and a job specification upon starting the background command. To bring a background process to the foreground, the fg builtin can be used with the job specification or PID; conversely, bg resumes a suspended job in the background. The wait builtin synchronizes script execution by pausing until specified background processes complete. Invoked as wait [PID], it returns the exit status of the waited process or 0 if no argument is provided (waiting for all background jobs). This is essential for ensuring dependent operations proceed only after asynchronous tasks finish, as in:

bash

sleep 5 & wait $! echo "Background task completed"

sleep 5 & wait $! echo "Background task completed"

The exec builtin replaces the current shell process with a specified command, without creating a new subshell, effectively terminating the shell upon invocation. When used as exec command, it overlays the command onto the shell's process image, inheriting all open file descriptors unless redirected. If no command is given (e.g., exec), it simply closes the shell after applying any redirections, useful for reassigning standard input/output in login shells. In contrast to subshells, command grouping with curly braces { list; } executes commands within the current shell environment, avoiding the overhead and isolation of a new process. This form requires a semicolon before the closing brace and spaces around the braces for proper parsing, as in:

bash

{ export FOO=bar; echo $FOO; } # 'bar' persists in current shell echo $FOO # Outputs 'bar'

{ export FOO=bar; echo $FOO; } # 'bar' persists in current shell echo $FOO # Outputs 'bar'

Unlike subshells, changes in brace groups directly affect the parent environment, making them suitable for operations requiring persistent state modifications without process forking.

Pipelines and Logical Operators

Bash pipelines allow multiple commands to be connected in a linear fashion, where the standard output of one command serves as the standard input for the next. A pipeline consists of one or more commands separated by the pipe operator |, as in the syntax [time [-p]] [!] command1 [ | or |& command2 ] .... The output of each command in the pipeline is directed to the input of the subsequent command, enabling data to flow sequentially through the chain without intermediate storage to disk. For instance, the command grep pattern file | wc -l searches for a specified pattern in a file and pipes the matching lines to wc -l, which counts the number of lines, effectively providing the total occurrences of the pattern. The pipe operator |& extends this by connecting both the standard output and standard error of the preceding command to the next, equivalent to appending 2>&1 | for redirection. By default, the of a is that of the last (rightmost) command, unless the pipeline is preceded by !, in which case the status is negated, or if run asynchronously, in which it is always 0. However, when the pipefail shell option is enabled via set -o pipefail, the reflects the rightmost command that failed with a non-zero status, or 0 if all commands succeeded; this promotes robust error detection in scripts. Logical operators facilitate conditional and sequential execution of pipelines or commands within lists, which are sequences separated by ;, &&, or ||. The semicolon ; enforces sequential execution, running each command or pipeline one after the other, with the shell waiting for completion before proceeding, and the overall exit status being that of the final command. The && operator (logical AND) executes the subsequent command only if the preceding one exits successfully (status 0), while || (logical OR) executes it only on failure (non-zero status); both associate left-to-right with equal precedence, and the list's exit status is that of the last executed command. For example, command1 && command2 || command3 runs command2 if command1 succeeds, otherwise skips to command3. Commands or pipelines can be grouped for compound execution using parentheses () or braces {}. Parentheses execute the enclosed list in a separate environment, treating it as a single unit whose redirections apply to the group, with the matching that of the list. Braces execute the list in the current environment, requiring a trailing or before the closing brace, and also yield the list's as the group's. These groupings integrate with pipelines and lists, such as (cmd1 | cmd2); cmd3. Process substitution enhances pipelines by allowing a command's input or output to be treated as a file. The syntax <(list) provides the output of the list as a readable , while >(list) supplies input to the list via a writable ; these expand during command and support asynchronous execution via named pipes or /dev/fd. In pipelines, this enables flexible data routing, as in sort <(cmd1) | cmd2, where cmd1's output is sorted before piping to cmd2. Redirections, such as those for files or devices, can be applied within pipelines but are handled per command unless grouped.

Arrays and Data Structures

Bash supports arrays as a fundamental data structure for storing and manipulating collections of values under a single variable name, primarily through one-dimensional indexed arrays and associative arrays. Indexed arrays use integer indices starting from 0, while associative arrays, available since Bash 4.0, use string keys for mapping values. These arrays enable efficient handling of lists, mappings, and dynamic data in scripts without relying on external tools. Indexed arrays can be explicitly declared using the declare -a builtin, though declaration is often implicit through assignment. For example, elements are assigned with arrayname[index]=value, where the index is a non-negative integer (negative indices, such as -1 for the last element, are supported and count from the end). An array can also be initialized with multiple values using compound assignment: arrayname=(value1 value2 value3). To expand all elements, use ${arrayname[@]} or ${arrayname[*]}, where @ preserves word separation based on the IFS variable and * joins elements with IFS. Associative arrays require explicit declaration with declare -A arrayname (Bash 4.0 and later) and use string keys for assignments like arrayname[key]=value. Initialization follows a similar compound format: declare -A assoc=( [key1]=value1 [key2]=value2 ). Keys must be non-empty strings, and retrieval uses ${arrayname[key]}. Like indexed arrays, expansion of all values employs ${arrayname[@]}. Common operations on arrays include determining length, unsetting elements, and appending values. The number of elements is obtained with ${#arrayname[@]} (for both types), while the length of a specific element uses ${#arrayname[index]} or ${#arrayname[key]}. To remove an element, unset arrayname[index] or unset arrayname[key] is used; unsetting the entire array clears it with unset arrayname. Appending works via arrayname+=(value) for indexed arrays or arrayname+=( [key]=value ) for associative ones, automatically extending or adding as needed. Bash simulates multidimensional arrays using compound subscripts within a flat, one-dimensional structure, such as declare -a matrix; matrix[0,0]=value; echo "${matrix[0,0]}", where the index "0,0" is treated as a single string key in an indexed array (or directly in associative arrays). This approach lacks true nesting and requires careful index management. Iteration over arrays is typically done with for loops. For values in an indexed array: for element in "${arrayname[@]}"; do echo "$element"; done. For associative arrays, keys are iterated with for key in "${!arrayname[@]}"; do echo "$key -> ${arrayname[$key]}"; done, where ${!arrayname[@]} expands to all indices or keys. Sparse arrays are supported, permitting non-contiguous indices without wasting space for gaps, but Bash provides no native support for true multidimensional arrays beyond this simulation.

Interactive Features

Command History and Recall

Bash maintains a list of recently executed commands, known as the command history, which is available only in interactive shells. This feature allows users to recall, edit, and reuse previous commands efficiently, enhancing productivity in terminal sessions. By default, Bash enables command history and history expansion for interactive use, storing commands in both memory and a persistent file. The history list in memory is controlled by the HISTSIZE variable, which specifies the maximum number of commands to maintain, with a default value of 500. When the shell session ends, Bash appends the in-memory history to the history file, typically located at ~/.bash_history, unless the histappend shell option is enabled via shopt -s histappend, in which case it appends rather than overwrites the file. The size of the history file is limited by the HISTFILESIZE variable, also defaulting to 500 lines; excess lines are truncated when writing. Users can customize the history file location with the HISTFILE variable. Commands can be excluded from history by starting them with a space (if ignorespace is set in HISTCONTROL) or by matching patterns in the colon-separated HISTIGNORE variable, such as HISTIGNORE="ls:cd" to ignore ls and cd commands. Additionally, HISTCONTROL can be set to ignoredups to suppress consecutive duplicates or erasedups to remove all prior instances of a command. To view or manipulate the history, Bash provides the built-in history command. Invoking history without arguments displays the entire history list, numbered sequentially starting from 1, while history n shows the most recent n commands. Options include history -c to clear the in-memory list, history -d n to delete the nth entry (supporting negative offsets from the end in Bash 5.3 and later), and history -a to append the current session's history to the file immediately. As of Bash 5.3, history -d also supports deleting ranges of entries with syntax like -d start-end. The fc built-in command facilitates editing and re-execution of historical commands; for example, fc -l lists history entries similar to history, fc n invokes the default editor on the nth command for modification before re-execution, and fc -s old=new substitutes old with new in the most recent command matching old and then executes it. History expansion, triggered by the ! character in interactive input, enables quick recall and modification of past commands without listing the full history. This expansion occurs after the command line is read but before it is split into words or executed, and it applies to each line individually. The ! must be quoted (e.g., with \!) if literal use is intended. Event designators specify which command to recall: !! refers to the previous command; !n selects the nth command by its history number; !-n selects the nth previous command (e.g., !-1 is equivalent to !!); !string matches the most recent command starting with string; and !?string matches the most recent containing string, optionally ending with ? for exact search. Word designators follow the event to select parts: ^ for the first argument, $ for the last, n for the nth word (starting from 0 for the command itself), * for all arguments, or x-y for a range. Modifiers alter the selected text, such as :p to print without executing, :s/old/new/ for single substitution, :gs/old/new/ for global substitution, :t to retain only the trailing filename component, :h for the head (directory), :r to remove the extension, and :e for the extension alone. For instance, !! re-executes the last command, !ls:1 inserts the first argument of the last ls command, and !!:s/foo/bar/ substitutes "foo" with "bar" in the previous command before running it. If no match is found, expansion fails and the command aborts unless modified with :p. History expansion can be disabled with set +H or set -o history (enabled by default for interactive shells). To share history across multiple terminal sessions in real-time, users can set the PROMPT_COMMAND variable to history -a; history -r, which appends the current session's new commands to the file and reads updates from other sessions after each prompt. Combined with shopt -s histappend, this ensures non-destructive updates without overwriting.

Programmable Completion

Programmable completion in Bash allows users to customize the tab-completion behavior for commands and their arguments in interactive shells, enabling context-aware suggestions that enhance efficiency. This feature, enabled by default via the progcomp shell option, integrates with the Readline library to generate and display possible matches when the Tab key is pressed. By default, Bash attempts completion by first checking for any programmable specifications associated with the command; if none exist, it falls back to filename completion or other defaults like alias expansion. The complete -p command lists all current completion specifications, providing a way to inspect and reuse existing setups. As of Bash 5.3, command completion matches aliases and shell function names case-insensitively if the Readline variable completion-ignore-case is set. Customization occurs primarily through the complete builtin command, which defines completion specifications (compspecs) for specific commands or globally. For instance, complete -F func cmd associates a shell function func with the command cmd, where the function generates options dynamically. The compgen builtin aids in this process by generating lists of possible completions, such as compgen -f for filenames or compgen -A alias for aliases, which can be integrated into completion functions. These builtins support various options to refine behavior, including -o bashdefault to combine custom logic with Bash's defaults, -o nospace to prevent adding a space after completion, and -X filterpat to exclude patterns matching a glob. In programmable completion functions, Bash sets several environment variables to provide context, such as COMP_WORDS (an of words in the current command line), COMP_CWORD (the index of the word containing the cursor), and COMP_LINE (the full command line). The function must populate the COMPREPLY with matching strings—one per element—to supply the completions, which Readline then uses to display a , cycle through options, or insert the match. For example, a completion function for the cd builtin might use compgen -d -- "$cur" to suggest directories, incorporating tilde expansion and the $CDPATH variable for enhanced navigation, and bind it via complete -F _comp_cd cd. This approach allows for sophisticated logic, such as filtering based on previous arguments or integrating external data. The bash-completion project extends this capability system-wide by providing a collection of pre-written completion scripts for common commands, loaded automatically from /etc/bash_completion or /etc/bash_completion.d/. This framework, sourced in user profiles like ~/.bashrc, supports on-demand loading and per-user overrides in directories such as ~/.local/share/bash-completion/completions. A prominent example is the completion script, which offers detailed tab-completion for Git subcommands, branches, and options; it includes the __git_ps1 function for integrating repository status into the shell prompt, though the core completions are handled by a dedicated _git function bound via complete -F _git git. These scripts demonstrate how programmable completion scales to complex tools, reducing errors and speeding up workflows in development environments.

Custom Prompts and Readline

Bash provides extensive customization options for interactive prompts through environment variables and integration with Readline library, allowing users to tailor the for better usability and aesthetics. The primary prompt, displayed before each command in an interactive shell, is defined by the PS1 variable, which supports a variety of backslash-escaped special characters for dynamic content. For instance, \u inserts the current username, \w displays the current (with expansion for the ), and \h shows the up to the first period. As of Bash 5.3, a new PS0 prompt string variable has been introduced, which is expanded and displayed after reading a command line but before executing it. Secondary prompts extend this customization for specific interactive scenarios. The PS2 variable controls the prompt for multi-line commands, defaulting to "> " and appearing when the shell awaits further input, such as after an open quote or unclosed brace. PS3 defines the prompt for the select built-in command in scripts, prompting for menu choices, while PS4 prefixes debug output when tracing is enabled with set -x, typically set to "+ " to indicate traced commands. Like PS1, these variables interpret the same escape sequences, allowing consistent formatting across prompt types. In Bash 5.3, prompt expansion now quotes the results of the \U escape sequence. Colors and non-printing sequences enhance prompt readability on supported terminals. ANSI escape codes can be embedded within $$ $$ delimiters to apply formatting without affecting cursor positioning or line wrapping; for example, $$\e[32m$$ sets green text, and $$\e[0m$$ resets to default. A common colored prompt is export PS1='$$\e[32m$$\u@\h:\w\$ $$\e[0m$$', displaying the username and host in green. These escapes rely on the terminal's capabilities, determined by the TERM environment variable (e.g., "xterm-256color" for full color support); users should verify TERM before applying colors to avoid garbled output on basic terminals like "dumb". The GNU Readline library underpins Bash's command-line editing, providing programmable interfaces for input handling and key customization. Readline supports two primary editing modes: Emacs mode (default, with Ctrl-based shortcuts like Ctrl-A for beginning of line) and Vi mode (enabled via set -o vi or set editing-mode vi in ~/.inputrc), allowing users to switch between familiar keymaps for navigation and editing. As of Readline 8.3 (included with Bash 5.3), new bindable commands next-screen-line and previous-screen-line allow cursor movement by screen lines, and non-incremental Vi-mode searches (N, n) can use shell pattern matching via fnmatch(3) if available. New Readline variables include completion-display-width to set the number of columns used for displaying matches and menu-complete-display-prefix to show a common prefix before cycling through completions. Additionally, the export-completions command writes possible completions to stdout. Key bindings can be defined dynamically using the bind built-in, such as bind '"\C-x": "some-command"' to map Ctrl-X to a specific Readline function, or to shell commands with -x. Persistent customizations are managed through the ~/.inputrc file, which Readline reads on startup (falling back to /etc/inputrc if absent). This file supports variable assignments (e.g., set history-search-delimiter / for custom history searches) and key bindings in the format keyseq: function-name, such as "\e[A": history-search-backward to enable upward arrow for prefix-based history navigation. Bindings in ~/.inputrc apply globally to Readline-using applications, including Bash, and can be reloaded interactively with Ctrl-X Ctrl-R. For Vi mode specifics, ~/.inputrc can include $if mode=vi conditionals to set insertion or command-mode bindings separately.

Job Control and Signals

Job control in Bash provides mechanisms for managing multiple processes or jobs within an interactive shell session, allowing users to suspend, resume, and run processes in the background or foreground. Each pipeline executed in the shell constitutes a single job, which Bash tracks with a unique job number starting from 1, displayed in brackets (e.g., [1]) alongside the process ID and status when listed. This feature relies on support from the underlying operating system and terminal driver, enabling selective suspension and resumption of process execution. As of Bash 5.3, in interactive shells, job completion notifications are suppressed while sourcing scripts and printed during trap execution. Jobs can be referenced using job specifications (jobspecs) such as %n for the job with number n (e.g., %1), %string for the job whose command begins with string (e.g., %ce for a job starting with "ce"), or %?string for any job containing string. The jobs builtin command lists all active jobs, showing their numbers, PIDs, status (running, stopped, or done), and commands, with the current job marked by a + and the previous job by a -. For example, running sleep 100 & followed by jobs might output {{grok:render&&&type=render_inline_citation&&&citation_id=1&&&citation_type=wikipedia}}+ Running sleep 100 &. To manage jobs, users can suspend a foreground job by pressing Ctrl+Z, which sends the SIGTSTP signal to stop its execution and return control to the shell. The fg builtin then resumes the specified job (or the current one if none provided) in the foreground, blocking the shell until completion (e.g., fg %1). Conversely, bg %1 resumes a stopped job in the background, allowing the shell to accept new commands while the job runs asynchronously. These operations facilitate multitasking in interactive sessions without terminating processes. As of Bash 5.3, the wait builtin can wait for the last created and includes a -f option to wait until a job or terminates. Bash handles signals to manage job lifecycle and interruptions, with interactive shells ignoring SIGTERM by default and catching SIGINT (generated by Ctrl+C) to interrupt commands or loops. As of Bash 5.3, in mode, the SIGCHLD trap runs once per exiting even if job control is disabled. The trap builtin allows customization of signal handling by specifying a command to execute upon receipt of a signal, such as trap 'echo "Interrupted"' SIGINT, which runs the handler when SIGINT is received. Common signals include SIGINT for user interruptions and SIGTERM for graceful termination requests, though the latter is ignored in interactive mode unless explicitly trapped. The disown builtin removes jobs from the shell's active table or prevents them from receiving (hangup signal) on shell exit, with disown -h %1 marking a job to ignore while keeping it listed. This is useful for long-running background jobs that should persist after logout. Additionally, wait synchronizes script execution by pausing until specified jobs or processes complete, returning their (e.g., wait %1 for job 1). By default, job control is disabled in non-interactive scripts for performance reasons, but it can be enabled with set -m, allowing background job management similar to interactive sessions. In such cases, subshell processes created by scripts can be treated as jobs under this mode. With job control active, Bash places processes in separate process groups, notifying the user upon background job completion.

Debugging and Observability

Tracing and Verbose Output

Bash provides built-in options for enabling tracing and verbose output during script execution, allowing users to observe command and expansions for purposes. The set -x option, also known as xtrace, instructs the shell to print each command and its expanded arguments to just before execution. This output is prefixed by the value of the PS4 shell variable, which defaults to + but can be customized, for example, to include line numbers with PS4='Line ${LINENO}: '. In contrast, the set -v option, or , causes the shell to echo each line of input as it is read, before any expansions or substitutions occur. This is particularly useful for verifying the raw script content during processing. Both options can be combined for comprehensive visibility; for instance, running an external script with tracing enabled from the outset is achieved via bash -xv script.sh. Tracing can be toggled dynamically within a script using set +x to disable xtrace or set +v to turn off . For conditional activation, the trap builtin can be employed with the DEBUG signal to enable or disable tracing based on specific events, such as errors, by executing set -x or set +x in response. Additionally, the BASH_XTRACEFD shell variable allows redirection of xtrace output to a specific rather than the default (file descriptor 2); setting it to an like 3 directs output there, while unsetting it reverts to . The is automatically closed if BASH_XTRACEFD is unset or reassigned. These features are commonly used in Bash scripts to log command expansions and trace execution flow, helping identify issues like variable substitution errors or unexpected argument passing without altering the script's logic. For example, enabling xtrace in a complex script reveals the actual commands after globbing and parameter expansion, aiding in .

bash

#!/bin/bash set -x # Enable tracing echo "Value: $VAR" # Output: + echo "Value: hello" (assuming VAR=hello) set +x # Disable tracing

#!/bin/bash set -x # Enable tracing echo "Value: $VAR" # Output: + echo "Value: hello" (assuming VAR=hello) set +x # Disable tracing

Error Handling and

In Bash, commands and scripts communicate success or failure through codes, which are integers ranging from 0 to 255. A value of 0 indicates successful execution, while any non-zero value signals an error or failure, with specific conventions such as 126 for commands that cannot be executed due to permissions and 127 for commands not found. The special parameter $? holds the of the most recently executed foreground or command, allowing scripts to inspect and respond to outcomes programmatically. To handle errors dynamically, Bash provides the trap builtin, which can intercept non-zero exit statuses via the ERR pseudo-signal. For instance, the command trap 'echo "Error at line $LINENO"' ERR will execute the specified action whenever a command exits with a non-zero status, excluding certain contexts like conditionals or command lists; $LINENO expands to the current line number for precise error location. This mechanism enables custom error logging or cleanup without halting execution unless desired. The set -e option, also known as errexit, causes the shell to terminate immediately upon any command's non-zero exit status, unless the failing command is part of a conditional construct (such as if or while) or executed within an && or || list, promoting stricter error propagation in scripts. For pipelines, where multiple commands are chained with |, the default exit status is that of the last command, but set -o pipefail alters this to return the exit status of the last command that failed (rightmost non-zero) or 0 if all succeed, ensuring intermediate failures in the pipeline are not masked. This is particularly useful for detecting errors in data processing chains. In functions, the return builtin explicitly sets the function's exit status to a specified value between 0 and 255, overriding the status of the last command executed within it; for example, return 42 sets $? to 42 upon function completion. Practical examples illustrate these features. To check a command's outcome conditionally, one might use if ! ls /nonexistent; then echo "Failed with status $?"; fi, where ! negates the for the test (detailed further in conditional constructs). Enabling set -e in a script like set -e; false; echo "This won't print" results in immediate exit without printing, halting on the failure. With pipefail, set -o pipefail; false | true; echo $? outputs 1, reflecting the failure in the pipeline. In a function, defining error() { return 1; } and calling error; echo $? prints 1, demonstrating controlled status setting. These tools collectively allow robust management, balancing automation with explicit control in Bash scripting.

Comments and Debugging Tools

In Bash scripts, comments are introduced by a hash symbol (#) at the beginning of a word, causing the shell to ignore the # and all subsequent characters until the end of the line. This feature is enabled by default in interactive shells through the interactive_comments shell option, but it applies universally in non-interactive contexts like scripts. Bash does not support native multi-line comments; instead, developers typically achieve this by prefixing each line of a block with # or by employing a here-document to encapsulate explanatory text, though the latter is not strictly a comment mechanism. For debugging Bash scripts, there is no built-in debugger analogous to gdb for other languages; instead, developers rely on manual techniques and shell builtins. The declare -p command prints the definitions of specified variables, including their attributes and values, which aids in inspecting the script's state during development. Similarly, typeset serves as an alias for declare and can display variable attributes when used without additional options, facilitating the verification of data types and scopes. The LINENO special variable provides the current within a script or function, proving particularly useful in trap handlers to log or respond to errors at specific locations. Best practices for include liberally adding inline comments to clarify logic, grouping related comments into blocks with multiple # lines for readability, and employing the printf builtin for conditional debug output, such as printing variable states only when a debug flag is set (e.g., printf "Debug: var=%s\n" "$var"). For runtime execution tracing, the set -x option enables verbose output of each command as it runs, though detailed coverage of tracing appears in the dedicated section on tracing and verbose output.

Data Manipulation

Parameter and Variable Expansion

Parameter expansion in Bash allows the substitution of the value of a shell parameter, which can be a variable, positional parameter, or special parameter, into the command line. The basic syntax uses the dollar sign followed by the parameter name, such as $var to expand the value of the variable var. For clarity or when the parameter name might be ambiguous, such as with multi-digit positional parameters or when followed by characters that could be part of the name, the preferred form is ${var}. This expansion occurs during the shell's parsing phase, replacing the parameter reference with its value before the command is executed. Bash provides several operators for handling unset or null parameters, enabling defaults, assignments, or error checks. The form ${parameter:-default} substitutes the default value if the parameter is unset or null, but does not assign it to the parameter; for example, echo ${var:-unknown} safely outputs "unknown" if var is unset, otherwise its value. In contrast, ${parameter:=default} assigns the default to the parameter if it is unset or null and then substitutes the value, useful for initializing variables on first use, as in : ${var:=default}; echo $var. For error handling, ${parameter:?error-message} substitutes the parameter's value if set and non-null, but if unset or null, it prints the error message to standard error and exits the shell (or returns a non-zero status in interactive mode). Substring extraction and pattern-based removal are supported through specific operators. The syntax ${parameter:offset:length} extracts a starting from the zero-based offset, taking up to length characters; negative offsets count from the end of the . For instance, with var=abcdefgh, ${var:2:3} yields "cde". Prefix removal uses ${parameter#pattern} to delete the shortest matching prefix from the expanded value, or ${parameter##pattern} for the longest match; an example is var=/path/to/file; echo ${var#/path} outputting "/to/file". The length of a 's value can be obtained with ${#parameter}, which returns the number of characters in the expanded value for scalars. For , ${#array[@]} or ${#array[*]} gives the number of elements in the , providing a way to count size without delving into -specific structures. Indirect expansion, ${!parameter}, treats the value of parameter as the name of another and substitutes that one's value; for example, if var=HOME and HOME=/home/user, then echo ${!var} outputs "/home/user". Introduced in Bash version 4.0 and later, case modification operators allow transforming the case of the expanded value. The form ${parameter^^} converts all characters in the expansion to uppercase (or matching a specified pattern), while ${parameter,,} converts to lowercase; for var=hello, echo ${var^^} produces "HELLO". These features enhance string manipulation directly in expansions, reducing the need for external commands like tr.

Brace, Tilde, and Pathname Expansion

Brace expansion in Bash generates arbitrary strings that share a common prefix and , allowing users to create multiple variations efficiently. It is performed before any other expansions and treats the content strictly textually, preserving special characters for later processing. The syntax consists of an optional preamble, followed by an unquoted opening brace {, a comma-separated list of strings or a sequence expression, and an unquoted closing brace }, optionally followed by a . For example, echo a{d,c,b}e expands to ade ace abe. Nested brace expansions are supported, and the results are generated in left-to-right order. Sequence expressions, introduced in Bash 3.0, enable numeric or alphabetic ranges within braces. The form {x..y} expands to strings from x to y inclusive, where x and y can be integers or single characters; an optional increment ..incr allows custom steps, defaulting to 1 for ascending or -1 for descending sequences. Integer sequences are zero-padded if the upper bound requires more digits, while character sequences follow lexicographic order in the C locale. For instance, echo file{1..3}.txt produces file1.txt file2.txt file3.txt, and echo {a..c} yields a b c. To prevent expansion, backslashes can escape the braces or commas, or the opening brace can follow a dollar sign as in ${. Tilde expansion substitutes the tilde ~ at the beginning of an unquoted word with directory paths, facilitating shorthand references to user directories and navigation history. If the word starts with an unquoted ~ followed by characters up to the first unquoted slash (or the end if none), it forms the tilde-prefix for substitution. The plain ~ expands to the value of the $HOME environment variable, representing the current user's home directory. ~user expands to the home directory of the specified user, as determined by the password database. Variants include ~+ for the current working directory ($PWD), ~- for the previous working directory ($OLDPWD if set), and ~N or ~+N for elements in the directory stack via dirs +N or dirs -N. The expansion result is quoted to prevent further splitting or expansion, and invalid prefixes remain unchanged. For example, cd ~user/documents navigates to /home/user/documents. Tilde expansion also applies after the colon or first equals sign in certain variable assignments like PATH or CDPATH. Pathname expansion, also known as globbing or filename expansion, replaces unquoted patterns containing *, ?, or [ in words with a sorted list of matching filenames from the current directory (or specified paths). The * matches any string of zero or more characters, ? matches exactly one character, and [...] matches any single character from the specified set or range, such as [a-z]. Dots at the start of filenames or after slashes must be matched explicitly unless the dotglob option is enabled. For example, ls *.txt lists all files ending in .txt. By default, patterns that match no files remain unexpanded. Extended globbing patterns are available when the extglob shell option is enabled via shopt -s extglob, adding operators like !(pattern) to match anything except the given pattern, ?(pattern) for zero or one occurrence, *(pattern) for zero or more, +(pattern) for one or more, and @(pattern) for exactly one of the patterns. These must be enabled before parsing, as parentheses otherwise have syntactic meaning. For instance, with extglob active, ls !(README) lists all files except README. Several options modify pathname expansion behavior. The nullglob option, set with shopt -s nullglob, causes unmatched patterns to expand to an rather than remaining literal. GLOBIGNORE is a colon-separated list of patterns that, when set, ignores matching filenames during expansion, excluding . and .. by default. Additional options like nocaseglob enable case-insensitive matching, failglob treats no matches as errors, and globstar (Bash 4.0+) allows ** to recursively match directories. These expansions occur after brace and but before word splitting, which divides the resulting words into fields. Examples include echo file{1,2}.txt expanding first via braces to file1.txt file2.txt, then via pathname if files exist.

Word Splitting and Globbing

In Bash, word splitting is the process by which the shell divides the results of certain expansions—specifically parameter expansion, command substitution, and arithmetic expansion—into individual words, or fields, when they are not enclosed in double quotes. This occurs after the initial expansions but before further processing like filename expansion. The splitting is controlled by the Internal Field Separator (IFS) variable, which by default consists of the space, tab, and newline characters; if IFS is unset, it defaults to this value, and if set to null, no splitting occurs. The rules for word splitting are precise: sequences of IFS whitespace characters (, tab, or ) are first stripped from the beginning and end of the expansion result, treating any sequence of such characters as equivalent to a single without creating empty fields. For non-whitespace IFS characters, the text is split at each occurrence, and consecutive non-whitespace delimiters produce empty fields; however, if IFS contains only whitespace, consecutive delimiters are treated as one, ignoring potential empty fields unless the expansion is quoted. Expansions within double quotes are not subject to word splitting, preserving the entire result as a single word, though explicit null arguments (like "") are retained. Following word splitting, Bash performs filename expansion, also known as globbing, on each resulting word that contains unquoted pattern characters such as * (matching any string, including the null string), ? (matching any single character), or [...] (matching any single character in the specified set or range). This expansion replaces the pattern with a sorted list of matching filenames in the current directory (or specified path), but only if the pattern does not begin with a / or ./ and is not quoted; if no matches are found, the original word is retained unless modified by shell options. Globbing thus applies independently to each split word, potentially expanding a single split field into multiple filenames. Several shell options, set via the shopt builtin, influence globbing behavior. The nocaseglob option enables case-insensitive , so *.[Tt][Xx][Tt] would files like file.TXT. The failglob option causes the shell to report an error and exit if a fails to any files, rather than leaving the pattern unexpanded. These options do not affect word splitting directly but control how the post-split words are further processed. For example, consider the command var="file1.txt file2.txt"; for i in $var; do echo "$i"; done, which splits $var on spaces (default IFS) into two words, then applies globbing if patterns are present, outputting each filename on a separate line. If IFS is set to a colon, as in IFS=:'; var="a::b"; echo $var, the result splits into three fields: "a", an empty field, and "b", demonstrating how non-whitespace delimiters create null fields. In contrast, quoting prevents this: for i in "$var"; do echo "$i"; done treats the entire expansion as one word.

Security Considerations

Common Vulnerabilities and Exploits

One of the most significant historical vulnerabilities in Bash is Shellshock, identified as CVE-2014-6271, which affected Bash versions through 4.3. This flaw allowed remote attackers to execute arbitrary commands by injecting malicious code into environment variables, as Bash processed trailing strings after function definitions in these variables without proper sanitization. The vulnerability was particularly dangerous in network-facing applications like web servers using CGI scripts, where environment variables such as HTTP headers could be controlled by attackers, leading to widespread exploitation attempts shortly after disclosure. Patches were released in Bash 4.3 update 25 and subsequent versions to prevent execution of this trailing code. Command injection represents a common usage-related in Bash scripts, particularly when the eval builtin is used with untrusted input. By passing attacker-controlled data to eval, such as through eval "$untrusted_input", arbitrary shell commands can be executed, potentially compromising the system if the input originates from external sources like user forms or network requests. This issue exploits Bash's ability to interpret and execute strings as code, enabling attackers to append or inject commands that alter script behavior. For instance, if untrusted input contains a followed by a malicious command, it can chain executions beyond the intended operation. Path traversal vulnerabilities arise in Bash scripts that construct file paths using unsanitized user input, allowing attackers to access files outside the intended directory. By injecting sequences like ../ into path variables, an attacker can navigate the filesystem to read or write sensitive files, such as configuration data or logs, if the script performs operations like reading or creating files based on this input. This is especially risky in scripts handling file uploads or dynamic path resolution without validation, leading to unauthorized data exposure or modification. Time-of-check to time-of-use (TOCTOU) race conditions are prevalent in Bash scripts involving file operations, where a check for file existence or permissions occurs separately from its subsequent use. For example, a script might use [ -f "&#36;file" ] to verify a file's presence before reading or writing to it, but an attacker could replace the file with a malicious one in the intervening time, exploiting the window between the check and use to execute unintended or overwrite . These races are exacerbated in multi-process environments and can lead to if the script runs with elevated permissions. An untrusted PATH environment variable poses risks when Bash searches for executables in directories under attacker control, potentially executing malicious binaries instead of legitimate ones. If the PATH includes writable or remote directories, an attacker can place a trojanized version of a common command (e.g., ls) that performs harmful actions while mimicking normal behavior, leading to arbitrary code execution during script invocation. This vulnerability is common in shared or multi-user systems where PATH is modified without verification. Symlink abuse occurs when Bash scripts create or access temporary files without proper protections, allowing attackers to replace predictable temp files with symbolic links pointing to sensitive . For instance, if a script writes to /tmp/predictable_file without atomic operations, an attacker can symlink it to a critical file like /etc/[passwd](/page/Passwd), causing the script's write to overwrite or corrupt the target when executed. This can result in or unauthorized modifications, particularly in privileged scripts using fixed temp names.

Secure Coding Practices

Secure coding practices in Bash scripting emphasize preventing common security risks through rigorous input handling, controlled execution environments, and adherence to least-privilege principles. Developers should prioritize validating all user inputs to mitigate command injection attacks, where untrusted could alter script behavior. For instance, use the [[ ]] conditional construct instead of the single [ ] test command, as it performs no word splitting or pathname expansion on variables, reducing the risk of unintended command execution. Always quote variables (e.g., "$var") to preserve literal values and prevent globbing or splitting, and apply matching within [[ ]] for strict validation, such as [[ &#36;input =~ ^[a-zA-Z0-9]+$ ]] to allow only alphanumeric characters. These techniques align with input validation strategies that assume all external is potentially malicious, enforcing a "" approach to accept only known-good formats. Avoiding dangerous builtins like is crucial, as it can execute arbitrary strings constructed from untrusted input, leading to . Instead, opt for safer alternatives such as Bash arrays to build and iterate over dynamic command lists; for example, declare an array with cmds=("/bin/[ls](/page/Ls)" "-l") and execute via "${cmds[@]}" to separate arguments securely. Similarly, secure the PATH environment variable by using absolute paths for commands (e.g., /usr/bin/[ls](/page/Ls) instead of [ls](/page/Ls)) or invoking scripts in a clean environment with [env](/page/Env) -i PATH=/secure/path script.sh to prevent substitution of malicious binaries in user-controlled directories. File permissions must be managed proactively: set an appropriate at the script's start, such as umask 077 to restrict new files to owner-only access, and avoid operating in world-writable directories to prevent tampering. Robust error handling enhances security by failing fast and transparently on issues. Enable set -u (nounset) to treat references to unset variables as errors, preventing silent failures that could expose systems to unintended behavior, and set -e (errexit) to exit immediately upon any command returning a non-zero status, ensuring partial executions do not leave systems in insecure states. Combine these with set -o pipefail for pipelines to propagate errors from any segment. For logging, redirect errors and output to secure files (e.g., command 2>> /var/log/script_errors.log) while avoiding inclusion of sensitive data like passwords in messages; use tools like logger for syslog integration if needed. Adhere to least-privilege principles by designing scripts to run as non-root users whenever possible—check effective UID with id -u and exit if zero unless root access is explicitly required—and elevate privileges only for specific operations using sudo targeted commands. These practices minimize the attack surface and align with established Unix security models.

Compliance and Modes

POSIX Compliance Mode

Bash's POSIX compliance mode configures the shell to adhere more closely to the Shell and Utilities standard (IEEE Std 1003.1), limiting its behavior to ensure compatibility with other -compliant shells. This mode can be enabled by invoking Bash with the --posix command-line option, executing set -o posix within a running session, or starting Bash as sh after processing its startup files. Additionally, setting the POSIXLY_CORRECT environment variable forces Bash into this mode, which can be checked via echo $POSIXLY_CORRECT to verify its status. In POSIX mode, Bash disables several Bash-specific extensions (Bashisms) to enforce POSIX limits, such as treating the [[ compound command as an ordinary command rather than a test construct, thereby requiring the use of single brackets [ ] for conditionals. Arrays and associative arrays, which are non-POSIX features, are unavailable, and brace expansion—including range forms like {1..10}—is suppressed, so commands like echo {1..5} output the literal string instead of expanded numbers. Other changes include always enabling alias expansion even in non-interactive shells, performing no filename expansion on redirections (e.g., > *.txt fails unless the shell is interactive), and restricting tilde expansion to assignments preceding command names (e.g., PATH=~/bin expands, but echo ~ does not). Non-interactive shells exit immediately on errors like invalid variable assignments or syntax issues in eval, and special builtins such as export take precedence over functions of the same name. The primary benefit of POSIX compliance mode is enhanced portability, allowing scripts written for Bash to run predictably on other Unix-like systems using POSIX shells like those based on the original Bourne shell, without relying on proprietary extensions. This is particularly useful for system administration tasks or software packaging that must operate across diverse environments, such as Linux distributions and BSD variants. However, POSIX mode imposes limitations, including the absence of local variables within functions (no local or declare -l), restricted parameter expansions compared to full Bash capabilities, and incomplete implementation of some POSIX requirements, such as byte-oriented word splitting or the default behavior of echo and fc builtins. Bash in this mode reads POSIX-specific startup files like $ENV instead of its usual profiles, which may alter initialization but ensures stricter adherence. Common use cases include testing shell scripts for compatibility with /bin/sh on systems, developing portable automation tools, and ensuring compliance during software builds or deployments where Bash acts as a for traditional shells. For instance, developers might invoke bash --posix script.sh to validate that the script avoids Bashisms before committing it to a portable repository.

Restricted and Other Special Modes

Bash supports a restricted shell mode designed to create a more controlled environment, limiting certain user actions to enhance security in scenarios such as jailed user accounts or limited access systems. This mode is invoked by starting Bash with the -r or --restricted option, or by naming the executable rbash, which causes Bash to enter restricted mode automatically. In restricted mode, the shell behaves like standard Bash but imposes several key limitations: users cannot change directories with cd, modify critical environment variables like PATH, SHELL, ENV, BASH_ENV, or HISTFILE, or execute commands containing slashes in their names, which prevents absolute or relative path usage. Additionally, output redirection operators (such as >, >>, or >|), the exec builtin, and certain options to enable or command are disabled, while importing functions from the environment or sourcing files via SHELLOPTS is prohibited. These restrictions take effect after startup files are read, and they cannot be disabled once enabled, as commands like set +r or shopt -u restricted_shell are ignored. For practical deployment in secure environments, administrators often set the user's shell to /bin/rbash using chsh -s /bin/rbash, combined with a controlled PATH limited to trusted directories and a non-writable home directory to further constrain access. Beyond restricted mode, Bash offers other special invocation and runtime modes that alter its behavior for specific use cases. The login shell mode, activated with bash -l or bash --login, simulates a shell started by a login process, sourcing profile files like ~/.bash_profile or /etc/profile to initialize the environment appropriately for session starts. In non-interactive mode, invoked via bash -c "command_string" to execute a command string or bash -s to read from standard input, Bash omits interactive prompts, expands positional parameters from arguments or input, and exits on end-of-file (EOF) without further input. For debugging, the -x or --xtrace option enables trace mode, where Bash prints each command and its expanded arguments to before execution, aiding in script troubleshooting; the --debugger variant additionally sets the extdebug shell option for enhanced features like trap tracing. Command-line modes can be switched at runtime using the set builtin: set -o emacs enables Emacs-style key bindings for line (the default), while set -o vi switches to Vi-style , allowing modal insertion and command modes for navigation and modification via the Readline library. These modes apply to interactive shells and the read -e builtin, providing familiar interfaces for users preferring one editing paradigm over the other. The shopt builtin further allows toggling of optional shell behaviors that function as special modes for customization. For instance, shopt -s nocaseglob enables case-insensitive filename globbing during pathname expansion, useful in file systems where case sensitivity varies. Other options like nocasematch for case-insensitive pattern matching in case statements or [[ tests, and extglob for extended glob patterns (e.g., +(pattern) for one-or-more matches), can be enabled or disabled similarly to fine-tune expansion and matching behaviors without altering core shell invocation. These modes collectively allow Bash to adapt to diverse operational needs while maintaining its POSIX-compatible foundation.

References

Add your contribution
Related Hubs
User Avatar
No comments yet.