We constantly refer to “Bash” around the office in reference to our scripts but what actually is Bash and can we use it more effectively?

Bash is a Unix Shell, that runs inside a Terminal instance.

Your Terminal is a piece of software on your computer that can interact with your selected Unix Shell. Some of the common terminal tools we see and use are:

The Terminal program handles input, output and errors between you and your shell.

The most common shell is Bash which is shorthand for its full name, “Bourne Again shell”. Bourne Again since it’s intended to be used as a replacement for the Bourne Shell (sh)1. Bash can run scripts written for the Bourne Shell but not vice versa

MacOS no longer ships with Bash as the default shell and instead uses ZSH. ZSH can run Bourne Shell scripts, but it cannot run Bash scripts.

So there are 3 main Unix Shells we interact with:

So which shell do our scripts use?!

All scripts we use are prepended with the file extension .sh, however this file extension is not what is used to determine the shell!

Enter, the shebang!

#!/usr/bin/env bash

This line sits at the top of all of our scripts and tells our Terminal program what Unix Shell to run the script against.

Most Unix old heads will push for you to use either:

Since it’s the most widely supported and come pre-installed on most unix/unix-like systems: MacOS, BSD, GNU Linux, etc.

MacOS & BSD do not ship with most GNU utils. MacOS ships with GNU Bash, but not anything else. causing confusion when using tools like sed or date which have BSD and GNU versions.

The shebang tells your Terminal what program to run the following file under. This can be anything as long as it’s a valid path to a binary.

Syntax Examples

The following are some handy snippets that help explain the Syntax of Shell Scripts. Bash, ZSH and Shell all have extremely similar syntax. There are no types, and everything is considered “text”.

Binaries on your system can pass data via stdin and stdout, stdin can be referenced in your scripts with numbered variables:

#!/usr/bin/env bash
echo "$1"
> ./ "hello world"

hello world

a space is considered a separator, ensure you use quotes or escape the space if your input contains a space.

> ./ hello world


All 3 shells offer operators for moving your binaries stdout into the stdin of another binary.

Pipe (|)

The Pipe (|) operator directly connects the output of one program to the input another

Arrow Brace (>)

The Arrow Brace (>) writes the output of a binary to file

> echo "hello" > ./world.txt
> cat ./world.txt


Double Arrow Brace (>>)

The Double Arrow Brace (>>) appends the output of a binary to file

> echo "hello" > ./world.txt
> cat ./world.txt


Subshells $()

Subshells fork the current process and runs the inner content inside another process, it does not have access to any of its parents environment.
The stdout of the subshell is “returned”.

MY_VAR=$(echo "Hello World")
echo "$MY_VAR"

Variable Expansion ${}

This one’s a bit complex, it allows operations to be run against a variable before the line is executed.

> MY_VAR=""
> echo ${MY_VAR:-"Missing!"}


We use the :- operator here to substitute a default value if the left hand is missing. This is executed before the full line (with echo) is run.

There are a few operators:

These are explained in further detail in other guides2.

Single Quotes ('')

This represents a plain string, you cannot use variables inside these.

> echo '$MY_VAR'


Double quotes ("")

Fancy pants string, you can use variables inside them.

> echo "$MY_VAR"


You should always wrap variables in double quotes when using them as input to a binary.

> echo $MY_VAR

Error: unknown command WORLD

Useful binaries & builtins

A lot of Unix-like systems ship with these tools, however be weary of slight syntactical differences between the BSD/GNU versions of these tools.


cat is commonly used to output the contents of a file

> cat hello.txt

Hello world!
This is a second line
and a third line


grep lets you easily search and filter stdout or files.

> cat hello.txt | grep "world"

Hello world!
> grep "world" -f hello.txt

Hello world!

You can also pass a Regular Expression to grep. Linux Grep & macOS Grep have minor differences in how they handle Regex, it’s almost never an issue, but it’s worth keeping in mind. (-o flag)

cat .zsh_history | grep -E '[A-z]([0-9]*)'


for allows for easy iteration over files or arrays.

for file in *.png; do
  echo "$file"

Bash & ZSH have support for arrays:

declare -a ARRAY_VAR=("foo" "bar" "baz") 

for variable in "${ARRAY_VAR[@]}"; do
  echo "$variable"


if has different syntax than expected depending on your shell.

Bourne Shell (SH/BASH/ZSH)

You can pass flags to if to handle comparisons3.


if $A -eq $B; then
  echo "A is equal to B"
elif $A -ne $B; then
  echo "A is not equal to B"

Bash & ZSH

Bash 2.0.2 introduced the [[]] “extend test command” which allows for more standard syntax when doing comparisons.


if [[ "$A" == "$B" ]]; then
  echo "A is equal to B"
elif [[ "$A" != "$B" ]]; then
  echo "A is not equal to B"

You can also use the original flags inside the square brackets:


if [[ "$A" -eq "$B" ]]; then
  echo "A is equal to B"
elif [[ "$A" -ne "$B" ]]; then
  echo "A is not equal to B"


Sed allows you to edit input or a file with a regular expression. Sed also has differences across Unix-like systems4.

echo "Hello world" | sed 's/world/mars/g'

On BSD sed you need to supply -i when editing a file, this is not mandatory on GNU sed.

sed -i .bak -e "s/world/mars/g" hello.txt


JQ is not a built-in tool, but we use it often enough that it’s worth mentioning. JQ allows you to parse JSON objects and offers almost all the utilities JS offers for working with Objects: find, map, reduce, filter etc. It’s quite extensive and worth reading their docs5 about its capabilities.

© 2024 Pfych