Local Variables and Getting a Value Back
local keeps a function's variables to itself, so it can't change the script's by accident; a function hands back a result by printing it with echo, which the caller catches with $( ), and answers yes or no with return, which if can test.
Journeyman · Unix · Linux · Updated
After this lesson you can
- Keep a function's variables to itself with local
- Get a result from a function with echo and $( ), and a yes or no with return
Key takeaways
- A script's variables are shared with every function in it. local makes a variable the function's own, so it can't change the script's.
- Make every variable inside a function local, the loop's variable too.
- A function hands back a result by printing it, and total=$(sum_minutes 4 3 5) catches it, the same as $(date +%F).
- Everything a function prints is caught, so a function that answers with echo prints nothing else.
- return sets the function's exit code: 0 for yes, 1 for no, ready for if. It can't hand back a number such as 300.
The drive to the coast takes 45 minutes, and you want to know whether the mix fills it. You add a function that adds up half the mix, run the script, and it says the drive is 13 minutes long. Nothing you typed says 13. Picture an office whiteboard that everyone shares: you write the drive's length on it, a coworker uses the same corner for a sum, and your number is gone. The function did its sums on the shared whiteboard. This lesson gives a function scratch paper of its own, and two ways to hand an answer back: a result, such as a total, or a plain yes or no. Every function in a real tool does one or the other; in spend, one function will work out a month's total and another will answer "is this a real date?".
A function that changes your variables
Go into the practice folder from Functions: Naming a Job (this line makes it, if it isn't there yet):
mkdir -p ~/terminal-practice/shuffler && cd ~/terminal-practice/shuffler
Write drive.sh, make it runnable with chmod +x drive.sh, and run it:
#!/usr/bin/env bash
# drive.sh: how long is each half of the mix, and is the drive long enough?
add_up() {
total=0
for m in "$@"; do
total=$((total + m))
done
echo "This half: $total minutes"
}
total=45
add_up 4 3 5
add_up 4 6 3
echo "The drive: $total minutes"
This half: 12 minutes
This half: 13 minutes
The drive: 13 minutes
The script set total=45, the drive's length, and then add_up used a variable with the same name for its sum. In a script, every variable is global: shared by the whole script, functions included. The function wrote over the drive's length, and nothing warned you.
local: the function's own scratch paper
Change the first line inside add_up to this, and run ./drive.sh again:
local total=0 m
This half: 12 minutes
This half: 13 minutes
The drive: 45 minutes
local makes total and m this function's own local variables. They exist while it runs and disappear when it ends, and the script's total is left alone. Make every variable inside a function local, the loop's variable (m) too. local works only inside a function: at the prompt, or in the main part of a script, bash stops with local: can only be used in a function.
Handing back a result: echo and $( )
add_up prints a sentence, which is fine for reading but no use for adding. A function's answer is what it prints, so make it print only the number, and catch it with $( ), the way today=$(date +%F) catches the date (Values from Commands: $( ) and $(( ))). To bash, your function is one more command:
#!/usr/bin/env bash
# drive.sh: how long is the mix?
sum_minutes() {
local total=0 m
for m in "$@"; do
total=$((total + m))
done
echo "$total"
}
first=$(sum_minutes 4 3 5)
second=$(sum_minutes 4 6 3)
echo "The mix: $((first + second)) minutes"
The mix: 25 minutes
$( ) catches everything the function prints. Add a friendly echo "Adding up $# songs..." inside sum_minutes, and first holds that sentence as well as the number, so the sum on the last line stops with an arithmetic error. A function that answers with echo prints its answer and nothing else.
return: a yes or no
Some questions have no number for an answer: does the mix fit the drive? Make the drive's length the script's first argument, and add a function that answers yes or no:
#!/usr/bin/env bash
# drive.sh: does the mix fit the drive?
# Usage: ./drive.sh MINUTES
sum_minutes() {
local total=0 m
for m in "$@"; do
total=$((total + m))
done
echo "$total"
}
fits() {
local minutes=$1 limit=$2
if [[ $minutes -gt $limit ]]; then
return 1
fi
return 0
}
mix=$(sum_minutes 4 3 5 4 6 3)
if fits "$mix" "$1"; then
echo "The $mix-minute mix fits the $1-minute drive."
else
echo "The $mix-minute mix is too long for the $1-minute drive."
fi
./drive.sh 45
./drive.sh 20
The 25-minute mix fits the 45-minute drive.
The 25-minute mix is too long for the 20-minute drive.
return ends the function and sets its exit code (Did It Work? Exit Codes, && and ||): 0 means yes, it worked, and 1 to 255 mean no. if tests that code, the same as it tests cp or [[ ]] (If This, Then That: if and [[ ]]). Without a return, a function's exit code is its last command's, so fits could be the one line [[ $minutes -le $limit ]].
What return can't do is hand back a result. Exit codes only go from 0 to 255:
add() {
return $(($1 + $2))
}
add 4 3
echo "$?"
add 200 100
echo "$?"
7
44
300 wrapped around to 44, with no warning. Use echo and $( ) for results, and return for yes or no.
Check yourself
2 questions to try on the lesson page.
So what
local keeps each function's work to itself, echo with $( ) hands back a result, and return answers yes or no, which together let functions be pieces you can trust without reading their insides every time.