| |

LFCS 125 ๐Ÿง Shell Scripting โ€” Functions

โฑ๏ธ Estimated read: 10 min | |

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

SyntaxPortability
name() { ... }POSIX
function name { ... }Bash/ksh/zsh

Function Arguments

VariableValue
$1, $2Positional arguments
$#Argument count
$@All arguments (separate)
$*All arguments (joined)

Return Mechanisms

MechanismPurpose
return NExit status (0โ€“255)
echo "..."Output (captured by $())
>&2Error output

Variable Scope

DeclarationScope
local var=valueFunction only
var=valueGlobal (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

PitfallWhy It HappensFix
Global variable overwrittenNo localUse local
Function returns wrong valueUsed return "string"Use echo + $()
Function not definedCalled before definitionDefine first
Arguments emptyNot passedPass arguments
Recursion stack overflowNo base caseAdd 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

ItemValue
Function definitionname() { ... }
Function callname arg1 arg2
Arguments$1, $2, $@, $#
Local variablelocal var=value
Exit statusreturn N (0โ€“255)
Outputecho "..." + $()
Error output>&2
RecursionFunction 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.
  • local prevents global pollution. A variable declared local exists only within the function. Without local, the variable is global and can overwrite the caller’s value.
  • return sets the exit status, not a return value. It accepts an integer between 0 and 255. To return a string, use echo and 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.

Was this helpful?


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!