Wed 28 Jun 2017 — Thu 16 May 2019


Bash is an awful language, which we nonetheless have to use because it's available everywhere. It's also quite effective for many tasks, despite its awfulness.

Configured by "$HOME/.bashrc" . Alternatively, you can use the –rcfile flag to specify a different file.

Bash can accept commands on the command line (an interactive shell), from standard in, or from a file. When running a file, always include a shebang #!/usr/bin/env bash.


–posix flag makes it behave standard.

This happens automatically if you launch bash using the sh command.

Login Shell

If it is run with no arguments, - or –login, it is assumed to be a login shell.

In this case, it will look in ~/.profile instead of ~/.bashrc.

Non-interactive Shell

If we passed Bash some arguments when we ran it, then it will be non-interactive.

It looks at $BASHENV to find a file.


Parameter assignment is done using PARAM=something.

Get the value of a parameter using "$PARAM" or "${PARAM}". You can leave out the double quotes, but it's a bad idea.

Parameters are dynamically scoped in Bash, and always contain strings.

Parameter Expansions

There are a variety of string substitutions that you can perform when you use a parameter.

use something if parameter isn't set.
set parameter to something if it isn't already set.
bail with an error if the parameter isn't set.
cut the end off parameter.
cut the beginning and end off parameter.
length of parameter.
delete substring matching something from the start of parameter. Use double ## to be greedy.
delete substring matching something from the end of parameter. Use double %% to be greedy.

There are more, but htis is a reasonable start.

Special Parameters

$1, $2, and so on
positional parameter
all positional parameters concatenated
all positional parameters as separate words
number of positional parameters
last exit code
current shell's option flags
process id (subshell return parent's process id)
process id of last backgrounded job
script name. Shell name in interactive mode.
last positional parameter to the most recently invoked command

There are many others.


Strings quoted with 'my-string' are literal. They also use the C backslash escape sequence.

Strings quoted with "my-string" may be expanded. Additionally, most of the backslash escape sequences are disabled.

Strings in Bash are null-terminated C-style strings.

Brace Expansion

You can generate strings using prefix{a,b,c}postfix.

This will create a string for each element in the list between the braces.

Command Substution

$(command) inside a string runs the command in a subshell, and replaces that section of the string with the stdout of that command.

Process Substitution

<(command) and >(command) both substitute in a filename.

For <(command), this filename will contain the stdout of the command.

For >(command), writing to this filename will send stdin to the command.

This is done by creating named pipes. You can also arrange it manually with mkinfo.

Control Flow

Chaining Commands

&& and || mean the same as in C.

& means execute the previous command in the background. The return status is 0. Never follow & with a ;, because it will break it.

; means sequence. Wait for the thing on the left before doing the thing on the right.

All of && || & ; create command lists.

() puts the commands list inside it into a subshell.

{} groups the commands list inside it, and returns the combined result. This is used to override operator precedence rules.


  • | connects stdout of command1 to stdin of command2.
  • |& does stderr and stdout. It means 2>&1 |.

These connections happen before any other redirection.

Pipes return the output of the last command in the pipe. You can turn on pipefail to make it return the last non-zero output instead.

Each command in a pipe is executed in a subshell.


The if statement checks the return value of a predicate command.

You can use the double square brackets to test a thing, or the test command.

if [[ -z "$SOME_PARAMETER" ]]; then
    echo "SOME_PARAMETER was empty"
    echo "SOME_PARAMETER had content"

The case statement pattern matches against a string.

case "$MY_PAREMTER" in
    a* | b*) echo "MY_PAREMTER began with 'a' or 'b'";;
    *) echo "MY_PAREMTER was something else";;

There's an alternative ;& syntax to allow multiple cases to match. Don't use this, it will become really confusing.

The select statement prompts the user with a numbered menu. This seems a bit useless?


Bash supports:

until expression; do something; done
do-until loop
while expression; do something; done
do-while loop
for (( init-command ; predicate-command ; increment-command )) ; do something; done
for loop
for token in tokens; do something-with "$token"; done
for-each loop

Looping constructs have break and continue builtin statements, which behave the same as in other languages.


Functions in bash are really procedures.

While they usually look sort of Algol-like, you can also replace the curly braces {} with a different type of compound command.

Function calls look like normal bash commands: my-function arg1 arg2. The arguments are available as "$1", "$2" and so on.


Write (( expression )) to use arithmetic mode. Write $(( expression )) to substitute arithmetic results into a string.

Arithmetic in Bash is integer only, and a bit rubbish. Try to use a more general-purpose programming language for numbers work.

Debugging and Profiling

You can put the time command in front of a pipe and it will do clever things.