Step 1 of 5 · Reading · ~3 min
Learn
Functions and Pipes
Functions
By the end of this lesson you can write a function that takes arguments, hands back either text or a yes/no answer, and keeps its variables from leaking into the rest of the script. Bash functions are miniature scripts inside your script: they take arguments the way a script does, report success or failure the way a command does, and "return" data the way commands do, by printing it.
Defining and calling
shout() {
echo "${1^^}!"
}
shout "ready" # READY!
shout "go" # GO!
A call looks like any command, with no parentheses. Arguments arrive as positional parameters: $1, $2, and so on, plus $# (how many) and "$@" (all of them, each kept as one word, which is the only correct way to forward arguments). Nothing is declared in the parentheses after the name; they only mark "this is a function".
Two ways out: text and status
A function has two separate output channels, and they answer different questions.
Data leaves on stdout. return cannot carry a value, so a function prints its result and the caller captures it with command substitution (a later lesson covers it fully):
double() {
echo "$(( $1 * 2 ))"
}
total=$(double 21) # runs the function and captures what it printed
echo "$total" # 42
Anything the function prints becomes part of the captured value, so a stray debug echo corrupts the result. Send diagnostics to stderr with echo "..." >&2.
Yes/no answers leave as the exit status. return N ends the function with status N, where 0 means success and anything else means failure. Because if branches on a command's status, a function can be used directly as a condition:
contains() {
local needle=$1
shift # drop $1, so "$@" is now just the list
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0 # found: stop right here, success
fi
done
return 1 # reached the end: not found
}
if contains "pear" apple pear plum; then
echo "in stock"
fi
return leaves the function immediately, even from inside a loop. It leaves only the function; exit would end the whole script. A function that never says return reports the status of its last command.
local keeps your variables yours
Variables in bash are global by default, even when assigned inside a function:
tally() {
i=100 # silently overwrites the caller's i
}
for i in 1 2 3; do
tally
echo "$i" # prints 100 every time
done
local i creates a variable that exists only for the duration of the call. The rule has no exceptions worth learning: declare every variable inside a function with local, including loop counters. It is the cheapest bug insurance in the language, because a leaked name collides with the caller's variables in ways that depend on call order.
Common mistakes
returnwith data.return "$result"fails unless the value is a number from 0 to 255. Print the value instead.- Printing when you meant a status. A yes/no function that also echoes something pollutes every
ifand$( )that uses it. - Forgetting
localon the loop variable.for i in ...inside a function changes the caller'si. - Using
$0for the first argument.$0is the name of the script. The first argument is$1. - Unquoted calls.
check $namesplits the value into several arguments if it contains spaces; writecheck "$name".
Your exercise
Finish a yes/no function that decides whether a number is prime, and let the script's own loop use it to list primes. Decide what the function reports and through which channel. The loop that calls it uses i and prints it right after each call, so a function that clobbers i gives wrong output.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…