Introduction to Bash Syntax
Bash (Bourne Again SHell) is the default shell on most Linux distributions and macOS. Understanding its syntax is fundamental for writing shell scripts, automating tasks, and effectively using the command line. This guide covers the essential syntax elements you need to know.
Key Concepts
- Commands: The basic units of execution
- Variables: Store and manipulate data
- Control Structures: Conditionals and loops for flow control
- Functions: Reusable code blocks
- Input/Output: Handle data streams and user interaction
1. Basic Command Structure
Simple Commands
# Basic command syntax command [options] [arguments] # Examples ls -la /home echo "Hello World" grep "pattern" file.txt # Multiple commands on one line (separated by ;) cd /tmp; ls -la; pwd # Commands can span multiple lines (using \) echo "This is a very long line that \ needs to be continued on the next line" # Comments (ignored by shell) # This is a comment ls -la # This is also a comment
Command Types
# 1. Built-in shell commands
cd /tmp # Change directory
echo "Hello" # Print text
pwd # Print working directory
type cd # Show command type
# 2. External commands (binaries)
/bin/ls # Full path
ls # Found in PATH
which ls # Show location of command
# 3. Aliases
alias ll='ls -la'
ll # Runs ls -la
# 4. Functions
myfunc() {
echo "This is a function"
}
2. Variables
Variable Declaration and Usage
# Variable assignment (no spaces around =)
name="John"
age=25
city="New York"
# Using variables ($ prefix)
echo $name
echo "My name is $name"
echo "My name is ${name}" # Braces for clarity
# Variable scope
local var="local" # Inside functions
export VAR="global" # Environment variable
# Read-only variables
readonly PI=3.14159
# PI=3.14 # Error: cannot change
# Unsetting variables
unset name
Special Variables
# Positional parameters $0 # Script name $1 # First argument $2 # Second argument $# # Number of arguments $@ # All arguments as separate words $* # All arguments as single string # Process status $$ # Current script PID $? # Exit status of last command $! # PID of last background job $- # Current shell options # Example #!/bin/bash echo "Script name: $0" echo "First argument: $1" echo "All arguments: $@" echo "Number of arguments: $#" echo "Exit status: $?"
Arrays
# Declaring arrays
fruits=("apple" "banana" "cherry")
numbers=(1 2 3 4 5)
mixed=("text" 42 "more text")
# Accessing array elements
echo ${fruits[0]} # First element
echo ${fruits[1]} # Second element
echo ${fruits[@]} # All elements
echo ${#fruits[@]} # Array length
# Array operations
fruits+=("orange") # Add element
unset fruits[1] # Remove element
echo ${!fruits[@]} # Get indices
# Associative arrays (Bash 4+)
declare -A user
user[name]="John"
user[age]=30
user[city]="Boston"
echo ${user[name]}
3. Quotes and Escaping
Quote Types
# Double quotes - variable expansion, command substitution name="John" echo "Hello $name" # Hello John echo "Today is $(date)" # Today is ... # Single quotes - literal strings echo 'Hello $name' # Hello $name echo 'Today is $(date)' # Today is $(date) # Backticks - old command substitution (avoid) echo `date` # Same as $(date) # No quotes - word splitting, glob expansion echo Hello World # Hello World (multiple spaces collapse) echo *.txt # Lists all txt files # Examples path="/home/user/my file" # Space needs quoting echo "$path" # Safe echo $path # Splits into two words
Escaping Special Characters
# Escape with backslash
echo "Price: \$100" # Price: $100
echo "File: file\ with\ spaces" # File: file with spaces
echo "Newline: \n" # Newline: \n (literal)
# Special characters to escape
# $ & * ? [ ] { } | \ ; ' " ` ~
# Examples
echo "Home directory: ~" # Home directory: ~
echo "Home directory: \~" # Home directory: ~ (escaped)
grep "\[error\]" log.txt # Search for [error]
4. Command Substitution
Syntax and Usage
# Modern syntax (preferred) current_date=$(date) files=$(ls -la) count=$(wc -l < file.txt) # Old syntax (backticks) current_date=`date` files=`ls -la` # Nested command substitution dir_count=$(echo $(ls -la | wc -l)) # Examples echo "Today is $(date +%Y-%m-%d)" echo "There are $(find . -type f | wc -l) files" echo "Current directory: $(basename $(pwd))" # In calculations total=$(($(wc -l < file1.txt) + $(wc -l < file2.txt)))
5. Arithmetic Operations
Integer Arithmetic
# Arithmetic expansion result=$((5 + 3)) echo $result # 8 # Operators $((a + b)) # Addition $((a - b)) # Subtraction $((a * b)) # Multiplication $((a / b)) # Division (integer) $((a % b)) # Modulo $((a ** b)) # Exponentiation # Increment/decrement x=5 ((x++)) # Post-increment ((++x)) # Pre-increment ((x--)) # Post-decrement ((--x)) # Pre-decrement # Compound assignment ((x += 5)) # x = x + 5 ((x *= 2)) # x = x * 2 # Examples a=10 b=3 echo $((a + b)) # 13 echo $((a - b)) # 7 echo $((a * b)) # 30 echo $((a / b)) # 3 echo $((a % b)) # 1 echo $((a ** b)) # 1000
Using let and expr
# let command let "result = 5 + 3" let x++ # expr command (old style) result=$(expr 5 + 3) # Spaces required result=$(expr $a \* $b) # * must be escaped
6. Conditional Statements
if-then-else
# Basic if statement if [ condition ]; then commands fi # if-else if [ condition ]; then commands else commands fi # if-elif-else if [ condition1 ]; then commands1 elif [ condition2 ]; then commands2 else commands3 fi # Examples if [ -f "$file" ]; then echo "$file exists" fi if [ "$name" = "John" ]; then echo "Hello John" else echo "You're not John" fi # Test command alternatives if test -f "$file"; then echo "File exists" fi if [[ "$name" == "John" ]]; then echo "Hello John" fi
Test Conditions
# File tests [ -f "$file" ] # Is a regular file [ -d "$dir" ] # Is a directory [ -e "$path" ] # Exists [ -r "$file" ] # Is readable [ -w "$file" ] # Is writable [ -x "$file" ] # Is executable [ -s "$file" ] # Size > 0 [ -L "$file" ] # Is a symlink # String tests [ -z "$str" ] # String is empty [ -n "$str" ] # String is not empty [ "$s1" = "$s2" ] # Strings equal [ "$s1" != "$s2" ] # Strings not equal [ "$s1" < "$s2" ] # Lexicographic less than [ "$s1" > "$s2" ] # Lexicographic greater than # Numeric tests [ "$a" -eq "$b" ] # Equal [ "$a" -ne "$b" ] # Not equal [ "$a" -lt "$b" ] # Less than [ "$a" -le "$b" ] # Less or equal [ "$a" -gt "$b" ] # Greater than [ "$a" -ge "$b" ] # Greater or equal # Logical operators [ ! condition ] # NOT [ cond1 -a cond2 ] # AND (old style) [ cond1 -o cond2 ] # OR (old style) [[ cond1 && cond2 ]] # AND (new style) [[ cond1 || cond2 ]] # OR (new style) # Examples if [ -f "$file" ] && [ -r "$file" ]; then echo "File exists and is readable" fi if [[ $age -gt 18 && $age -lt 65 ]]; then echo "Working age" fi
case Statement
# Basic case syntax
case $variable in
pattern1)
commands1
;;
pattern2)
commands2
;;
*)
default_commands
;;
esac
# Examples
case "$1" in
start)
echo "Starting service..."
systemctl start myservice
;;
stop)
echo "Stopping service..."
systemctl stop myservice
;;
restart)
echo "Restarting service..."
systemctl restart myservice
;;
status)
systemctl status myservice
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
# Patterns with wildcards
case "$filename" in
*.txt)
echo "Text file"
;;
*.jpg|*.jpeg|*.png)
echo "Image file"
;;
*.sh)
echo "Shell script"
;;
*)
echo "Unknown type"
;;
esac
# Character classes
case "$char" in
[0-9])
echo "Digit"
;;
[a-zA-Z])
echo "Letter"
;;
*)
echo "Special character"
;;
esac
7. Loops
for Loop
# Loop over list
for item in list; do
commands
done
# Loop over range
for i in {1..5}; do
echo "Number: $i"
done
# C-style for loop
for ((i=0; i<5; i++)); do
echo "i = $i"
done
# Loop over files
for file in *.txt; do
echo "Processing: $file"
done
# Loop over command output
for user in $(cut -d: -f1 /etc/passwd); do
echo "User: $user"
done
# Loop with array
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done
# Loop with index
for i in "${!fruits[@]}"; do
echo "$i: ${fruits[$i]}"
done
# Examples
# Sum numbers
sum=0
for i in {1..10}; do
sum=$((sum + i))
done
echo "Sum: $sum"
# Process files
for file in *.log; do
if [ -f "$file" ]; then
echo "Compressing $file"
gzip "$file"
fi
done
while Loop
# Basic while loop while [ condition ]; do commands done # Read file line by line while IFS= read -r line; do echo "Line: $line" done < file.txt # Infinite loop while true; do echo "Press Ctrl+C to exit" sleep 1 done # Counter loop count=1 while [ $count -le 5 ]; do echo "Count: $count" count=$((count + 1)) done # Reading user input while read -p "Enter command: " cmd; do if [ "$cmd" = "quit" ]; then break fi echo "You entered: $cmd" done # Process monitoring while pgrep -x "process" >/dev/null; do echo "Process still running" sleep 5 done # Examples # Wait for file while [ ! -f "/tmp/ready.txt" ]; do echo "Waiting for file..." sleep 2 done echo "File found!" # Menu loop while true; do echo "1. Option 1" echo "2. Option 2" echo "3. Quit" read -p "Choose: " choice case $choice in 1) echo "Option 1 chosen" ;; 2) echo "Option 2 chosen" ;; 3) break ;; *) echo "Invalid choice" ;; esac done
until Loop
# Basic until loop (opposite of while) until [ condition ]; do commands done # Examples count=1 until [ $count -gt 5 ]; do echo "Count: $count" count=$((count + 1)) done # Wait for process to finish until pgrep -x "process" >/dev/null; do sleep 1 done echo "Process started" # Wait for network until ping -c1 google.com >/dev/null 2>&1; do echo "Waiting for network..." sleep 5 done echo "Network is up"
Loop Control
# break - exit loop
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
# continue - skip to next iteration
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo $i # Prints odd numbers
done
# break with level (nested loops)
for i in {1..3}; do
for j in {1..3}; do
if [ $i -eq 2 ] && [ $j -eq 2 ]; then
break 2 # Break out of both loops
fi
echo "$i,$j"
done
done
8. Functions
Function Definition
# Function syntax
function_name() {
commands
}
# Alternative syntax
function function_name {
commands
}
# Simple function
greet() {
echo "Hello, $1!"
}
# Call function
greet "John"
# Function with multiple parameters
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
result=$(add 5 3)
echo "Result: $result"
Function Examples
# Return values (via echo)
get_date() {
echo $(date +%Y-%m-%d)
}
today=$(get_date)
echo "Today: $today"
# Return exit status
is_file_exists() {
if [ -f "$1" ]; then
return 0 # Success
else
return 1 # Failure
fi
}
if is_file_exists "/etc/passwd"; then
echo "File exists"
fi
# Local variables
calculate() {
local a=$1
local b=$2
local result=$((a * b))
echo $result
}
# Function with options
log() {
local level="INFO"
local message=""
while getopts "l:m:" opt; do
case $opt in
l) level="$OPTARG" ;;
m) message="$OPTARG" ;;
esac
done
echo "[$level] $message"
}
log -l ERROR -m "Something went wrong"
# Recursive function
factorial() {
if [ $1 -le 1 ]; then
echo 1
else
local prev=$(factorial $(($1 - 1)))
echo $(($1 * prev))
fi
}
echo "Factorial 5: $(factorial 5)"
9. Input/Output Redirection
File Descriptors
# Standard file descriptors 0: stdin (standard input) 1: stdout (standard output) 2: stderr (standard error) # Redirection operators > # Redirect stdout to file (overwrite) >> # Redirect stdout to file (append) < # Redirect stdin from file 2> # Redirect stderr to file &> # Redirect both stdout and stderr # Examples ls > output.txt # Save listing to file echo "Hello" >> log.txt # Append to file sort < input.txt # Read from file grep error 2> errors.txt # Save errors only command &> all.txt # Save both output and errors # Discard output command > /dev/null # Discard stdout command 2> /dev/null # Discard stderr command &> /dev/null # Discard both
Here Documents and Here Strings
# Here document (heredoc) cat << EOF This is a multiline text block that will be passed as input. EOF # With variable expansion name="John" cat << EOF Hello $name, This is a message. EOF # Without variable expansion (quote delimiter) cat << 'EOF' This will not expand $variables EOF # Here string grep "pattern" <<< "This is a test string" read first <<< "apple banana cherry" # Examples # Create file with heredoc cat > config.txt << EOF host=localhost port=8080 debug=true EOF # Execute SQL mysql << EOF SELECT * FROM users WHERE active = 1; EOF
Pipes
# Basic pipe command1 | command2 # Common examples ls -la | grep ".txt" cat file.txt | sort | uniq ps aux | grep python | wc -l # Multiple pipes cat access.log | grep "ERROR" | cut -d' ' -f1 | sort | uniq -c # Named pipes (FIFO) mkfifo mypipe command1 > mypipe & command2 < mypipe # Process substitution diff <(ls dir1) <(ls dir2) while read line; do echo "$line"; done < <(command)
10. String Manipulation
String Operations
# String length
str="Hello World"
echo ${#str} # 11
# Substring extraction
echo ${str:6} # World (from position 6)
echo ${str:6:5} # Worl (5 chars from pos 6)
# Substring removal (from beginning)
echo ${str#H* } # World (remove shortest match)
echo ${str##* } # World (remove longest match)
# Substring removal (from end)
echo ${str% *} # Hello (remove shortest from end)
echo ${str%% *} # Hello (remove longest from end)
# Replace
echo ${str/Hello/Hi} # Hi World (first match)
echo ${str//o/O} # HellO WOrld (all matches)
echo ${str/#Hello/Hi} # Hi World (beginning)
echo ${str/%World/All} # Hello All (end)
# Case conversion
echo ${str,,} # hello world (lowercase)
echo ${str^^} # HELLO WORLD (uppercase)
echo ${str,} # hello World (first char lowercase)
echo ${str^} # Hello World (first char uppercase)
Pattern Matching
# Check if string matches pattern if [[ "$filename" == *.txt ]]; then echo "Text file" fi # Pattern matching operators [[ "$str" =~ ^[0-9]+$ ]] # Regex match [[ "$str" == "value" ]] # Exact match [[ "$str" == *pattern* ]] # Wildcard match # Examples email="[email protected]" if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "Valid email" fi phone="123-456-7890" if [[ "$phone" =~ ^[0-9]{3}-[0-9]{3}-[0-9]{4}$ ]]; then echo "Valid phone" fi
11. Arrays and Lists
Array Operations
# Indexed arrays
fruits=("apple" "banana" "cherry")
numbers=([0]=10 [1]=20 [2]=30)
# Access
echo ${fruits[0]} # apple
echo ${fruits[@]} # all elements
echo ${#fruits[@]} # array length
echo ${!fruits[@]} # array indices
# Add/Modify
fruits[3]="orange" # add element
fruits+=("grape") # append
fruits=("${fruits[@]}" "kiwi") # another append
# Remove
unset fruits[1] # remove element
# Slice
subset=("${fruits[@]:1:2}") # elements 1-2
# Loop through array
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# Associative arrays (Bash 4+)
declare -A user
user[name]="John"
user[age]=30
user[city]="NYC"
echo ${user[name]} # John
echo ${!user[@]} # keys: name age city
echo ${user[@]} # values: John 30 NYC
# Loop through associative array
for key in "${!user[@]}"; do
echo "$key: ${user[$key]}"
done
12. Error Handling
Exit Status and Error Checking
# Check exit status
command
if [ $? -eq 0 ]; then
echo "Success"
else
echo "Failed"
fi
# OR operator (run if previous failed)
command1 || command2 # Run command2 if command1 fails
# AND operator (run if previous succeeded)
command1 && command2 # Run command2 if command1 succeeds
# Examples
mkdir /tmp/test && cd /tmp/test || exit 1
# Set exit on error
set -e # Exit on any error
set -u # Exit on undefined variable
set -o pipefail # Exit on pipe failure
# Trap errors
trap 'echo "Error on line $LINENO"' ERR
# Custom error function
error_exit() {
echo "$1" >&2
exit 1
}
# Usage
[ -f "$file" ] || error_exit "File not found: $file"
13. Command-line Arguments
Parsing Arguments
#!/bin/bash
# Simple argument handling
echo "Script: $0"
echo "Arguments: $@"
echo "Count: $#"
# Loop through arguments
for arg in "$@"; do
echo "Arg: $arg"
done
# Using getopts for option parsing
while getopts "f:o:vh" opt; do
case $opt in
f) input_file="$OPTARG" ;;
o) output_file="$OPTARG" ;;
v) verbose=true ;;
h) show_help
exit 0 ;;
*) echo "Invalid option"
exit 1 ;;
esac
done
shift $((OPTIND-1)) # Remove processed options
# Remaining arguments are positional
echo "Positional args: $@"
# Example with default values
verbose=${verbose:-false}
input_file=${input_file:-/dev/stdin}
output_file=${output_file:-/dev/stdout}
14. Common Patterns and Best Practices
Script Header
#!/bin/bash # Script Name: myscript.sh # Description: Does something useful # Author: Your Name # Date: 2024-03-12 set -euo pipefail # Strict mode IFS=$'\n\t' # Safe IFS # Script code follows...
Safe Patterns
# Always quote variables
file="my file.txt"
cat "$file" # Good
cat $file # Bad (breaks with spaces)
# Use default values
name=${1:-"default"}
echo ${var:-"default"} # Use default if unset
echo ${var:="default"} # Set default if unset
# Check if variable is set
if [ -z "${var+x}" ]; then
echo "var is unset"
fi
# Temporary files
tempfile=$(mktemp)
trap "rm -f $tempfile" EXIT
# Safe file operations
if [ -f "$file" ] && [ -r "$file" ]; then
process < "$file"
fi
# Read file safely
while IFS= read -r line; do
echo "Line: $line"
done < "$file"
Debugging
# Enable debug mode
set -x # Print commands before executing
set -v # Print input lines as read
# Debug sections
set -x
# debug code here
set +x
# Check syntax without executing
bash -n script.sh
# Trace execution
bash -x script.sh
# Debug function
debug() {
if [ "$DEBUG" = true ]; then
echo "DEBUG: $*" >&2
fi
}
DEBUG=true
debug "Variable x=$x"
Conclusion
Key Takeaways
- Commands: Basic units with options and arguments
- Variables: Store data, with special variables for arguments and status
- Quoting: Essential for handling spaces and special characters
- Conditionals: if, case for decision making
- Loops: for, while, until for iteration
- Functions: Reusable code blocks
- Redirection: Control input/output streams
- Error Handling: Check exit status, use traps
Quick Reference
| Category | Syntax | Purpose |
|---|---|---|
| Variable | $var ${var} | Access variable |
| Command sub | $(cmd) | Use command output |
| Arithmetic | $((expr)) | Math operations |
| Test | [ condition ] | Conditional tests |
| If | if [ cond ]; then | Conditional execution |
| For | for i in list; do | Iteration |
| While | while [ cond ]; do | Conditional loop |
| Function | func() { } | Define function |
| Case | case $var in | Pattern matching |
| Here doc | << EOF | Multi-line input |
Best Practices Checklist
- [ ] Always quote variables, especially if they might contain spaces
- [ ] Use
#!/bin/bashshebang for Bash scripts - [ ] Set
set -euo pipefailfor strict error handling - [ ] Use functions for reusable code
- [ ] Add comments for complex logic
- [ ] Check return codes of commands
- [ ] Use meaningful variable names
- [ ] Validate input parameters
- [ ] Clean up temporary files
- [ ] Test scripts with
bash -nfor syntax errors
Mastering Bash syntax is essential for effective command-line usage and scripting. Practice these concepts regularly to become proficient in shell programming.