Functions in bash
Declare a function and call it with an argument.
A script grows, and the same pieces start showing up in it: print a message to a log, check a directory, stop with an error. Copying them five times is annoying, and fixing them later in all five places is worse. A function gives a piece like that a name. From then on it's called by that name, like an ordinary command.
Where to practice
The examples below work in an ubuntu:24.04 container (/bin/sh is
dash). You can start the same one yourself:
docker run --rm -it ubuntu:24.04 bash
On Linux, macOS and in WSL2 the commands work the same way in your own
terminal. We create script files with cat > file <<'EOF', same as in
previous lessons.
Declare and call
cat > /tmp/util.sh <<'EOF'
#!/bin/sh
greet() {
echo "hello, $1"
}
greet world
greet "dear friend"
EOF
sh /tmp/util.sh
hello, world
hello, dear friend
What it's made of:
greet() { ... }declares a function namedgreet. The parentheses are always empty, arguments are never listed there.- The body between
{and}runs on every call. The declaration itself runs nothing. - A call is written as a command:
greet world, without parentheses. Arguments are separated by spaces, and a value with spaces goes in quotes.
You can check that the shell knows a function with type:
greet() { echo "hello, $1"; }
type greet
greet is a function
greet ()
{
echo "hello, $1"
}
If the body is on one line, it needs a ; before }, and a space after
{. { and } are separate words for the shell, like [ and ] in the
previous lesson.
Two forms
bash also has a form with the word function:
function greet {
echo "hello, $1"
}
It only works in bash. dash (sh on Ubuntu and Debian) doesn't know it:
/tmp/fk.sh: 1: function: not found
/tmp/fk.sh: 3: Syntax error: "}" unexpected
The form name() { ...; } is described in POSIX and works in both bash and
dash. This lesson uses it everywhere.
Function arguments
Inside a function, $1, $2, $# and "$@" refer to the function's own
arguments, not the script's. After returning from the function, the script
has its own arguments back. $0 stays the script's name throughout.
cat > /tmp/args.sh <<'EOF'
show() {
echo "in function: \$#=$# \$1=$1 \$@=$@"
}
echo "in script: \$#=$# \$1=$1"
show a b c
show "$@"
EOF
bash /tmp/args.sh x y
in script: $#=2 $1=x
in function: $#=3 $1=a $@=a b c
in function: $#=2 $1=x $@=x y
To hand a function the script's own arguments, you pass them explicitly:
show "$@" or show "$1". The function doesn't see them on its own.
Declare before calling
The shell reads a script top to bottom. If you call a function before it's declared, it's just an unknown command:
cat > /tmp/b.sh <<'EOF'
#!/bin/bash
greet world
greet() { echo "hello, $1"; }
EOF
bash /tmp/b.sh
/tmp/b.sh: line 2: greet: command not found
That's why functions are collected at the top of a script, and the main work is written after them.
Exit code: return
Like any command, a function has an exit code. It's set by return N.
Without return, a function returns the exit code of the last command in
its body. Since that's an exit code, you can put a function straight into
an if:
has_root() { grep -q root /etc/passwd; }
if has_root; then echo "root exists"; fi
root exists
return only works for "succeeded / failed". The exit code fits in 0-255,
anything larger gets truncated:
f() { return 300; }; f; echo "rc=$?"
rc=44
300 modulo 256 is 44. Nothing will show an error, the script just gets a
different number. So you can't count files, a size or a sum through
return.
A function's result: echo and $( )
A function prints its value, and the caller grabs its output through
$( ), like the output of any command:
add() { echo $(( $1 + $2 )); }
s=$(add 2 3)
echo "s=$s"
s=5
There's a trap here. $( ) grabs all of a function's stdout, not just the
last line. If the function also prints what it's doing, that ends up in
the variable too:
add() { echo "computing $1+$2"; echo $(( $1 + $2 )); }
s=$(add 2 3)
echo "s=[$s]"
s=[computing 2+3
5]
Messages for a human go to stderr through >&2. $( ) doesn't grab it,
and the line is visible in the terminal:
add() { echo "computing $1+$2" >&2; echo $(( $1 + $2 )); }
s=$(add 2 3)
echo "s=[$s]"
computing 2+3
s=[5]
Variables: global and local
A variable a function sets is visible to the whole script. If its name matches a variable from outside, the function overwrites it:
name=prod
f() { name=test; }
f
echo "name=$name"
name=test
local makes a variable visible only inside the function:
name=prod
f() { local name=test; echo "inside name=$name"; }
f
echo "name=$name"
inside name=test
name=prod
local works in bash and in dash, even though it's not in the POSIX
standard. Outside a function it doesn't work:
local: can only be used in a function.
exit inside a function
return exits the function, exit exits the whole script. This is how you
build a "stop with an error" function:
die() { echo "error: $1" >&2; exit 1; }
die "no file"
echo "we won't get here"
error: no file
The script's exit code here is 1. It's convenient to call it right after a
check: [ -f "$f" ] || die "no file $f".
Common mistakes
- Calling with parentheses,
greet(world):syntax error near unexpected token. Parentheses only belong in the declaration. - A one-line body without
;before}: the shell keeps looking for the end of the function and fails withunexpected end of file. function name { ... }in a script run throughsh:function: not found.- Calling before declaring:
command not found, and the script keeps going. - A number through
return: anything above 255 gets truncated without a warning. - An
echowith a message inside a function whose output is captured through$( ): the message ends up in the variable. Put messages in>&2. - A variable inside a function without
localoverwrote the script's variable. - A function named after an existing command (
ls() { ...; }) replaces that command until the end of the script.type nameshows what will actually run.
What you'll need for the tasks
- Declaring a function as
name() { ...; }at the top of a script and calling it without parentheses. - Reading
$1,$2inside a function. - Returning yes/no through
return 0/return 1and checking a function inifor with||. - Returning a number through
echoand$( ), and writing messages to>&2. - Stopping the script from inside a function with
exitand a code. - Everything from the module so far: the shebang and
chmod +x,$1and$#, a sum of numbers from a file,ifwith[.
Practice in your browser
Linux runs right here. Solve the task and we'll save your progress automatically.
The greet function
Create /root/util.sh: declare a function greet that takes one argument and prints "hello, <arg>". At the end of the file, call greet world. Running sh /root/util.sh should print the line hello, world.
0%
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson terminal-scripts-05-function-script
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress