LFCS 125 ๐ง Shell Scripting โ Functions
A function is a named block of commands that can be called multiple times within a script. It is the mechanism for eliminating repetition and for encapsulating logic that would otherwise be duplicated. The LFCS exam tests the syntax of the function definition, the scope of variables within a function, the mechanism for returning values, and the use of local to avoid polluting the global namespace. The critical distinction is between the function’s exit status (an integer returned by return) and the function’s output (a string returned by echo and captured with command substitution). A function can return only one integer status, but it can produce any amount of output.
Key point: The return command in a function sets the function’s exit status, which is limited to the range 0โ255. The echo command produces output that the caller can capture with $(function_name). The local keyword declares a variable that is scoped to the function. Without local, a variable assigned inside a function is global and can overwrite a variable from the caller.
Why functions require careful scoping
The global variable problem. The shell does not have block scope by default. A variable assigned inside a function is global unless it is declared with local. A function that uses i as a loop counter will overwrite the caller’s i variable, which may be a loop counter in the calling code. The local keyword prevents this by creating a variable that exists only within the function’s execution.
The return value problem. The return command is not for returning arbitrary values. It sets the function’s exit status, which is an integer between 0 and 255. A function that tries to return "hello" produces an error or a truncated value. The correct way to return a string is to echo it and have the caller capture the output with $(...).
The argument problem. A function receives arguments the same way a script does: $1, $2, $#, $@. The $0 inside a function is still the script name, not the function name. The local variables and the positional parameters are the only context that the function has. The function cannot access the caller’s local variables.
The recursion problem. A function can call itself. The shell does not impose a recursion limit, so a function without a base case will consume memory until the process is killed. The base case is the caller’s responsibility.
The trade-off. A function is not a compiled subroutine. It is a named block of commands that the shell interprets each time it is called. The function call overhead is minimal, but the function body is re-parsed on every call. For a script with many iterations, the function call overhead is measurable but rarely significant.
a. Defining and calling a function
A function is defined with the function_name() { ... } syntax or with the function function_name { ... } syntax. The second form is a bash extension. The first form is POSIX.
#!/bin/bash
greet() {
echo "Hello, $1"
}
greet "World"
# Output: Hello, World
The function is defined before it is called. The definition does not execute the body; it only registers the name. The call runs the body with the arguments provided.
The function keyword is optional but is sometimes used to distinguish a function definition from a command invocation.
function greet {
echo "Hello, $1"
}
The function keyword is not POSIX. It is supported by bash, ksh, and zsh, but not by dash. The name() { ... } form is portable.
b. Arguments, local variables, and return values
A function receives arguments as $1, $2, etc. The $# is the number of arguments. The "$@" is all arguments as separate words.
#!/bin/bash
add() {
local a="$1"
local b="$2"
echo $((a + b))
}
result=$(add 3 5)
echo "Sum: $result"
# Output: Sum: 8
The local keyword declares a variable that is scoped to the function. The variable is not visible to the caller. When the function returns, the local variable is removed.
The return command sets the function’s exit status. The caller checks it with $? or uses the function directly in an if statement.
#!/bin/bash
is_even() {
local num="$1"
if (( num % 2 == 0 )); then
return 0
else
return 1
fi
}
if is_even 4; then
echo "4 is even"
fi
The is_even function returns 0 (success) if the number is even, 1 (failure) if not. The if statement evaluates the exit status.
The echo output and the return status are separate. The function can produce output and also return a status.
#!/bin/bash
process_file() {
if [ ! -f "$1" ]; then
echo "Error: file not found" >&2
return 1
fi
cat "$1"
return 0
}
output=$(process_file /etc/hostname)
status=$?
The output captures the standard output. The status captures the exit status. The error message goes to stderr, which is not captured by the assignment.
c. The local keyword and variable scope
The local keyword is the mechanism for avoiding global pollution. A variable declared local exists only within the function.
#!/bin/bash
count=10
increment() {
local count=5
count=$((count + 1))
echo "Inside: $count"
}
increment
echo "Outside: $count"
# Output: Inside: 6
# Outside: 10
The local count=5 creates a function-scoped variable. The count inside the function is independent of the count outside. The increment inside the function changes the local variable, not the global one.
Without local, the assignment overwrites the global variable.
#!/bin/bash
count=10
increment() {
count=$((count + 1))
echo "Inside: $count"
}
increment
echo "Outside: $count"
# Output: Inside: 11
# Outside: 11
The global count is modified because the function did not declare a local variable. This is the most common source of subtle bugs in shell scripts.
The local keyword is not POSIX. It is supported by bash, dash, and ksh, but not by all shells. The local in a #!/bin/sh script may fail on some systems.
Complete Example Session
# ============================================
# PART 1: BASIC FUNCTION
# ============================================
cat > greet.sh <<'EOF'
#!/bin/bash
greet() {
echo "Hello, $1"
}
greet "World"
EOF
chmod +x greet.sh
./greet.sh
# Output: Hello, World
# ============================================
# PART 2: FUNCTION WITH ARGUMENTS
# ============================================
cat > add.sh <<'EOF'
#!/bin/bash
add() {
local a="$1"
local b="$2"
echo $((a + b))
}
result=$(add 3 5)
echo "Sum: $result"
EOF
chmod +x add.sh
./add.sh
# Output: Sum: 8
# ============================================
# PART 3: RETURN STATUS
# ============================================
cat > is_even.sh <<'EOF'
#!/bin/bash
is_even() {
local n="$1"
if (( n % 2 == 0 )); then
return 0
else
return 1
fi
}
if is_even 4; then
echo "4 is even"
fi
EOF
chmod +x is_even.sh
./is_even.sh
# Output: 4 is even
# ============================================
# PART 4: LOCAL SCOPE
# ============================================
cat > local.sh <<'EOF'
#!/bin/bash
count=10
increment() {
local count=5
count=$((count + 1))
echo "Inside: $count"
}
increment
echo "Outside: $count"
EOF
chmod +x local.sh
./local.sh
# Output: Inside: 6
# Outside: 10
# ============================================
# PART 5: WITHOUT LOCAL
# ============================================
cat > nolocal.sh <<'EOF'
#!/bin/bash
count=10
increment() {
count=$((count + 1))
echo "Inside: $count"
}
increment
echo "Outside: $count"
EOF
chmod +x nolocal.sh
./nolocal.sh
# Output: Inside: 11
# Outside: 11
# ============================================
# PART 6: OUTPUT CAPTURE
# ============================================
cat > capture.sh <<'EOF'
#!/bin/bash
today() {
date +%Y-%m-%d
}
echo "Today: $(today)"
EOF
chmod +x capture.sh
./capture.sh
# Output: Today: 2026-10-29
# ============================================
# PART 7: ERROR HANDLING
# ============================================
cat > check.sh <<'EOF'
#!/bin/bash
require_file() {
if [ ! -f "$1" ]; then
echo "Missing: $1" >&2
return 1
fi
return 0
}
if ! require_file /etc/passwd; then
exit 1
fi
echo "File exists"
EOF
chmod +x check.sh
./check.sh
# Output: File exists
# ============================================
# PART 8: RECURSIVE FUNCTION
# ============================================
cat > factorial.sh <<'EOF'
#!/bin/bash
factorial() {
local n="$1"
if [ "$n" -le 1 ]; then
echo 1
else
local prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
echo "5! = $(factorial 5)"
EOF
chmod +x factorial.sh
./factorial.sh
# Output: 5! = 120
# ============================================
# PART 9: FUNCTION WITH DEFAULT ARG
# ============================================
cat > default.sh <<'EOF'
#!/bin/bash
greet() {
local name="${1:-stranger}"
echo "Hello, $name"
}
greet
greet "World"
EOF
chmod +x default.sh
./default.sh
# Output: Hello, stranger
# Hello, World
The nine parts trace the functions: basic function, arguments, return status, local scope, no-local scope, output capture, error handling, recursion, and default argument.
Quick Reference
Function Definition
| Syntax | Portability |
|---|---|
name() { ... } | POSIX |
function name { ... } | Bash/ksh/zsh |
Function Arguments
| Variable | Value |
|---|---|
$1, $2 | Positional arguments |
$# | Argument count |
$@ | All arguments (separate) |
$* | All arguments (joined) |
Return Mechanisms
| Mechanism | Purpose |
|---|---|
return N | Exit status (0โ255) |
echo "..." | Output (captured by $()) |
>&2 | Error output |
Variable Scope
| Declaration | Scope |
|---|---|
local var=value | Function only |
var=value | Global (or inherited) |
Best Practices
โ Do This:
myfunc() {
local var="$1"
echo "$var"
}
<!-- Use local for function variables --> # โ
result=$(myfunc arg)
<!-- Capture output with command substitution --> # โ
if myfunc; then
echo "success"
fi
<!-- Check exit status with if --> # โ
โ Don’t Do This:
myfunc() {
return "hello"
}
<!-- return only accepts integers 0-255 --> # โ
myfunc() {
var="value"
}
<!-- Without local, var is global --> # โ
function myfunc() {
...
}
<!-- Function keyword plus () is unusual --> # โ
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Global variable overwritten | No local | Use local |
| Function returns wrong value | Used return "string" | Use echo + $() |
| Function not defined | Called before definition | Define first |
| Arguments empty | Not passed | Pass arguments |
| Recursion stack overflow | No base case | Add base case |
Real-World Examples
1. Basic Function
greet() { echo "Hello, $1"; }
greet World
2. Function with Return Status
is_valid() { [ -n "$1" ]; }
if is_valid "x"; then echo "valid"; fi
3. Function with Output
get_date() { date +%Y-%m-%d; }
today=$(get_date)
4. Local Variable
f() { local x=1; echo "$x"; }
5. Error Handling
f() { if [ ! -f "$1" ]; then return 1; fi; }
6. Default Argument
f() { local name="${1:-default}"; echo "$name"; }
7. Recursion
fact() { if [ "$1" -le 1 ]; then echo 1; else echo $(( $1 * $(fact $(( $1 - 1 ))) )); fi; }
8. Multiple Returns
f() { echo "out"; return 0; }
9. Function as Command
f() { ls /tmp; }
f
10. Catching Errors
f() { echo "err" >&2; return 1; }
if ! f; then echo "handled"; fi
Visual
Function Call Flow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Script starts โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Function definition (registered) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Function call with arguments โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Function body executes โ
โ โโโ local variables created โ
โ โโโ commands run โ
โ โโโ return N (or last command status) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Caller receives exit status ($?) โ
โ or output ($(function)) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Local vs Global
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ count=10 (global) โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โ function call
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ local count=5 (function scope) โ
โ count += 1 โ 6 โ
โโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ After function returns โ
โ global count is still 10 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Function definition | name() { ... } |
| Function call | name arg1 arg2 |
| Arguments | $1, $2, $@, $# |
| Local variable | local var=value |
| Exit status | return N (0โ255) |
| Output | echo "..." + $() |
| Error output | >&2 |
| Recursion | Function calling itself |
| Default argument | ${1:-default} |
Key takeaways:
- Functions are named blocks of commands. They are defined with
name() { ... }and called by name. They receive arguments as$1,$2, and the other positional parameters. localprevents global pollution. A variable declaredlocalexists only within the function. Withoutlocal, the variable is global and can overwrite the caller’s value.returnsets the exit status, not a return value. It accepts an integer between 0 and 255. To return a string, useechoand capture the output with$(...).- The function’s exit status is the exit status of the last command. If the function does not call
return, the status is the status of the last command in the body. - The function body is re-parsed on every call. The shell does not compile functions. The overhead is minimal but measurable in tight loops.
Remember: The function is the reuse mechanism. The local is the scope. The return is the status. The echo is the output. The $() is the capture. The exam tests the syntax, the scope, and the return mechanism. The most common mistake is forgetting local and overwriting a global variable.
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!