Table of Contents
Why Functions Matter in Shell Scripts
Functions in shell scripts let you group commands under a single name. This avoids repeating the same sequence of commands and makes scripts easier to read, test, and change.
A function behaves like a mini script inside your script. You give it a name, it can receive input as arguments, it can use variables, and it can return a status code.
Key rule: Use functions to avoid duplicated code and to give clear names to logical steps in your script.
Defining a Function
In most shells used on Linux, especially Bash, there are two common styles to define a function. The preferred and more portable form is:
my_function() {
echo "Inside my_function"
}
You write the function name, an empty pair of parentheses, and then a block of commands wrapped in { and }. The closing brace must be on its own line or followed by ;. There must be a semicolon before the closing brace if the brace is not on its own line.
The alternative Bash-specific syntax is:
function my_function {
echo "Inside my_function"
}
Both forms define a function named my_function. For simple scripts, you can pick either style, but if you care about portability to other shells, the first style is safer.
Inside the function, you can use any commands you would normally use in a shell script.
Calling a Function
To run a function after you have defined it, you simply use its name like a command:
my_functionIf the function is defined earlier in the script, the shell will look it up and execute the commands inside it.
You should define functions before they are used, typically near the top of the script. A common layout is to put function definitions first and then have a small main section at the bottom that calls them.
Function Arguments
Functions receive arguments in the same way as the script itself. Inside the function:
$1is the first argument to the function$2is the second argument$#is the number of arguments passed to the function$@is the list of all arguments
This is separate from the arguments to the script as a whole. When you call a function, you pass arguments just like a normal command.
For example:
greet() {
name="$1"
echo "Hello, $name"
}
greet "Alice"
greet "Bob"
Inside greet, $1 will be "Alice" the first time and "Bob" the second time.
If you want to forward all arguments a function receives to another command, you can write:
run_with_log() {
echo "Running: $@"
"$@"
}
"$@" passes each argument as-is to the next command.
Important: Always quote $@ as "$@" when forwarding arguments. This preserves spaces inside arguments and avoids unexpected splitting.
Return Status from Functions
Every function has an exit status, also called a return status. This is just like the status code from a normal command. By convention, 0 means success and any nonzero value means some kind of error or special condition.
If you do not explicitly choose a status, the function returns the status of the last command that was run inside it.
You can explicitly set the status using return:
is_even() {
number="$1"
if [ $(( number % 2 )) -eq 0 ]; then
return 0
else
return 1
fi
}
is_even 4
echo "Status: $?" # 0
is_even 5
echo "Status: $?" # 1
Here the function is_even returns 0 for an even number and 1 otherwise. After calling the function, $? holds the status of that call.
A function can use return at any point. When return runs, the function stops and control goes back to the caller.
Rule: Function return values are status codes, not data. Use 0 for success and nonzero for different error cases or conditions.
Returning Data from Functions
If you want a function to give a text result instead of only a status code, you normally use output. The function prints something, and the caller captures it with command substitution.
Example:
get_date_string() {
date "+%Y-%m-%d"
}
today="$(get_date_string)"
echo "Today is $today"
Here the function uses date and lets its output flow to the caller through $(...). The function’s exit status still follows the usual rule, and you can check $? after the command substitution if needed.
You can also have a function set global variables, but for beginner scripts, command substitution is often clearer.
Important pattern:
Use output (echo, printf) plus $(function_name ...) to get data, and use the return status for success or error indications.
Variable Scope in Functions
By default in shells like Bash, variables inside functions are global. If you assign to a variable in a function, the change is visible in the rest of the script.
Example:
count=0
increment() {
count=$(( count + 1 ))
}
increment
increment
echo "$count" # 2If you want a variable to be visible only inside a specific function, you can mark it as local:
counter_demo() {
local temp=10
echo "Inside: $temp"
}
counter_demo
echo "Outside: $temp" # Empty, temp is not defined here
Using local helps prevent unexpected interference between functions that happen to use the same variable names.
Organizing Scripts with Functions
Functions help structure a script into clear, named pieces of work. A common beginner-friendly layout is:
- A group of function definitions at the top
- A short main section at the bottom that calls those functions
For example:
print_usage() {
echo "Usage: $0 <directory>"
}
check_directory() {
dir="$1"
if [ ! -d "$dir" ]; then
echo "Error: $dir is not a directory"
return 1
fi
return 0
}
list_files() {
dir="$1"
echo "Files in $dir:"
ls "$dir"
}
main() {
if [ $# -ne 1 ]; then
print_usage
return 1
fi
dir="$1"
if ! check_directory "$dir"; then
return 1
fi
list_files "$dir"
}
main "$@"
Here the functions have clear names that describe what they do: print_usage, check_directory, list_files, and main. The bottom line main "$@" starts the script by passing all script arguments to main.
Good practice: Give functions descriptive names and keep each function focused on one task.
Special Behavior of Functions
Functions share most behavior with regular commands, but a few details are useful to know:
- A function runs in the current shell, so it can change variables and the current directory with
cdfor the whole script. - A function does not start a new process by default. This makes functions faster than calling separate scripts.
- If a function name and a program name are the same, the function definition normally takes precedence. You can still reach the original command by writing its full path such as
/bin/ls.
You can see all defined functions in a shell with:
declare -for list the definition of a specific function with:
declare -f function_nameWhen you are building more complex scripts, being aware of these rules helps avoid surprises such as functions accidentally changing variables in other parts of the script.
Summary
Functions in shell scripts allow you to name reusable pieces of logic, pass arguments, return status codes, and optionally produce data through output. They execute in the current shell, can use global or local variables, and make scripts more modular and easier to understand when used thoughtfully.