Shell Functions
Defining Functions
Functions in shell scripts encapsulate reusable blocks of code. There are two common syntax styles:
# Style 1: Using the function keyword
function my_function {
commands
}
# Style 2: Bash-compatible parentheses syntax
my_function() {
commands
}
Inspecting Defined Functions
The declare builtin provides introspection capabilities for functions:
# List all function names
declare -F
# Display full function definitions
declare -f
# Show definition of a specific function
declare -f function_name
# List a specific function name
declare -F function_name
Return Values
Shell functions exit with status codes rather than traditional return values. The return statement sets the exit status (0-255), which becomes available via the $? special variable:
calculate() {
local input
read -p "Enter a number: " input
result=$((input * 3))
return $result
}
calculate
current_status=$?
Important considerations:
$?captures only the most recent command's exit status- Status codes wrap around: values beyond 255 are reduced via modulo 256
Parameter Passing
Functions access positional parameters just like the main script:
add_values() {
local total=$(($1 + $2))
echo "Sum: $total"
}
read -p "First number: " x
read -p "Second number: " y
add_values $x $y
# Alternative: pass script arguments directly
add_values $1 $2
Variable Scope
By default, shell variables have global scope throughout the script. The local keyword restricts variable visibility to the function body:
#!/bin/bash
process_data() {
echo "Global var before: $global_var"
local local_var=50
echo "Local var inside function: $local_var"
global_var=200
}
####### script start #######
global_var=100
echo "Global var before call: $global_var"
process_data
echo "Global var after call: $global_var"
Output demonstrates that global_var persists outside the function while local_var remains inaccessible:
Global var before call: 100
Global var before: 100
Local var inside function: 50
Global var after call: 200
Function Invocation Methods
Functions can be defined and used in multiple contexts:
- Interactive shell: Define directly at the command prompt
- Embedded in scripts: Include function definitions before the main execution block
- External library files: Store functions in separate files and source them when needed
Practical Example: IP Adress Conversion
This script converts dotted decimal IP addresses to binary representation:
#!/bin/bash
decimal_to_binary() {
local decimal=$1
local binary=""
local digit
for ((i=0; i<8; i++)); do
digit=$((decimal % 2))
binary="${digit}${binary}"
decimal=$((decimal / 2))
done
echo "$binary"
}
convert_ip() {
local ip_addr=$1
local octet
local result=""
for ((i=0; i<4; i++)); do
octet=${ip_addr%%.*}
ip_addr=${ip_addr#*.}
result+=$(decimal_to_binary $octet)
if [ $i -lt 3 ]; then
result+="."
fi
done
echo "$result"
}
######## main ########
read -p "Enter IP address (dotted decimal): " user_ip
binary_ip=$(convert_ip "$user_ip")
echo "Binary representation: $binary_ip"
Practical Example: Pattern Display
Generate a diamond pattern with configurable dimensions:
#!/bin/bash
print_upper() {
local rows=$1
local row space stars
for ((row=1; row<=rows; row++)); do
space=$((rows - row))
stars=$((2 * row - 1))
printf "%${space}s" ""
printf '%*s\n' "$stars" "" | tr ' ' '*'
done
}
print_lower() {
local rows=$1
local row space stars
for ((row=rows-1; row>=1; row--)); do
space=$((rows - row))
stars=$((2 * row - 1))
printf "%${space}s" ""
printf '%*s\n' "$stars" "" | tr ' ' '*'
done
}
######## main ########
read -p "Enter diamond size: " size
print_upper $size
print_lower $size
Recursive Functions
A function can call itself, enabling elegant solusions for problems with recursive structure.
Factorial Calculation
#!/bin/bash
factorial() {
local n=$1
if [ $n -le 1 ]; then
echo 1
else
local previous=$(factorial $((n - 1)))
echo $((n * previous))
fi
}
######## main ########
read -p "Enter a positive integer: " number
result=$(factorial $number)
echo "Factorial of $number is: $result"
Directory Traversal
Recursive functions excel at traversing directory trees:
#!/bin/bash
extract_paths() {
local directory=$1
local indent=$2
local entry
for entry in "$directory"/*; do
if [ -d "$entry" ]; then
echo "${indent}[DIR] ${entry##*/}"
extract_paths "$entry" "$indent "
elif [ ! -x "$entry" ]; then
echo "${indent}[FILE] ${entry##*/}"
fi
done
}
######## main ########
IFS=':' read -ra paths <<< "$PATH"
for p in "${paths[@]}"; do
echo "Directory: $p"
if [ -d "$p" ]; then
extract_paths "$p" " "
fi
done
Function Library Files
Reusable function collections can be stored in separate files and imported via source or ..
Creating a Library
Save this as mathlib.sh:
add() {
echo $(($1 + $2))
}
subtract() {
echo $(($1 - $2))
}
multiply() {
echo $(($1 * $2))
}
divide() {
if [ $2 -eq 0 ]; then
echo "Error: division by zero"
return 1
fi
echo $(($1 / $2))
}
factorial() {
local n=$1
if [ $n -le 1 ]; then
echo 1
else
echo $((n * $(factorial $((n - 1)))))
fi
}
Using the Library
#!/bin/bash
source mathlib.sh
read -p "Enter first number: " num1
read -p "Enter second number: " num2
sum=$(add $num1 $num2)
diff=$(subtract $num1 $num2)
prod=$(multiply $num1 $num2)
quot=$(divide $num1 $num2)
fact=$(factorial $num1)
echo "Sum: $sum"
echo "Difference: $diff"
echo "Product: $prod"
echo "Quotient: $quot"
echo "Factorial: $fact"
Function libraries provide modularity and code reuse across multiple scripts, reducing duplication and maintenance overhead.