Write a script

INPUT · Slides

Giving names with functions

01 / 08

When it gets long, divide it by name

The script you wrote last lesson has been getting longer.

set -eutrap ... EXIT[ $# -eq 0 ] && { echo ...; exit 2; }grep -c ... > ...COUNT=$(...)if [ ... ]; then ... fi

Even at seven lines it takes a moment to read "which part does what". At fifty, more so.

So you give a lump of procedure a name.

greet() {  echo "hello $1"}greet tarogreet hanako
hello tarohello hanako

This is a function.

It is worth two things.

1. the same procedure, called again and again2. it has a name, so it reads plainly

The second is bigger than you might think.

print_usage         you know at a glancegrep -c ... | awk ...   you have to read it

The name is the explanation. It carries without a comment.

02 / 08

Writing them and calling them

The shape is this.

name() {  the doing}

To call, just the name.

name

You do not write the brackets. That is where it differs from other languages.

name()      when defining (write the brackets)name        when calling (no brackets)

In other languages you call name(). Not in the shell. Here is why.

in the shell, a function is treated just like a command
ls a.txt        calling a commandname a.txt      calling a function

They look the same. So the user need not care which it is.

Check and you will see.

~ $ type greetgreet is a function~ $ type lsls is /bin/ls

The type of chapter 10. Then you saw three: alias, built-in, file. A function is one of the family too.

There is one important rule.

> Define a function before you call it

greet            <- it does not know it yetgreet() { ... }

That does not work, because it is read from the top. So you put your functions together near the top of the script.

03 / 08

$1 switches over

This is the most important part of the lesson.

#!/bin/shf() {  echo "the function's 1: $1"}f inner
~ $ ./a.sh outerthe function's 1: inner

inner came out, not outer.

./a.sh outer     the script's $1 is outerf inner          the $1 inside the function is inner

Inside a function it switches to what was handed to the function.

$# and $@ are the same.

#!/bin/shf() { echo "inside=$#"; }f a b cecho "outside=$#"
~ $ ./g.sh x yinside=3outside=2

Counted separately.

A useful property, because a function can then be written like a little script.

check() {  [ -f "$1" ] || return 1  grep -c . "$1"}check red.txtcheck blue.txt

The $1 writing you learned in lesson 2, usable as it is.

One caution. When you want the script's arguments inside a function, hand them over.

f "$1"           hand the script's $1 to the functionf "$@"           hand over all of them

They are not visible automatically.

04 / 08

Hand back success with return

A function can hand back an exit status too.

check() {  [ -f "$1" ] || return 1  grep -c . "$1"}
~ $ check there.txt; echo $?30~ $ check none.txt; echo $?1

You handed back a failure with return 1. Like the exit 1 of last lesson.

But the difference is large.

return 1     only the function ends (the script carries on)exit 1       the whole script ends

An important distinction.

for F in *.txt; do  check "$F" || echo "$F is no good"done

One failure and you can still move on to the next file. With exit it would all end there.

So the manner is this.

inside a function   use returnthe main flow       use exit

And functions can be joined with && too.

check red.txt && echo "ok"check none.txt || echo "ng"

Handled just like a command. Last lesson's "a tool that hands back success can be joined" works for functions as it stands.

With no return written, the last line's success comes back. The same rule as exit.

05 / 08

Variables are shared

Change a variable inside a function and it takes effect outside as well.

#!/bin/shV=outerchange() { V=inner; }changeecho $V
inner

The outer V changed. People used to other languages are surprised here.

ordinary languages   a variable in a function is the function's ownthe shell            it is all joined together

This is handy sometimes, but it is also a source of accidents.

f() { I=0; while ... done; }     f uses I insidefor I in 1 2 3; do f; done       I is used outside too!

It breaks without your noticing.

To keep them apart, use local.

f() {  local X=inner  echo $X}

A variable marked local is the function's own.

It is worth settling on a manner.

> Put local on the variables you use inside a function

check() {  local F="$1"  local COUNT  COUNT=$(grep -c . "$F")  echo "$COUNT"}

That way they never clash with the outside.

One note: local is not settled by POSIX. But it works in ash, bash and zsh alike, so in practice you may use it.

06 / 08

Carry it home as output

There are two ways to get a value out of a function.

1. Hand it back as output

howmany() { echo 42; }N=$(howmany)echo "got=$N"
got=42

You take it with the $(...) of chapter 10.

2. Put it in a variable

howmany() { RESULT=42; }howmanyecho "got=$RESULT"

Variables are shared, so this works too.

Which to use? Output is tidier.

as output       it works the same wherever you call it fromin a variable   it breaks when names clash

And handed back as output, it can flow into a pipe.

howmany | wc -c

The same shape as a command.

One thing to mind.

check() {  echo "checking..."           <- that is output too!  grep -c . "$1"}N=$(check red.txt)

Both lines go into N. So notices along the way go to standard error.

  echo "checking..." >&2

The >&2 of chapter 4.

1 (standard output)   the road for handing back results2 (standard error)    the road for showing a person

Why the roads were divided becomes clear here.

07 / 08

Choosing between this and an alias

You did aliases in chapter 10. Similar to functions.

alias ll='ls -l'll() { ls -l "$@"; }

Either is called with ll. Let us set out the differences.

AliasFunction
How longone line onlyany number
Argumentsonly tacked on the endput anywhere with $1
Conditions, repetitioncannotcan
Inside a scriptoften has no effectworks

How arguments are handled is the biggest difference.

alias mkcd='mkdir -p'        you cannot say wheremkcd() {  mkdir -p "$1" && cd "$1"    used twice!}

An alias cannot use $1 twice, because it is only tacked on the end.

A guide to choosing.

a short renaming              ->  aliasyou want to use arguments     ->  a functionyou need conditions or loops  ->  a function

In chapter 10 you were told "wanting to go deeper is the sign to make it a script". A function is the middle ground.

alias      a one-line renamingfunction   a lump of procedure you can keep in .profilescript     an independent tool you can hand to someone

Three steps. Put a function in ~/.profile and it becomes your own tool, ready every time you open a terminal.

08 / 08

Now have a go (the close of chapter 11)

Here are the shapes for this lesson.

name() { ... }       define (before you call)name arg             call (no brackets)$1 $# $@             what was handed to the functionreturn 1             hand back a failure (only the function ends)local X=...          a variable for inside onlyN=$(name)            take the outputecho ... >&2         notices to standard error

And let us look back on chapter 11. Seven lessons brought you here.

1  #! and the mark to run     put a procedure in a file2  arguments ($1)             choose what to work on3  if                         branch on a condition4  for                        to a great many5  while read                 lines, one at a time6  exit status                pass success about7  functions                  divide by name

All three elements of a program are there.

run in order      lesson 1branch            lesson 3repeat            lessons 4 and 5

There is hardly anything you cannot write now. The rest is combining, and reading well.

In chapter 12 you make your tools run while you are not watching.

cron      run at a settled timelogger    keep a recordip / ping look at the networkhttpd     serve your own page

The tools you built go off and work without your hand on them. Something to look forward to. Let us do the last one.