Functions: Naming a Job

A function keeps a few commands under one name, so a script says the job's name instead of repeating its lines: define it with name() { }, call it by its name like any command, and hand it arguments, which it reads as $1, $# and "$@".

Journeyman · Unix · Linux · Updated

After this lesson you can

  • Define a function with name() { } and call it by its name after the definition
  • Pass arguments to a function and read them inside it with $1, $# and "$@"

Key takeaways

  • A function is a few commands kept under one name: name() { commands }. Defining it runs nothing; calling it by its name runs its commands.
  • Bash reads a script from the top, so a function must be defined above the first line that calls it.
  • Inside a function, $1, $# and "$@" are the function's own arguments, not the script's. Pass the script's on, as in banner "$1".
  • A job written once as a function is fixed once: change the function, and every call gets the change.

You're making the music for Saturday's road trip with a script that prints the mix a part at a time, each part under a banner of dashes. The banner is three lines, and by the end of the summer it's in the script ten times. Then you decide the dashes should be longer, and you're changing thirty lines and missing one. A cookbook has a better habit: it writes the steps for the basic dough once, on its own page, and every recipe that needs dough says "make the dough (page 12)". A function is that page in a script. It's how every real tool is built: an administrator's setup script is a list of jobs such as install_packages and create_user, and spend, the expense tracker you'll build in this module, is a handful of jobs such as add and report.

The same lines, again and again

Make this chapter's practice folder. Over the chapter it becomes a playlist shuffler:

mkdir -p ~/terminal-practice/shuffler && cd ~/terminal-practice/shuffler

Write mix.sh with nano mix.sh:

#!/usr/bin/env bash
# mix.sh: the songs for Saturday's road trip.
echo "------------------------------"
echo "  Road trip"
echo "------------------------------"
echo "Highway Tea"
echo "Paper Lanterns"
echo "------------------------------"
echo "  Coming home"
echo "------------------------------"
echo "Window Down"
echo "Home by Dark"

Make it runnable and run it:

chmod +x mix.sh
./mix.sh
------------------------------
  Road trip
------------------------------
Highway Tea
Paper Lanterns
------------------------------
  Coming home
------------------------------
Window Down
Home by Dark

The dashed line is typed four times, and the banner's shape twice. Every copy is one more place to forget when something changes.

Defining a function and calling it

Aliases and Small Functions wrote a short function at the prompt. In a script, a function works the same way. Here's one for the dashed line:

divider() {
  echo "------------------------------"
}

Read it as "divider is the name for these commands". divider() (read "divider, brackets") starts a function named divider, and its commands sit between the braces, { and }. Defining a function runs nothing; it teaches the script a new word. Calling it, by typing its name like any other command, runs its commands.

The order matters, because bash reads a script from the top, one line at a time. Put a call above the definition, and bash hasn't learned the word yet. Try this version of mix.sh:

#!/usr/bin/env bash
# mix.sh: the songs for Saturday's road trip.
divider

divider() {
  echo "------------------------------"
}

divider
echo "  Road trip"
divider
./mix.sh: line 3: divider: command not found
------------------------------
  Road trip
------------------------------

Line 3 failed, and the calls below the definition worked. So functions go at the top of a script, and the lines that call them go underneath.

Arguments: what you hand a function

A banner needs a different title each time. You hand a function arguments the way you hand any command arguments: after its name. Inside the function they're $1, $2, $# and "$@", the same as in Arguments: $1, $# and "$@". A function can also call another function:

banner() {
  divider
  echo "  $1"
  divider
}

banner "Road trip"

One surprise: inside a function, $1 is the function's own first argument, not the script's. Write args.sh to see it:

#!/usr/bin/env bash
# args.sh: what does a function see?
show() {
  echo "Function - how many: $#, first: $1"
}

echo "Script   - how many: $#, first: $1"
show "Road trip" "Coming home"
chmod +x args.sh
./args.sh Saturday
Script   - how many: 1, first: Saturday
Function - how many: 2, first: Road trip

A function sees only what its call hands it. When it needs the script's argument, pass it on: banner "$1", or all of them with "$@".

A script made of named jobs

Put it together. Replace mix.sh with this version, where play loops over every song it's handed with "$@", each title in one piece (For Every File: for Loops):

#!/usr/bin/env bash
# mix.sh: prints a road-trip mix, one part at a time.
# Usage: ./mix.sh DAY

divider() {
  echo "------------------------------"
}

banner() {
  divider
  echo "  $1"
  divider
}

play() {
  echo "$# songs:"
  for song in "$@"; do
    echo "  $song"
  done
}

banner "Road trip, $1"
play "Highway Tea" "Paper Lanterns" "Window Down"
banner "Coming home"
play "Gas Station Sunrise" "Home by Dark"
./mix.sh Saturday
------------------------------
  Road trip, Saturday
------------------------------
3 songs:
  Highway Tea
  Paper Lanterns
  Window Down
------------------------------
  Coming home
------------------------------
2 songs:
  Gas Station Sunrise
  Home by Dark

The last four lines read like a plan, and each detail lives in one place: make the dashes longer in divider, and both banners change. A few habits keep a script of functions readable:

  • Name a function after its job, in lowercase, with underscores between words: banner, play, show_song.
  • Check the name with type first, as in Run It from Anywhere: ~/bin on Your PATH: a function named test or ls would hide the real command.
  • You'll also meet the older spelling function banner { ... } in other people's scripts. It does the same job; this course writes banner() { ... }.

zsh at a Mac's prompt defines and calls functions the same way, and your scripts run in bash 5 by their first line.

Check yourself

2 questions to try on the lesson page.

So what

A function turns a few lines into a word of your own: write the job once, give it a name, and the rest of the script reads like a list of what it does.

Key terms

Cheat sheets

Related