Sign in
← Terminal: bash scripts
5 min · medium · Lesson 5 of 5

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:

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

What you'll need for the tasks

Practice in your browser

Linux runs right here. Solve the task and we'll save your progress automatically.

01

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.

{ i }

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.

Get access for $49

Tell the agent: use MCP izi, give me lesson terminal-scripts-05-function-script