A variable declared as local
is one that is visible only
within the block of code in which it appears. It has local scope. In a
function, a local variable has meaning only within that function
block.
Local variable visibility
#!/bin/bash # ex62.sh: Global and local variables inside a function.
func () { local loc_var=23 # Declared as local variable. echo # Uses the 'local' builtin. echo "\"loc_var\" in function = $loc_var" global_var=999 # Not declared as local. # Therefore, defaults to global. echo "\"global_var\" in function = $global_var" }
func
# Now, to see if local variable "loc_var" exists outside the function.
echo echo "\"loc_var\" outside function = $loc_var" # $loc_var outside function = # No, $loc_var not visible globally. echo "\"global_var\" outside function = $global_var" # $global_var outside function = 999 # $global_var is visible globally. echo
exit 0 # In contrast to C, a Bash variable declared inside a function #+ is local ONLY if declared as such.
Caution: Before a function is called, all variables declared within the function are invisible outside the body of the function, not just those explicitly declared as local.
#!/bin/bash
func () { global_var=37 # Visible only within the function block #+ before the function has been called. } # END OF FUNCTION
echo "global_var = $global_var" # global_var = # Function "func" has not yet been called, #+ so $global_var is not visible here.
func echo "global_var = $global_var" # global_var = 37 # Has been set by function call.
Note: When declaring and setting a local variable in a single command, apparently the order of operations is to first set the variable, and only afterwards restrict it to local scope. This is reflected in the return value.
#!/bin/bash
echo "==OUTSIDE Function (global)==" t=$(exit 1) echo $? # 1 # As expected. echo
function0 () {
echo "==INSIDE Function==" echo "Global" t0=$(exit 1) echo $? # 1 # As expected.
echo echo "Local declared & assigned in same command." local t1=$(exit 1) echo $? # 0 # Unexpected! # Apparently, the variable assignment takes place before #+ the local declaration. #+ The return value is for the latter.
echo echo "Local declared, then assigned (separate commands)." local t2 t2=$(exit 1) echo $? # 1 # As expected.
}
function0
Note: a local variable declared in a function is also visible to functions called by the parent function.
#!/bin/bash
function1 () { local func1var=20
echo "Within function1, \$func1var = $func1var."
function2 }
function2 () { echo "Within function2, \$func1var = $func1var." }
function1
exit 0
# Output of the script:
# Within function1, $func1var = 20. # Within function2, $func1var = 20.
This is documented in the Bash manual: "Local can only be used within a function; it makes the variable name have a visible scope restricted to that function and its children." The ABS Guide author considers this behavior to be a bug.
Recursion is an interesting and sometimes useful form of self-reference. Herbert Mayer defines it as ". . . expressing an algorithm by using a simpler version of that same algorithm . . ."
Consider a definition defined in terms of itself, [1] an expression implicit in its own expression, [2] a snake swallowing its own tail, [3] or . . . a function that calls itself. [4]
Demonstration of a simple recursive function
#!/bin/bash # recursion-demo.sh # Demonstration of recursion.
RECURSIONS=9 # How many times to recurse. r_count=0 # Must be global. Why?
recurse () { var="$1"
while [ "$var" -ge 0 ] do echo "Recursion count = "$r_count" +-+ \$var = "$var"" (( var-- )); (( r_count++ )) recurse "$var" # Function calls itself (recurses) done #+ until what condition is met? }
recurse $RECURSIONS
exit $?
Another simple demonstration
#!/bin/bash # recursion-def.sh # A script that defines "recursion" in a rather graphic way.
RECURSIONS=10 r_count=0 sp=" "
define_recursion () { ((r_count++)) sp="$sp"" " echo -n "$sp" echo "\"The act of recurring ... \"" # Per 1913 Webster's dictionary.
while [ $r_count -le $RECURSIONS ] do define_recursion done }
echo echo "Recursion: " define_recursion echo
exit $?
Local variables are a useful tool for writing recursive code, but this practice generally involves a great deal of computational overhead and is definitely not recommended in a shell script.
Recursion, using a local variable
#!/bin/bash
# factorial # ---------
# Does bash permit recursion? # Well, yes, but... # It's so slow that you gotta have rocks in your head to try it.
MAX_ARG=5 E_WRONG_ARGS=85 E_RANGE_ERR=86
if [ -z "$1" ] then echo "Usage: `basename $0` number" exit $E_WRONG_ARGS fi
if [ "$1" -gt $MAX_ARG ] then echo "Out of range ($MAX_ARG is maximum)." # Let's get real now. # If you want greater range than this, #+ rewrite it in a Real Programming Language. exit $E_RANGE_ERR fi
fact () { local number=$1 # Variable "number" must be declared as local, #+ otherwise this doesn't work. if [ "$number" -eq 0 ] then factorial=1 # Factorial of 0 = 1. else let "decrnum = number - 1" fact $decrnum # Recursive function call (the function calls itself). let "factorial = $number * $?" fi
return $factorial }
fact $1 echo "Factorial of $1 is $?."
exit 0
Also see TODO Example A-15 for an example of recursion in a script. Be aware that recursion is resource-intensive and executes slowly, and is therefore generally not appropriate in a script.
Note: Too many levels of recursion may crash a script with a segfault.
#!/bin/bash
# Warning: Running this script could possibly lock up your system! # If you're lucky, it will segfault before using up all available memory.
recursive_function () { echo "$1" # Makes the function do something, and hastens the segfault. (( $1 < $2 )) && recursive_function $(( $1 + 1 )) $2; # As long as 1st parameter is less than 2nd, #+ increment 1st and recurse. }
recursive_function 1 50000 # Recurse 50,000 levels! # Most likely segfaults (depending on stack size, set by ulimit -m).
# Recursion this deep might cause even a C program to segfault, #+ by using up all the memory allotted to the stack.
echo "This will probably not print." exit 0 # This script will not exit normally.
A function may recursively call itself even without use of local variables.
The Fibonacci Sequence
#!/bin/bash # fibo.sh : Fibonacci sequence (recursive) # Author: M. Cooper # License: GPL3
# ----------algorithm-------------- # Fibo(0) = 0 # Fibo(1) = 1 # else # Fibo(j) = Fibo(j-1) + Fibo(j-2) # ---------------------------------
MAXTERM=15 # Number of terms (+1) to generate. MINIDX=2 # If idx is less than 2, then Fibo(idx) = idx.
Fibonacci () { idx=$1 # Doesn't need to be local. Why not? if [ "$idx" -lt "$MINIDX" ] then echo "$idx" # First two terms are 0 1 ... see above. else (( --idx )) # j-1 term1=$( Fibonacci $idx ) # Fibo(j-1)
(( --idx )) # j-2 term2=$( Fibonacci $idx ) # Fibo(j-2)
echo $(( term1 + term2 )) fi # An ugly, ugly kludge. # The more elegant implementation of recursive fibo in C #+ is a straightforward translation of the algorithm in lines 7 - 10. }
for i in $(seq 0 $MAXTERM) do # Calculate $MAXTERM+1 terms. FIBO=$(Fibonacci $i) echo -n "$FIBO " done # 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 # Takes a while, doesn't it? Recursion in a script is slow.
echo
exit 0
#! /bin/bash # # The Towers Of Hanoi # Bash script # Copyright (C) 2000 Amit Singh. All Rights Reserved. # http://hanoi.kernelthread.com # # Tested under Bash version 2.05b.0(13)-release. # Also works under Bash version 3.x. # # Used in "Advanced Bash Scripting Guide" #+ with permission of script author. # Slightly modified and commented by ABS author.
#=================================================================# # The Tower of Hanoi is a mathematical puzzle attributed to #+ Edouard Lucas, a nineteenth-century French mathematician. # # There are three vertical posts set in a base. # The first post has a set of annular rings stacked on it. # These rings are disks with a hole drilled out of the center, #+ so they can slip over the posts and rest flat. # The rings have different diameters, and they stack in ascending #+ order, according to size. # The smallest ring is on top, and the largest on the bottom. # # The task is to transfer the stack of rings #+ to one of the other posts. # You can move only one ring at a time to another post. # You are permitted to move rings back to the original post. # You may place a smaller ring atop a larger one, #+ but *not* vice versa. # Again, it is forbidden to place a larger ring atop a smaller one. # # For a small number of rings, only a few moves are required. #+ For each additional ring, #+ the required number of moves approximately doubles, #+ and the "strategy" becomes increasingly complicated. # # For more information, see http://hanoi.kernelthread.com #+ or pp. 186-92 of _The Armchair Universe_ by A.K. Dewdney. # # # ... ... ... # | | | | | | # _|_|_ | | | | # |_____| | | | | # |_______| | | | | # |_________| | | | | # |___________| | | | | # | | | | | | # .--------------------------------------------------------------. # |**************************************************************| # #1 #2 #3 # #=================================================================#
E_NOPARAM=66 # No parameter passed to script. E_BADPARAM=67 # Illegal number of disks passed to script. Moves= # Global variable holding number of moves. # Modification to original script.
dohanoi() { # Recursive function. case $1 in 0) ;; *) dohanoi "$(($1-1))" $2 $4 $3 echo move $2 "-->" $3 ((Moves++)) # Modification to original script. dohanoi "$(($1-1))" $4 $3 $2 ;; esac }
case $# in 1) case $(($1>0)) in # Must have at least one disk. 1) # Nested case statement. dohanoi $1 1 3 2 echo "Total moves = $Moves" # 2^n - 1, where n = # of disks. exit 0; ;; *) echo "$0: illegal value for number of disks"; exit $E_BADPARAM; ;; esac ;; *) echo "usage: $0 N" echo " Where \"N\" is the number of disks." exit $E_NOPARAM; ;; esac
# Exercises: # --------- # 1) Would commands beyond this point ever be executed? # Why not? (Easy) # 2) Explain the workings of the workings of the "dohanoi" function. # (Difficult -- see the Dewdney reference, above.)