8.1. Defining Functions#

8.1.1. Common Pitfalls#

1. Forgetting parentheses for POSIX compatibility

# Bash-specific (may not work in sh/dash)
function my_func {
  echo "hello"
}

# POSIX-compatible (preferred)
my_func() {
  echo "hello"
}

2. Using global variables when local is better

# Bad: Pollutes global scope
process_data() {
  temp_value=$1  # Affects global temp_value
  echo $temp_value
}

# Good: Use local
process_data() {
  local temp_value=$1
  echo $temp_value
}

3. Forgetting to quote function parameters

# Problematic with spaces/special characters
echo_name() {
  echo "Hello $1"  # Unquoted: word splitting occurs
}

# Better
echo_name() {
  echo "Hello $1"  # Actually the issue is when $1 contains spaces
}

echo_name "Alice Smith"  # "Hello Alice Smith" vs "Hello Alice Smith"

8.1.2. Real-World Example: Utility Functions#

#!/bin/bash

# Function to validate that a file exists and is readable
check_file() {
  local file=$1
  
  if [[ ! -f "$file" ]]; then
    echo "Error: File '$file' does not exist"
    return 1
  fi
  
  if [[ ! -r "$file" ]]; then
    echo "Error: File '$file' is not readable"
    return 1
  fi
  
  return 0
}

# Function to read and count lines
count_lines() {
  local file=$1
  wc -l < "$file"
}

# Usage
if check_file "$1"; then
  lines=$(count_lines "$1")
  echo "File '$1' has $lines lines"
else
  echo "Cannot process file"
fi

This example shows:

  • Functions checking file existence/permissions

  • Functions returning values via output

  • Error handling with exit codes

  • Functions used in conditional logic

8.1.3. Function Scope and Variables#

Variables defined in functions are global by default (visible to the rest of the script). Use the local keyword to create function-scoped variables.

8.1.3.1. Local Variables#

#!/bin/bash

# Global variable
count=0

increment() {
  local count=1  # This shadows the global count in function scope
  count=$((count + 1))
  echo "In function: count=$count"
}

echo "Before: count=$count"
increment
echo "After: count=$count"

# Output:
# Before: count=0
# In function: count=2
# After: count=0

The local keyword ensures changes don’t affect the outer scope.

8.1.4. Function Invocation Patterns#

8.1.4.1. Simple Function Call#

function_name           # Call with no arguments
function_name arg1      # Call with one argument
function_name arg1 arg2 # Call with multiple arguments

8.1.4.2. Capturing Function Output#

#!/bin/bash

get_timestamp() {
  date "+%Y-%m-%d %H:%M:%S"
}

# Capture output with command substitution
current_time=$(get_timestamp)
echo "Current time: $current_time"

# Or use in a pipeline
if get_timestamp | grep -q "2024"; then
  echo "We're in 2024"
fi

8.1.4.3. Using Functions in Command Substitution#

#!/bin/bash

calculate() {
  echo $((2 + 2))
}

# Output becomes the result
result=$(calculate)
echo "2 + 2 = $result"  # Output: 2 + 2 = 4

8.1.5. Functions with Parameters#

Functions receive arguments just like scripts do, using $1, $2, $#, and $@.

8.1.5.1. Accessing Function Parameters#

#!/bin/bash

# Function that uses parameters
greet_person() {
  local name=$1
  local greeting=$2
  echo "$greeting, $name!"
}

# Call with arguments
greet_person "Alice" "Hello"
greet_person "Bob" "Good morning"

# Output:
# Hello, Alice!
# Good morning, Bob!

Each function invocation has its own parameter scope. $1 in the function refers to the first argument passed to that function call, not to the script’s arguments.

8.1.5.2. Variable Number of Arguments#

#!/bin/bash

# Function that handles variable arguments
print_all() {
  echo "You passed $# arguments:"
  for arg in "$@"; do
    echo "  - $arg"
  done
}

print_all "apple" "banana" "cherry"

# Output:
# You passed 3 arguments:
#   - apple
#   - banana
#   - cherry

8.1.6. Function Basics: Syntax and Structure#

A bash function is a named block of code that can be called multiple times. Functions accept parameters (positional arguments) and return status codes.

8.1.6.1. Function Definition Syntax#

There are two ways to define a function in bash:

Method 1: function keyword

function function_name {
  # function body
  commands
}

Method 2: parentheses

function_name() {
  # function body
  commands
}

Both forms are equivalent. The parentheses form is more portable (POSIX-compatible), while the function keyword is bash-specific but more readable.

8.1.6.2. Simple Function Example#

#!/bin/bash

# Define a simple greeting function
greet() {
  echo "Hello, World!"
}

# Call the function
greet

# Output: Hello, World!