Skip to main content

Advanced Features

Parameter Expansion

Basic Parameter Expansion

# Variable expansion
name="John"
echo $name # John
echo ${name} # John (explicit form)
echo ${name}Doe # JohnDoe

# Array expansion
arr=(a b c)
echo ${arr[0]} # First element
echo ${arr[@]} # All elements
echo ${arr[*]} # All elements (different behavior with quoting)
echo ${#arr[@]} # Array length
echo ${!arr[@]} # Array indices

Default Values

# Default value if variable is unset
echo ${name:-"default"} # Use "default" if name is unset
echo ${name:="default"} # Set name to "default" if unset and use it
echo ${name:+"alternative"} # Use "alternative" if name is set
echo ${name:?"error"} # Error if name is unset

# Examples
config_file=${1:-"/etc/config"} # Use first argument or default
database_url=${DATABASE_URL:-"localhost"} # Environment variable with default

String Manipulation

string="Hello World"

# Length
echo ${#string} # 11

# Substring extraction
echo ${string:0:5} # Hello (from position 0, length 5)
echo ${string:6} # World (from position 6 to end)
echo ${string:(-5)} # World (last 5 characters)
echo ${string:(-5):3} # Wor (last 5 characters, take 3)

# Pattern removal
path="/usr/local/bin/script.sh"
echo ${path#*/} # usr/local/bin/script.sh (remove shortest match from start)
echo ${path##*/} # script.sh (remove longest match from start)
echo ${path%/*} # /usr/local/bin (remove shortest match from end)
echo ${path%%/*} # (remove longest match from end)

# Pattern replacement
echo ${string/o/0} # Hell0 World (replace first occurrence)
echo ${string//o/0} # Hell0 W0rld (replace all occurrences)
echo ${string/#H/h} # hello World (replace at beginning)
echo ${string/%d/D} # Hello WorlD (replace at end)

Case Conversion

string="Hello World"

# Case conversion (Bash 4+)
echo ${string^} # Hello World (first character uppercase)
echo ${string^^} # HELLO WORLD (all uppercase)
echo ${string,} # hello World (first character lowercase)
echo ${string,,} # hello world (all lowercase)
echo ${string~} # hello World (toggle first character case)
echo ${string~~} # hELLO wORLD (toggle all case)

Arrays

Indexed Arrays

# Array declaration
declare -a fruits
fruits=("apple" "banana" "cherry")

# Alternative declarations
fruits[0]="apple"
fruits[1]="banana"
fruits[2]="cherry"

# Array operations
fruits+=("date") # Append element
fruits=("${fruits[@]}" "elderberry") # Append element (alternative)
unset fruits[1] # Remove element at index 1
fruits=("${fruits[@]/cherry/grape}") # Replace element

# Array information
echo ${#fruits[@]} # Array length
echo ${!fruits[@]} # Array indices (0 2 3 4 if index 1 was unset)
echo ${fruits[@]} # All elements
echo ${fruits[*]} # All elements (different quoting behavior)

Associative Arrays

# Associative array declaration
declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"
colors["blue"]="#0000FF"

# Alternative assignment
colors=([red]="#FF0000" [green]="#00FF00" [blue]="#0000FF")

# Access elements
echo ${colors["red"]} # #FF0000
echo ${colors[red]} # #FF0000 (quotes optional for simple keys)

# Array operations
echo ${#colors[@]} # Number of elements
echo ${!colors[@]} # All keys
echo ${colors[@]} # All values

# Check if key exists
if [[ -v colors["purple"] ]]; then
echo "Purple is defined"
else
echo "Purple is not defined"
fi

# Remove element
unset colors["red"]

Array Iteration

# Iterate over elements
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "Fruit: $fruit"
done

# Iterate over indices
for i in "${!fruits[@]}"; do
echo "Index $i: ${fruits[$i]}"
done

# Iterate over associative array
declare -A colors=([red]="#FF0000" [green]="#00FF00")
for color in "${!colors[@]}"; do
echo "Color $color: ${colors[$color]}"
done

Process Substitution

Basic Process Substitution

# Use command output as file
diff <(ls dir1) <(ls dir2)
comm <(sort file1) <(sort file2)

# Multiple process substitution
paste <(cut -d: -f1 /etc/passwd) <(cut -d: -f3 /etc/passwd)

# Output redirection
sort file.txt > >(gzip > file.txt.gz)

Advanced Process Substitution

# Compare outputs
diff <(ps aux | grep httpd) <(ps aux | grep nginx)

# Process logs
while read line; do
echo "Log: $line"
done < <(tail -f /var/log/syslog)

# Complex processing
while IFS=: read user uid home; do
echo "User: $user, UID: $uid, Home: $home"
done < <(cut -d: -f1,3,6 /etc/passwd)

Command Substitution

Basic Command Substitution

# Modern syntax
current_date=$(date)
file_count=$(ls | wc -l)
uptime_info=$(uptime)

# Legacy syntax (backticks)
current_date=`date`
file_count=`ls | wc -l`

# Nested command substitution
backup_name="backup_$(date +%Y%m%d)_$(hostname).tar.gz"
full_path="$(dirname $(which bash))/bash"

Advanced Command Substitution

# Command substitution in arrays
files=($(ls *.txt))
users=($(cut -d: -f1 /etc/passwd))

# Multiple command substitution
echo "System: $(uname -s), Kernel: $(uname -r), Arch: $(uname -m)"

# Command substitution with error handling
if output=$(command 2>&1); then
echo "Success: $output"
else
echo "Failed: $output"
fi

Arithmetic Expansion

Basic Arithmetic

# Basic operations
result=$((5 + 3)) # 8
result=$((10 - 4)) # 6
result=$((6 * 7)) # 42
result=$((20 / 4)) # 5
result=$((17 % 5)) # 2
result=$((2 ** 3)) # 8 (exponentiation)

# With variables
a=10
b=5
result=$((a + b)) # 15
result=$((a * b)) # 50

Advanced Arithmetic

# Increment/decrement
count=5
((count++)) # count becomes 6
((count--)) # count becomes 5
((count += 3)) # count becomes 8
((count -= 2)) # count becomes 6
((count *= 2)) # count becomes 12
((count /= 3)) # count becomes 4

# Comparison in arithmetic context
if ((a > b)); then
echo "a is greater than b"
fi

# Ternary operator
result=$((a > b ? a : b)) # max(a, b)

Bitwise Operations

# Bitwise operators
result=$((5 & 3)) # AND: 1
result=$((5 | 3)) # OR: 7
result=$((5 ^ 3)) # XOR: 6
result=$((~5)) # NOT: -6
result=$((5 << 1)) # Left shift: 10
result=$((5 >> 1)) # Right shift: 2

# Practical example
permissions=$((0644))
is_readable=$(( (permissions & 0400) != 0 ))

Here Documents and Here Strings

Here Documents

# Basic here document
cat << EOF
This is a here document.
It can span multiple lines.
Variables like $USER are expanded.
EOF

# Here document with delimiter
cat << "EOF"
This is a literal here document.
Variables like $USER are NOT expanded.
EOF

# Here document to file
cat << EOF > output.txt
Line 1
Line 2
EOF

# Here document with indentation
cat <<- EOF
This text is indented,
but the leading tabs will be removed.
EOF

Here Strings

# Here string
grep "pattern" <<< "This is a test string"
wc -w <<< "Count the words in this string"

# Here string with variables
string="Hello World"
tr 'a-z' 'A-Z' <<< "$string"

# Here string in loops
while read word; do
echo "Word: $word"
done <<< "one two three"

Brace Expansion

Basic Brace Expansion

# Simple expansion
echo {a,b,c} # a b c
echo file{1,2,3}.txt # file1.txt file2.txt file3.txt

# Range expansion
echo {1..5} # 1 2 3 4 5
echo {a..z} # a b c ... z
echo {A..Z} # A B C ... Z

# Step expansion
echo {1..10..2} # 1 3 5 7 9
echo {a..z..2} # a c e g i k m o q s u w y

# Zero-padded expansion
echo {01..10} # 01 02 03 04 05 06 07 08 09 10
echo {001..100..10} # 001 011 021 031 041 051 061 071 081 091

Advanced Brace Expansion

# Nested expansion
echo {a,b{1,2},c} # a b1 b2 c

# Combination with other expansions
echo {a..c}{1..3} # a1 a2 a3 b1 b2 b3 c1 c2 c3

# Practical examples
mkdir -p project/{src,doc,test}/{main,utils}
cp file.txt{,.bak} # Creates file.txt.bak
rm file.{txt,log,tmp} # Remove multiple files

Pathname Expansion (Globbing)

Basic Globbing

# Wildcard patterns
echo *.txt # All .txt files
echo file?.txt # file1.txt, fileA.txt, etc.
echo file[0-9].txt # file0.txt through file9.txt
echo file[!0-9].txt # Files not ending with digit

# Character classes
echo *[[:digit:]]* # Files containing digits
echo *[[:alpha:]]* # Files containing letters
echo *[[:alnum:]]* # Files containing alphanumeric

Extended Globbing

# Enable extended globbing
shopt -s extglob

# Extended patterns
echo !(*.txt) # Everything except .txt files
echo ?(*.txt|*.log) # Optional .txt or .log files
echo +(*.txt|*.log) # One or more .txt or .log files
echo *(*.txt|*.log) # Zero or more .txt or .log files
echo @(*.txt|*.log) # Exactly one .txt or .log file

# Practical examples
rm !(important.txt) # Remove all files except important.txt
ls *.@(jpg|png|gif) # List image files

Advanced I/O Redirection

File Descriptors

# Standard file descriptors
# 0: stdin, 1: stdout, 2: stderr

# Redirect stderr to stdout
command 2>&1

# Redirect stdout to file, stderr to another file
command > output.txt 2> error.txt

# Redirect both to same file
command &> output.txt # Bash 4+
command > output.txt 2>&1 # Portable

# Redirect to /dev/null
command > /dev/null 2>&1 # Discard all output

Custom File Descriptors

# Open file descriptor for reading
exec 3< input.txt
read -u 3 line
exec 3<&- # Close file descriptor

# Open file descriptor for writing
exec 4> output.txt
echo "Hello" >&4
exec 4>&- # Close file descriptor

# Open file descriptor for appending
exec 5>> log.txt
echo "Log entry" >&5
exec 5>&- # Close file descriptor

Coprocesses

Basic Coprocess

# Start coprocess
coproc COPROC { while read line; do echo "Processed: $line"; done; }

# Write to coprocess
echo "Hello" >&${COPROC[1]}

# Read from coprocess
read response <&${COPROC[0]}
echo "Response: $response"

# Close coprocess
exec {COPROC[0]}<&-
exec {COPROC[1]}>&-

Named Coprocess

# Named coprocess
coproc processor {
while read line; do
echo "$(date): $line"
done
}

# Use named coprocess
echo "Test message" >&${processor[1]}
read processed <&${processor[0]}
echo "$processed"

Advanced Script Techniques

Option Parsing

# Advanced option parsing
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
show_help
exit 0
;;
-v|--verbose)
verbose=1
shift
;;
-f|--file)
filename="$2"
shift 2
;;
--)
shift
break
;;
-*)
echo "Unknown option: $1" >&2
exit 1
;;
*)
break
;;
esac
done

Configuration Files

# Source configuration file
config_file="/etc/myapp.conf"
if [[ -f "$config_file" ]]; then
source "$config_file"
fi

# Parse key-value configuration
while IFS='=' read -r key value; do
case $key in
database_url) database_url="$value" ;;
port) port="$value" ;;
debug) debug="$value" ;;
esac
done < config.txt

Logging and Debugging

# Logging function
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >&2
}

# Debug function
debug() {
[[ $debug -eq 1 ]] && echo "DEBUG: $*" >&2
}

# Usage
log "Starting application"
debug "Configuration loaded"

Performance Optimization

Efficient String Operations

# Use parameter expansion instead of external commands
# Slow
basename=$(basename "$path")
dirname=$(dirname "$path")

# Fast
basename=${path##*/}
dirname=${path%/*}

# String replacement
# Slow
result=$(echo "$string" | sed 's/old/new/g')

# Fast
result=${string//old/new}

Efficient Array Operations

# Building arrays efficiently
# Slow
array=()
for file in *.txt; do
array+=("$file")
done

# Fast
array=(*.txt)

# Array copying
# Slow
new_array=()
for item in "${old_array[@]}"; do
new_array+=("$item")
done

# Fast
new_array=("${old_array[@]}")

Best Practices

Error Handling

# Set strict error handling
set -euo pipefail

# Custom error handling
error_exit() {
echo "Error: $1" >&2
exit 1
}

# Usage
[[ -f "$file" ]] || error_exit "File not found: $file"

Code Organization

# Function libraries
# lib/utils.sh
log() { echo "[$(date)] $*" >&2; }
die() { echo "Error: $*" >&2; exit 1; }

# main.sh
source lib/utils.sh
log "Starting application"

Testing

# Simple testing framework
test_count=0
test_passed=0

assert_equals() {
local expected="$1"
local actual="$2"
local message="$3"

((test_count++))
if [[ "$expected" == "$actual" ]]; then
((test_passed++))
echo "PASS: $message"
else
echo "FAIL: $message (expected: $expected, got: $actual)"
fi
}

# Run tests
assert_equals "5" "$((2 + 3))" "Addition test"
echo "Tests: $test_passed/$test_count passed"