Defining and Invoking Shell Functions
Shell functions allow you to encapsulate repetitive logic into reusable blocks, improving script organization and maintainability. Functions can be defined directly in a script or interactively within a terminal session.
monitor_process() {
echo "Monitoring logic goes here";
}
Building a Process Watchdog
A common operational use case for shell functions is creating a daemon to monitor critical services. The following script continuously checks if a specific service is active, restarting it automatically if it crashes.
#!/bin/bash
# watchdog.sh
current_pid=$$
while true; do
if pgrep -x nginx > /dev/null; then
echo "Service nginx is active."
sleep 5
else
systemctl restart nginx
echo "Service nginx was inactive. Attempting restart..."
fi
done
To execute this script in the background as a persistent daemon, allowing it to survive terminal disconnections:
nohup bash watchdog.sh &
View the generated output logs in real-time:
tail -f nohup.out
Passing Arguments to Functions
Arguments are passed to shell functions in the same manner as passing parameters to a script itself, utilizing positional variables such as $1, $2, and $3.
The following calculator script accepts two numeric operands and an arithmetic operator as arguments:
#!/bin/bash
# compute.sh
calculate() {
local val_a=$1
local operator=$2
local val_b=$3
case $operator in
+) echo "$((val_a + val_b))" ;;
-) echo "$((val_a - val_b))" ;;
x) echo "$((val_a * val_b))" ;;
/) echo "$((val_a / val_b))" ;;
esac
}
calculate $1 $2 $3
Execute the script by providing the required parameters:
bash compute.sh 45 x 12
Handling Function Output and Status
Functions can communicate back to the calling environment using two primary mechanisms: exit statuses via return, and data streams via echo.
Using return for Status: The return directive outputs an integer exit code (0 for success, non-zero for failure), which is ideal for boolean logic and conditional checks.
#!/bin/bash
# service_status.sh
is_active() {
if pgrep -x nginx > /dev/null; then
return 0
else
return 1
fi
}
is_active && echo "Nginx is operational." || echo "Nginx is offline."
Using echo for Data: When a function must output strings, arrays, or computed values, echo is utilized. The calling script can capture this output using command substitution.
#!/bin/bash
# list_accounts.sh
fetch_accounts() {
awk -F: '{print $1}' /etc/passwd
}
registered_users=$(fetch_accounts)
counter=1
for account in $registered_users; do
echo "Account $counter: $account"
((counter++))
done
Understanding Variable Scope
Variables declared inside a function without the local keyword become globally accessible once the function is executed. Conversely, variables defined with local are strictly confined to the function's execution context and are destroyed upon function completion.
#!/bin/bash
# scope_demo.sh
global_msg="System initialized"
assign_vars() {
inner_global="Accessible everywhere"
local confined="Restricted context"
}
assign_vars
echo "$global_msg"
echo "$inner_global"
echo "$confined" # This will output an empty line
Creating Reusable Function Libraries
To promote code reuse across multiple scripts, common functions can be extracted into standalone library files. These libraries are then imported into a script using the source command (or its shorthand .).
Library file located at /usr/local/lib/sys_math.lib:
#!/bin/echo "Warning: This is a library, do not execute directly."
sum() {
echo "$(($1 + $2))"
}
subtract() {
echo "$(($1 - $2))"
}
multiply() {
echo "$(($1 * $2))"
}
divide() {
echo "$(($1 / $2))"
}
show_resources() {
echo "=== Memory Utilization ==="
free -h
echo "=== Disk Usage ==="
df -h
}
Main application script importing the library:
#!/bin/bash
# app.sh
source /usr/local/lib/sys_math.lib
sum 15 85
subtract 100 35
multiply 6 9
divide 90 3
show_resources
Best practices for managing shell function libraries:
- Adopt a conventional extension like
.libfor easy identification. - Library files do not require executable permissions, as they are sourced rather than executed.
- Store libraries in dedicated directories (e.g.,
/usr/local/lib) and reference them using absolute paths. - Use
#!/bin/echoas the shebang to prevent accidental direct execution, which will simply print the warning message.