Complete Guide to Basic Bash Syntax

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

  1. Commands: Basic units with options and arguments
  2. Variables: Store data, with special variables for arguments and status
  3. Quoting: Essential for handling spaces and special characters
  4. Conditionals: if, case for decision making
  5. Loops: for, while, until for iteration
  6. Functions: Reusable code blocks
  7. Redirection: Control input/output streams
  8. Error Handling: Check exit status, use traps

Quick Reference

CategorySyntaxPurpose
Variable$var ${var}Access variable
Command sub$(cmd)Use command output
Arithmetic$((expr))Math operations
Test[ condition ]Conditional tests
Ifif [ cond ]; thenConditional execution
Forfor i in list; doIteration
Whilewhile [ cond ]; doConditional loop
Functionfunc() { }Define function
Casecase $var inPattern matching
Here doc<< EOFMulti-line input

Best Practices Checklist

  • [ ] Always quote variables, especially if they might contain spaces
  • [ ] Use #!/bin/bash shebang for Bash scripts
  • [ ] Set set -euo pipefail for 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 -n for syntax errors

Mastering Bash syntax is essential for effective command-line usage and scripting. Practice these concepts regularly to become proficient in shell programming.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper