Linux CLI 57 🐧 shell scripts functions
#!/bin/bash
function greet() {
echo "Hello, World!"
}
greet2() {
echo "Hello, World!"
}
greet
greet2
greet3() {
echo "Hello, $1! You are $2 years old."
}
greet3 "Kronos" 30
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
result=$(add 5 3)
echo "The sum is: $result"
Functions let you encapsulate code into reusable blocks. Instead of repeating the same commands in several places, you define them once and call them by name. This makes your scripts cleaner, shorter, and easier to maintain.
Key point: Shell functions are not like functions in C or Python. They don’t have a return type, they don’t have a separate scope (unless you use local), and they return an exit status (0–255), not a value. To pass a value back, you echo it and capture it with $(...).
a – functions in shell scripts
Functions within shell scripts allow you to encapsulate code into reusable blocks. It makes your scripts cleaner and more maintainable.
Defining a function — two ways:
Method 1 — Using the function keyword:
function greet() {
echo "Hello, World!"
}
Method 2 — Without the function keyword (POSIX):
greet2() {
echo "Hello, World!"
}
Both work in bash. The second form is portable — it works in sh, dash, ksh, and zsh too.
Calling a function:
Just write its name — like any other command:
$ greet
[ Hello, World! ]
$ greet2
[ Hello, World! ]
A function can call other functions:
#!/bin/bash
say_hello() {
echo "Hello"
}
say_world() {
echo "World"
}
greet() {
say_hello
say_world
}
greet
[ Hello ]
[ World ]
Functions must be defined before they’re called:
#!/bin/bash
greet # ❌ error: greet: command not found
greet() {
echo "Hello"
}
Order matters. Bash reads the script top to bottom, so the function must be defined before the call.
Where to put functions:
#!/bin/bash
# Option 1: at the top
myfunc() {
echo "Hello"
}
# Main script
myfunc
# Option 2: at the bottom (works because the shell parses first, then runs)
main() {
myfunc
}
myfunc() {
echo "Hello"
}
main
Most scripts put all functions at the top, then a main function, then call main at the bottom.
A more complete example:
#!/bin/bash
log() {
echo "[$(date +%T)] $1"
}
error() {
echo "[ERROR] $1" >&2
}
cleanup() {
echo "Cleaning up..."
rm -f /tmp/myscript.*
}
main() {
log "Starting"
log "Doing work..."
error "Something went wrong"
cleanup
log "Done"
}
main
$ ./script.sh
[ 10:00:00 ] Starting
[ 10:00:00 ] Doing work...
[ ERROR ] Something went wrong
Cleaning up...
[ 10:00:00 ] Done
Listing and unsetting functions:
$ declare -f # list all functions
$ declare -f greet # show one function
$ unset -f greet # remove a function
b – Passing arguments to a function in shell scripts
You can pass arguments to a function just like you would with any other command. Arguments are values that are passed to the function.
Inside the function, arguments are accessed as $1, $2, etc.:
| Variable | Meaning |
|---|---|
$1, $2, … | First, second, … argument |
$0 | Script name (not function name!) |
$# | Number of arguments |
$@ | All arguments as separate words |
$* | All arguments as one word |
Important: Inside a function,
$0is still the script name — not the function name. There’s no built-in way to get the function’s own name.
Example — a function with two arguments:
#!/bin/bash
greet() {
echo "Hello, $1! You are $2 years old."
}
greet "Kronos" 30
[ Hello, Kronos! You are 30 years old. ]
Passing arguments with spaces:
greet "Alice Smith" 25
[ Hello, Alice Smith! You are 25 years old. ]
Quote arguments that contain spaces.
Arguments are positional — order matters:
describe() {
echo "Name: $1"
echo "Age: $2"
echo "City: $3"
}
describe Alice 30 Paris
[ Name: Alice ]
[ Age: 30 ]
[ City: Paris ]
Using $# to count arguments:
count_args() {
echo "You passed $# arguments"
}
count_args a b c d
[ You passed 4 arguments ]
Using $@ to loop over arguments:
print_all() {
for arg in "$@"; do
echo "Argument: $arg"
done
}
print_all one two three
[ Argument: one ]
[ Argument: two ]
[ Argument: three ]
Providing defaults:
greet() {
local name="${1:-Guest}"
echo "Hello, $name!"
}
greet
[ Hello, Guest! ]
greet Alice
[ Hello, Alice! ]
Validating argument count:
add() {
if [ $# -ne 2 ]; then
echo "Usage: add <num1> <num2>" >&2
return 1
fi
echo $(( $1 + $2 ))
}
add 5 3
[ 8 ]
add 5
[ Usage: add <num1> <num2> ]
Passing arguments from the script to a function:
#!/bin/bash
greet() {
echo "Hello, $1!"
}
greet "$1"
$ ./script.sh Alice
[ Hello, Alice! ]
Referencing all script arguments inside a function:
#!/bin/bash
show() {
echo "Function got: $@"
echo "Script got: $@"
}
show one two three
Note: Inside a function,
$@refers to the function’s arguments, not the script’s. To access the script’s arguments inside a function, save them first:SCRIPT_ARGS=("$@") show() { echo "Function: $@" echo "Script: ${SCRIPT_ARGS[@]}" }
c – Returning values from a function in shell scripts
In shell scripting, functions don’t return values like in some other programming languages. Instead, they can modify variables or print output that can be captured by the caller.
The most common way to return values — echo:
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
result=$(add 5 3)
echo "The sum is: $result"
[ The sum is: 8 ]
The function echoes the result. The caller captures it with $(...) — command substitution.
Return with exit status (return):
Every function returns an exit status — a number between 0 and 255. 0 means success, anything else means failure.
is_even() {
if [ $(( $1 % 2 )) -eq 0 ]; then
return 0 # success — even
else
return 1 # failure — odd
fi
}
if is_even 4; then
echo "4 is even"
fi
if is_even 5; then
echo "5 is even"
else
echo "5 is odd"
fi
[ 4 is even ]
[ 5 is odd ]
Reading the return value with $?:
add() {
return $(( $1 + $2 ))
}
add 5 3
echo "Status: $?"
[ Status: 8 ]
⚠️ Warning:
returnin bash only accepts values 0–255.return 300wraps around to 44. For real values, useecho+$(...).
Returning multiple values:
Use echo with multiple lines, then read into an array:
get_name_and_age() {
echo "Alice"
echo "30"
}
readarray -t info < <(get_name_and_age)
echo "Name: ${info[0]}"
echo "Age: ${info[1]}"
[ Name: Alice ]
[ Age: 30 ]
Or use read with process substitution:
get_info() {
echo "Alice 30"
}
read name age < <(get_info)
echo "$name is $age"
[ Alice is 30 ]
Using global variables (not recommended):
RESULT=""
add() {
RESULT=$(( $1 + $2 ))
}
add 5 3
echo "Sum: $RESULT"
[ Sum: 5 + 3 = 8 ]
This works but is fragile — globals can be accidentally overwritten. Prefer echo + $(...) or return for status.
Using local for internal variables:
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
add 5 3
echo "sum is: $sum" # empty — sum was local
[ 8 ]
[ sum is: ]
local keeps variables inside the function. Without it, sum would leak into the global scope.
Comparing return approaches:
| Approach | Use for | Example |
|---|---|---|
echo + $() | Return a value | result=$(add 5 3) |
return N | Return status (0–255) | if is_even 4; then ... |
| Global variable | Simple flags | RESULT=... |
readarray | Return multiple values | readarray -t arr < <(fn) |
Full example — returning both a value and a status:
#!/bin/bash
divide() {
if [ "$2" -eq 0 ]; then
echo "Error: division by zero" >&2
return 1
fi
echo $(( $1 / $2 ))
return 0
}
result=$(divide 10 2)
if [ $? -eq 0 ]; then
echo "10 / 2 = $result"
fi
result=$(divide 10 0)
if [ $? -ne 0 ]; then
echo "Division failed"
fi
[ 10 / 2 = 5 ]
[ Error: division by zero ]
[ Division failed ]
Complete Example Session
# ============================================
# PART 1: DEFINING FUNCTIONS
# ============================================
$ greet() { echo "Hello, World!"; }
$ greet
[ Hello, World! ]
$ function greet2() { echo "Hi!"; }
$ greet2
[ Hi! ]
# ============================================
# PART 2: FUNCTIONS WITH ARGUMENTS
# ============================================
$ greet3() { echo "Hello, $1! You are $2 years old."; }
$ greet3 "Kronos" 30
[ Hello, Kronos! You are 30 years old. ]
$ greet3 "Alice Smith" 25
[ Hello, Alice Smith! You are 25 years old. ]
# ============================================
# PART 3: COUNTING ARGUMENTS
# ============================================
$ count_args() { echo "You passed $# arguments"; }
$ count_args a b c d
[ You passed 4 arguments ]
# ============================================
# PART 4: LOOPING OVER ARGUMENTS
# ============================================
$ print_all() {
> for arg in "$@"; do
> echo "Argument: $arg"
> done
> }
$ print_all one two three
[ Argument: one ]
[ Argument: two ]
[ Argument: three ]
# ============================================
# PART 5: RETURNING A VALUE WITH ECHO
# ============================================
$ add() {
> local sum=$(( $1 + $2 ))
> echo $sum
> }
$ result=$(add 5 3)
$ echo "The sum is: $result"
[ The sum is: 8 ]
# ============================================
# PART 6: RETURNING A STATUS
# ============================================
$ is_even() {
> if [ $(( $1 % 2 )) -eq 0 ]; then
> return 0
> else
> return 1
> fi
> }
$ if is_even 4; then echo "even"; fi
[ even ]
$ if is_even 5; then echo "even"; else echo "odd"; fi
[ odd ]
# ============================================
# PART 7: LOCAL VARIABLES
# ============================================
$ myfunc() {
> local secret="hidden"
> echo "Inside: $secret"
> }
$ myfunc
[ Inside: hidden ]
$ echo "Outside: $secret"
[ Outside: ]
# ============================================
# PART 8: DEFAULTS
# ============================================
$ greet() {
> local name="${1:-Guest}"
> echo "Hello, $name!"
> }
$ greet
[ Hello, Guest! ]
$ greet Alice
[ Hello, Alice! ]
# ============================================
# PART 9: MULTIPLE RETURN VALUES
# ============================================
$ get_info() {
> echo "Alice"
> echo "30"
> }
$ readarray -t info < <(get_info)
$ echo "Name: ${info[0]}, Age: ${info[1]}"
[ Name: Alice, Age: 30 ]
# ============================================
# PART 10: A LOGGING FUNCTION
# ============================================
$ log() { echo "[$(date +%T)] $1"; }
$ log "Starting backup"
[ [10:00:00] Starting backup ]
$ log "Backup complete"
[ [10:00:00] Backup complete ]
# ============================================
# PART 11: A SCRIPT WITH MAIN
# ============================================
$ cat > script.sh << 'EOF'
#!/bin/bash
log() {
echo "[$(date +%T)] $1"
}
error() {
echo "[ERROR] $1" >&2
}
cleanup() {
echo "Cleaning up..."
}
main() {
log "Starting"
error "Something failed"
cleanup
}
main
EOF
$ chmod +x script.sh
$ ./script.sh
[ [10:00:00] Starting ]
[ [ERROR] Something failed ]
[ Cleaning up... ]
# ============================================
# PART 12: VALIDATING ARGUMENTS
# ============================================
$ divide() {
> if [ "$2" -eq 0 ]; then
> echo "Error: division by zero" >&2
> return 1
> fi
> echo $(( $1 / $2 ))
> return 0
> }
$ result=$(divide 10 2) && echo "Result: $result"
[ Result: 5 ]
$ result=$(divide 10 0) || echo "Failed"
[ Error: division by zero ]
[ Failed ]
# ============================================
# PART 13: UNIQUE TEMP FILE WITH FUNCTION
# ============================================
$ make_temp() {
> mktemp "/tmp/${1:-script}.XXXXXX"
> }
$ f=$(make_temp backup)
$ echo "$f"
[ /tmp/backup.aB3xYz ]
Quick Reference
Defining Functions
| Syntax | Portability |
|---|---|
function name() { ... } | Bash, ksh |
name() { ... } | POSIX — all shells |
function name { ... } | Bash only |
Calling Functions
| Command | Purpose |
|---|---|
name | Call the function |
name arg1 arg2 | Call with arguments |
$(name args) | Capture output |
Inside a Function
| Variable | Meaning |
|---|---|
$1, $2, … | Arguments |
$# | Number of arguments |
$@ | All arguments (separate) |
$* | All arguments (one word) |
$0 | Script name (not function name) |
$? | Exit status of last command |
Returning Values
| Method | Purpose | Example |
|---|---|---|
echo | Return value | result=$(add 5 3) |
return N | Return status (0–255) | if fn; then ... |
| Global var | Simple flag | RESULT=... |
readarray | Multiple values | readarray -t a < <(fn) |
Scope
| Keyword | Effect |
|---|---|
| (none) | Global |
local | Function-local |
declare | Declare inside function |
Managing Functions
| Command | Purpose |
|---|---|
declare -f | List all functions |
declare -f name | Show one function |
unset -f name | Remove a function |
Best Practices
✅ Do This:
# Use POSIX syntax for portability
myfunc() { ... } # ✅
# Use local for internal variables
myfunc() {
local tmp="x"
} # ✅
# Validate arguments
if [ $# -ne 2 ]; then
echo "Usage: $0 <a> <b>" >&2
exit 1
fi # ✅
# Return values with echo
add() { echo $(( $1 + $2 )); }
result=$(add 5 3) # ✅
# Return status with return
is_even() { [ $(( $1 % 2 )) -eq 0 ]; } # ✅
# Use "$@" to preserve argument boundaries
for arg in "$@"; do ...; done # ✅
# Provide defaults
name="${1:-Guest}" # ✅
# Put functions at the top
# ... functions ...
main "$@" # ✅
# Use descriptive names
log_message() { ... } # ✅
❌ Don’t Do This:
# Don't use global variables for return values
RESULT=""
add() { RESULT=$(( $1 + $2 )); } # ⚠️ fragile
# Don't return values > 255
return 300 # ❌ wraps to 44
# Don't forget local
myfunc() {
tmp="x" # ❌ leaks to global
} # ✅ use local tmp="x"
# Don't call a function before defining it
greet # ❌ not yet defined
greet() { echo "Hi"; }
# Don't use $0 as function name
echo "Function: $0" # ❌ it's the script name
# Don't trust unvalidated arguments
add() { echo $(( $1 + $2 )); } # ❌ fails if $1 is empty
# Don't use function keyword if you want portability
function myfunc() { ... } # ⚠️ bash-only
# Don't nest functions unnecessarily
outer() {
inner() { ... } # ⚠️ inner redefined each call
}
Common Pitfalls
| Pitfall | Problem | Solution |
|---|---|---|
| Called before defined | command not found | Define first |
return 300 | Wraps to 44 | Use echo for values |
Forgot local | Variable leaks | Use local |
$0 inside function | Script name, not function | Save it if needed |
| Argument count | Missing args | Validate $# |
$@ vs $* | Word splitting | Use "$@" |
| Global var return | Overwrites | Prefer echo + $() |
| Nested functions | Redefined each call | Define once outside |
Real-World Examples
1. Simple Greeting
#!/bin/bash
greet() {
echo "Hello, World!"
}
greet
[ Hello, World! ]
2. Greeting with Arguments
#!/bin/bash
greet() {
echo "Hello, $1! You are $2 years old."
}
greet "Kronos" 30
[ Hello, Kronos! You are 30 years old. ]
3. Add Function
#!/bin/bash
add() {
local sum=$(( $1 + $2 ))
echo $sum
}
result=$(add 5 3)
echo "The sum is: $result"
[ The sum is: 8 ]
4. Logging Function
#!/bin/bash
log() {
echo "[$(date +%T)] $1"
}
log "Starting"
log "Done"
[ [10:00:00] Starting ]
[ [10:00:00] Done ]
5. Error Function
#!/bin/bash
error() {
echo "[ERROR] $1" >&2
return 1
}
error "File not found"
[ [ERROR] File not found ]
6. Check Even/Odd
#!/bin/bash
is_even() {
[ $(( $1 % 2 )) -eq 0 ]
}
if is_even 4; then echo "even"; fi
[ even ]
7. Usage Message
#!/bin/bash
usage() {
echo "Usage: $0 <src> <dst>" >&2
exit 1
}
if [ $# -ne 2 ]; then
usage
fi
8. Cleanup Function
#!/bin/bash
cleanup() {
echo "Cleaning up temp files"
rm -f /tmp/script.*
}
trap cleanup EXIT
echo "Working..."
9. Loop Over Arguments
#!/bin/bash
print_all() {
for arg in "$@"; do
echo "Argument: $arg"
done
}
print_all one two three
[ Argument: one ]
[ Argument: two ]
[ Argument: three ]
10. Default Arguments
#!/bin/bash
greet() {
local name="${1:-Guest}"
echo "Hello, $name!"
}
greet
greet Alice
[ Hello, Guest! ]
[ Hello, Alice! ]
11. Validation Function
#!/bin/bash
is_number() {
[[ "$1" =~ ^[0-9]+$ ]]
}
if is_number "42"; then
echo "It's a number"
fi
[ It's a number ]
12. Divide with Error Handling
#!/bin/bash
divide() {
if [ "$2" -eq 0 ]; then
echo "Error: division by zero" >&2
return 1
fi
echo $(( $1 / $2 ))
}
result=$(divide 10 2) && echo "Result: $result"
result=$(divide 10 0) || echo "Failed"
[ Result: 5 ]
[ Error: division by zero ]
[ Failed ]
13. Multiple Return Values
#!/bin/bash
get_info() {
echo "Alice"
echo "30"
echo "Paris"
}
readarray -t info < <(get_info)
echo "Name: ${info[0]}"
echo "Age: ${info[1]}"
echo "City: ${info[2]}"
[ Name: Alice ]
[ Age: 30 ]
[ City: Paris ]
14. Retry Function
#!/bin/bash
retry() {
local n=$1
shift
for i in $(seq 1 "$n"); do
if "$@"; then
return 0
fi
echo "Attempt $i failed, retrying..."
sleep 1
done
return 1
}
retry 3 curl -s https://example.com
15. Confirm Function
#!/bin/bash
confirm() {
local prompt="${1:-Are you sure?}"
read -r -p "$prompt [y/N] " answer
[[ "$answer" =~ ^[Yy]$ ]]
}
if confirm "Delete files?"; then
echo "Deleting..."
else
echo "Cancelled"
fi
16. Array Return
#!/bin/bash
get_fruits() {
echo "apple banana cherry"
}
read -ra fruits <<< "$(get_fruits)"
for f in "${fruits[@]}"; do
echo "Fruit: $f"
done
[ Fruit: apple ]
[ Fruit: banana ]
[ Fruit: cherry ]
17. Counter with Local
#!/bin/bash
count_up() {
local i
for ((i=1; i<=$1; i++)); do
echo "$i"
done
}
count_up 3
[ 1 ]
[ 2 ]
[ 3 ]
18. Main Function Pattern
#!/bin/bash
log() { echo "[$(date +%T)] $1"; }
error() { echo "[ERROR] $1" >&2; return 1; }
main() {
log "Starting"
if [ $# -eq 0 ]; then
error "No arguments"
return 1
fi
log "Processing: $@"
log "Done"
}
main "$@"
19. Reusable Config Loader
#!/bin/bash
load_config() {
local file="${1:-/etc/myapp.conf}"
if [ ! -f "$file" ]; then
echo "Config not found: $file" >&2
return 1
fi
# shellcheck disable=SC1090
source "$file"
}
load_config ./myapp.conf
echo "Host: $HOST"
20. Colors as Functions
#!/bin/bash
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
success() { echo -e "${GREEN}$1${NC}"; }
failure() { echo -e "${RED}$1${NC}" >&2; }
success "All good"
failure "Something broke"
[ All good ] # in green
[ Something broke ] # in red, on stderr
Visual: Function Anatomy
┌──────────────────────────────────────────────┐
│ Function structure │
│ │
│ name() { │
│ local var="value" │
│ echo "Using $1" │
│ return 0 │
│ } │
│ │ │
│ │ ┌─────────────────────────────────┐ │
│ │ │ name → function name │ │
│ │ │ () → no parameters here │ │
│ │ │ { } → body │ │
│ │ │ local → function-local var │ │
│ │ │ $1 → first argument │ │
│ │ │ echo → output (return val) │ │
│ │ │ return → exit status (0–255) │ │
│ │ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ result=$(name arg1 arg2) │
│ │
└──────────────────────────────────────────────┘
Visual: Return vs Echo
┌──────────────────────────────────────────────┐
│ Two ways to "return" │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ return N (exit status) │ │
│ │ │ │
│ │ myfunc() { │ │
│ │ if [ ... ]; then │ │
│ │ return 0 ← success │ │
│ │ else │ │
│ │ return 1 ← failure │ │
│ │ fi │ │
│ │ } │ │
│ │ │ │
│ │ if myfunc; then echo "ok"; fi │ │
│ └────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────┐ │
│ │ echo VALUE (capture with $()) │ │
│ │ │ │
│ │ myfunc() { │ │
│ │ echo "result" │ │
│ │ } │ │
│ │ │ │
│ │ x=$(myfunc) │ │
│ │ echo "$x" │ │
│ └────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────┘
Summary
| Concept | Syntax | Example |
|---|---|---|
| Define (POSIX) | name() { ... } | greet() { echo hi; } |
| Define (bash) | function name { ... } | function greet { echo hi; } |
| Call | name | greet |
| Call with args | name arg1 arg2 | greet "Kronos" 30 |
| First arg | $1 | echo "$1" |
| Arg count | $# | echo "$#" |
| All args | "$@" | for a in "$@"; do |
| Local variable | local var=val | local sum=$(( $1 + $2 )) |
| Return value | echo ... + $() | x=$(add 5 3) |
| Return status | return N | return 0 |
| Default arg | ${1:-default} | local name="${1:-Guest}" |
| List functions | declare -f | declare -f |
| Remove function | unset -f name | unset -f greet |
Key takeaways:
- Functions encapsulate reusable blocks — define once, call many times
- Two syntaxes:
name() { }(portable) andfunction name { }(bash) - Define functions before calling them
- Arguments are accessed with
$1,$2, …, and$@for all $0inside a function is the script name, not the function name- Use
localto keep variables from leaking into the global scope - Functions return a status (0–255), not a value
- To return a value,
echoit and capture with$(...) - Use
return Nfor success/failure andif fn; thento test - For multiple return values, use
echo+readarrayorread - Use
"$@"in loops to preserve argument boundaries - Always validate arguments — check
$#and provide defaults - The
mainfunction pattern — define functions at the top, then callmain "$@"at the bottom
Remember: Functions turn a script from a list of commands into a program. Define them at the top, use local for internals, pass arguments as $1/$2, return values with echo and $(...), and use return for status. Watch out for the two big gotchas: $0 is the script name, and return only takes 0–255. Master functions, and your scripts become modular, testable, and readable.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!