The scripting language of the terminal, automates the commands you'd otherwise type by hand. bash is the most common shell on Linux and (via WSL/Git Bash) widely used on Windows too; macOS ships zsh by default but is largely bash-compatible for scripting purposes.
Running a script
#!/usr/bin/env bash
set -euo pipefail # exit on error, undefined var, or failed pipe, put this in almost every script
echo "Hello, world"
chmod +x script.sh
./script.sh
set -euo pipefail is close to a universal best practice: without it, a script silently continues past a failed command, which is rarely what you want.
Variables
name="Ada"
count=0
echo "Hello, $name! Count: $count"
echo "Hello, ${name}!" # braces avoid ambiguity when concatenating
# command substitution
current_dir=$(pwd)
files=$(ls | wc -l)
No spaces around = in assignment, name = "Ada" is a syntax error (Bash parses it as running a command called name with arguments).
Conditionals
if [ -f "config.yml" ]; then
echo "Config found"
elif [ -d "config" ]; then
echo "Config directory found"
else
echo "No config"
fi
# [[ ]] is the more modern, more forgiving test syntax
if [[ "$name" == "Ada" && -n "$count" ]]; then
echo "Match"
fi
Loops
for file in *.txt; do
echo "Processing $file"
done
for i in {1..5}; do
echo "Iteration $i"
done
while read -r line; do
echo "Line: $line"
done < input.txt
Functions
greet() {
local name="$1" # local scopes the variable to the function
echo "Hello, $name!"
}
greet "Ada"
Pipes and redirection
cat access.log | grep "ERROR" | wc -l # pipe output between commands
echo "log line" >> output.log # append
echo "log line" > output.log # overwrite
command 2> errors.log # redirect stderr only
command > all.log 2>&1 # redirect both stdout and stderr
Argument handling
#!/usr/bin/env bash
echo "Script name: $0"
echo "First arg: $1"
echo "All args: $@"
echo "Arg count: $#"
Common gotchas
- Unquoted variables (
$nameinstead of"$name") break on spaces/glob characters in the value, quote variables almost everywhere, including in[ ]tests. rm -rf $dirwith an empty or unset$dircan expand torm -rf /in the worst case, always quote, and considerset -uto fail loudly on unset variables instead.- Exit codes matter for scripting logic (
0= success, non-zero = failure), check$?or useif command; thendirectly rather than parsing command output to detect failure.