Table of Contents
Why Use Functions in Shell Scripts?
Functions let you group a series of commands under a single name so you can:
- Avoid repeating code
- Make scripts easier to read and maintain
- Organize complex scripts into logical pieces
- Reuse logic with different inputs
This chapter focuses on how to define, call, and use functions in shell scripts, especially in bash.
Defining a Function
There are two common ways to define a function in bash:
my_function() {
echo "Hello from a function"
}or
function my_function {
echo "Hello from a function"
}
Both are valid in bash. The first style (name() { ... }) is more portable to other POSIX shells, so it’s generally preferred.
Key points:
- Function names usually use letters, numbers, and underscores, and must not start with a number (e.g.
backup_files,print_result). - Avoid using names that are existing commands (
test,echo,cd, etc.). - The function body is enclosed in
{ ... }and typically ends each command with a newline or;.
Example:
#!/usr/bin/env bash
greet() {
echo "Welcome to my script!"
}
greetWhere to Place Function Definitions
- A function must be defined before you call it in the script.
- A common pattern is to put all function definitions at the top, and the “main” part of the script at the bottom:
#!/usr/bin/env bash
say_hello() {
echo "Hello, $1!"
}
main() {
say_hello "Alice"
say_hello "Bob"
}
mainCalling a Function
Calling a function is just like running a command:
greet_user() {
echo "Hi there!"
}
# Call the function
greet_user
You do not use () when calling:
- Correct:
greet_user - Not recommended:
greet_user()(this can work in bash but is confusing and non-standard)
Passing Arguments to Functions
Functions receive arguments just like scripts do:
$1— first argument$2— second argument$#— number of arguments$@— all arguments as separate words$*— all arguments as a single word/string (usually less useful)
Example:
print_greeting() {
echo "Hello, $1!"
}
print_greeting "Alice"
print_greeting "Bob"Output:
Hello, Alice!
Hello, Bob!Using multiple arguments:
show_sum() {
echo "First number: $1"
echo "Second number: $2"
echo "Sum: $(($1 + $2))"
}
show_sum 3 7Using all arguments:
list_args() {
echo "You passed $# arguments:"
for item in "$@"; do
echo " - $item"
done
}
list_args one two "three four"
Note the use of "$@" (quoted) to preserve spaces in arguments.
Return Codes vs Output
In shell, a function can:
- Print output using
echo(or other commands). - Set an exit status (also called return code) using
return.
These are different things:
- Command output is what you see on the screen or can capture with command substitution:
result=$(my_func). - Exit status is a number from 0–255 that indicates success or failure and is stored in
$?.
By convention:
0means success- Non-zero values indicate different kinds of errors
Using `return` to Set Exit Status
Use return inside a function:
is_even() {
local number="$1"
if (( number % 2 == 0 )); then
return 0 # success
else
return 1 # failure
fi
}
is_even 4
echo "Exit status: $?" # 0
is_even 5
echo "Exit status: $?" # 1
You then typically check $? or use if directly:
if is_even 10; then
echo "10 is even"
else
echo "10 is odd"
fiReturning Data via Output
Because return only carries a small number code, you return actual data by printing it and (optionally) capturing it:
get_hostname() {
hostname
}
my_host=$(get_hostname)
echo "This machine is called: $my_host"Local Variables in Functions
By default, variables in shell are global within the script: a variable set in a function is visible outside it.
To avoid accidental interference, use local inside functions:
counter=0
increment_global() {
counter=$((counter + 1))
}
increment_local() {
local counter=0
counter=$((counter + 1))
echo "Inside increment_local: counter=$counter"
}
increment_global
echo "After increment_global: counter=$counter"
increment_local
echo "After increment_local: counter=$counter"Output (approx.):
After increment_global: counter=1
Inside increment_local: counter=1
After increment_local: counter=1
Use local for variables that are meant to be used only within the function:
greet_person() {
local name="$1"
echo "Hello, $name!"
}Function Naming and Organization
Good function names and organization make scripts easier to understand:
- Use clear, descriptive names:
backup_home,check_disk_space,log_message. - Use verbs for functions that “do” something:
send_email,create_user. - Group related functions together in the script.
Example layout:
#!/usr/bin/env bash
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
backup_file() {
local file="$1"
cp "$file" "$file.bak"
log "Backed up $file"
}
main() {
backup_file "/etc/hosts"
}
mainUsing Functions for Repeated Tasks
Any time you find yourself repeating similar commands, it’s a good candidate for a function.
Without function:
echo "Starting process A..."
# commands for A
echo "Finished process A."
echo "Starting process B..."
# commands for B
echo "Finished process B."With a function:
run_process() {
local name="$1"
echo "Starting process $name..."
# commands for "$name"
echo "Finished process $name."
}
run_process "A"
run_process "B"This reduces duplication and makes later changes easier.
Functions and Exit
Be aware of the difference between return and exit inside a function:
return— exits the function and goes back to the caller.exit— exits the entire script immediately.
Example:
dangerous() {
echo "About to exit the whole script!"
exit 1
}
safe() {
echo "Leaving this function only."
return 0
}
In general, prefer return in functions unless you intentionally want to end the whole script.
Simple Debugging of Functions
You can:
- Add temporary
echostatements inside functions to see what they do. - Call a function directly from the command line if it’s defined in your current shell (for example, after
source-ing the script).
Example:
#!/usr/bin/env bash
debug_example() {
echo "Argument 1: $1"
echo "Argument 2: $2"
}
debug_example "foo" "bar"Run the script and check that the output matches your expectations.
Basic Reusable Function Examples
Confirm Action
confirm() {
local prompt="${1:-Are you sure?} [y/N] "
read -r -p "$prompt" reply
case "$reply" in
[Yy][Ee][Ss]|[Yy]) return 0 ;;
*) return 1 ;;
esac
}
if confirm "Delete all temporary files?"; then
echo "Deleting..."
else
echo "Cancelled."
fiCheck if a Command Exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
if command_exists "git"; then
echo "Git is installed."
else
echo "Git is not installed."
fiThese small, focused functions are building blocks you can reuse in many scripts.