Preface
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:
- Terminal.app
- iTerm2
- The terminal in our IDEs
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:
- Bourne Shell (often just called shell or
sh
) - Bash (Technically GNU Bash)
- ZSH
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:
#!/bin/sh
#!/usr/bin/env sh
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"
> ./example.sh "hello world"
hello world
a space is considered a separator, ensure you use quotes or escape the space if your input contains a space.
> ./example.sh hello world
hello
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
hello
Double Arrow Brace (>>
)
The Double Arrow Brace (>>
) appends the output of a binary to file
> echo "hello" > ./world.txt
> cat ./world.txt
hello
hello
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!"}
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:
:-
Substitution with a default value:=
Substitution with default assignment:+
Substitution for actual value:?
Substitution with value check
These are explained in further detail in other guides2.
Single Quotes (''
)
This represents a plain string, you cannot use variables inside these.
> MY_VAR='HELLO WORLD'
> echo '$MY_VAR'
$MY_VAR
Double quotes (""
)
Fancy pants string, you can use variables inside them.
> MY_VAR="HELLO WORLD"
> echo "$MY_VAR"
HELLO WORLD
You should always wrap variables in double quotes when using them as input to a binary.
> MY_VAR="HELLO WORLD"
> echo $MY_VAR
HELLO
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
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
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
for
allows for easy iteration over files or arrays.
for file in *.png; do
echo "$file"
done;
Bash & ZSH have support for arrays:
declare -a ARRAY_VAR=("foo" "bar" "baz")
for variable in "${ARRAY_VAR[@]}"; do
echo "$variable"
done
if
if
has different syntax than expected depending on your shell.
Bourne Shell (SH/BASH/ZSH)
You can pass flags to if
to handle comparisons3.
A="FOO"
B="BAR"
if $A -eq $B; then
echo "A is equal to B"
elif $A -ne $B; then
echo "A is not equal to B"
done
Bash & ZSH
Bash 2.0.2 introduced the [[]]
“extend test command” which allows for more standard syntax when doing comparisons.
A="FOO"
B="BAR"
if [[ "$A" == "$B" ]]; then
echo "A is equal to B"
elif [[ "$A" != "$B" ]]; then
echo "A is not equal to B"
done
You can also use the original flags inside the square brackets:
A="FOO"
B="BAR"
if [[ "$A" -eq "$B" ]]; then
echo "A is equal to B"
elif [[ "$A" -ne "$B" ]]; then
echo "A is not equal to B"
done
Sed
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
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.