Write a script

INPUT · Slides

Branching with `if`

01 / 08

Last lesson's one line, made readable

Last lesson you wrote this.

[ $# -eq 0 ] && echo "name one" && exit 1

It works, but as the things to do grow it gets hard to read. Three && in a row and you cannot tell where the condition ends.

The same thing can be written this way.

if [ $# -eq 0 ]; then  echo "name one"  exit 1fi

The condition and the doing have come apart. This is if.

You read it like this.

if    ifthen  thenfi    that is the end

fi is if written backwards, the mark for closing. The same job as the } of other languages.

And you can write the fork in the road.

if [ test ]; then  this wayelse  that wayfi

Which gives you the second of the three elements of a program.

1. run in order      <- done in lesson 12. branch on a test  <- today3. repeat            <- the next lesson

02 / 08

[ is the name of a command

This is the most important part of the lesson.

~ $ ls /usr/bin | grep -x "\["[~ $ which test/usr/bin/test

There really is a file named [! Not a symbol, but the name of a command.

Its real name is test. They are the same thing, so this works too.

~ $ test -f memo.txt; echo $?0~ $ [ -f memo.txt ]; echo $?0

Both returned 0, and that 0 is the answer.

0        the condition held (true)not 0    it did not (false)

And if is only looking at the exit status of the command after it.

if command; then ...     | run that command     | if it finished with 0, go to then

Which is to say if is not looking at "a conditional expression" but at whether a command succeeded.

So you can see why a space is needed after [.

[ -f memo.txt ]     yes, a command and its arguments[-f memo.txt]       no, there is no command by that name

The ] is an argument too. It looks like an odd rule, but it is only that a command named [ has decided "close me with a ]".

03 / 08

Marks for looking at files

Here are the marks you use most.

MarkTrue when
-fan ordinary file is there
-da container (directory) is there
-esomething is there (of any kind)
-sthe contents are not empty
-ryou can read it
-wyou can write it
-xyou can run it

Use them like this.

if [ -f "$1" ]; then  wc -l < "$1"else  echo "there is no $1"fi

Checking before you act. With this you can put out kind words instead of an error.

The same names as the marks of chapter 6 are lined up here.

-r -w -x     the same rwx as chapter 6

Quite so: it is checking whether you can do it. So the answer to the same file differs from person to person.

Look at the difference between -e and -f, too.

-e box     true even for a container-f box     false for a container (it is not an ordinary file)

For "is it there" alone, -e; for "is it a file I can read", -f. -f is the stricter.

04 / 08

Comparing what is inside

Letters and numbers are written differently.

Comparing letters

[ "$1" = "red" ]      the same[ "$1" != "red" ]     different[ -z "$1" ]           empty[ -n "$1" ]           not empty

Comparing numbers

[ $# -eq 0 ]     equal[ $# -ne 0 ]     not equal[ $# -gt 2 ]     greater than[ $# -lt 2 ]     less than[ $# -ge 2 ]     greater or equal[ $# -le 2 ]     less or equal

The names are short for English.

eq  equalne  not equalgt  greater thanlt  less than

Why are numbers special? Because you cannot use >.

[ $# > 2 ]      this writes out to a file named 2!

> was the redirection symbol. So comparing numbers uses other names. The knowledge of chapter 4 explains the reason for the rule.

And always wrap your variables.

[ "$1" = "red" ]    yes[ $1 = "red" ]      no, it breaks when it is empty

With $1 empty it becomes [ = "red" ] and the shape falls apart. Wrapped, it becomes [ "" = "red" ] and is properly false.

05 / 08

Three ways or more

When the road forks three ways or more, use elif.

if [ "$1" = "red" ]; then  echo itsredelif [ "$1" = "blue" ]; then  echo itsblueelse  echo dontknowfi

elif is short for "else if". It is checked from the top and only the first one that holds runs.

~ $ ./b.sh reditsred~ $ ./b.sh yellowdontknow

There is a knack to the order: put the common ones at the top.

if [ the usual ]; thenelif [ now and then ]; thenelse  everything elsefi

The reader reads from the top, so it is easier that way.

You need not have an else.

if [ $# -eq 0 ]; then  exit 1fi

When "do nothing if it does not apply" is what you want, this is enough. Not writing what need not be written is the tidy way.

You can combine conditions, too.

[ -f "$1" -a -s "$1" ]      both (and)[ -f "$1" -o -d "$1" ]      either (or)[ ! -f "$1" ]               the reverse (not)

When that is hard to read you may split with &&.

[ -f "$1" ] && [ -s "$1" ]

This is often said to read better.

06 / 08

A command can be the condition itself

I said [ was a command. In that case other commands can be conditions too.

They can.

if grep -q red "$1"; then  echo foundelse  echo notfoundfi

The -q of grep means "put nothing out, just return whether it was found".

found      ->  finishes with 0  ->  the then sidenot there  ->  finishes with 1  ->  the else side

An if with no [. Often it reads more plainly.

if [ "$(grep -c red "$1")" -gt 0 ]; then     roundaboutif grep -q red "$1"; then                    clean

All sorts of things can go there.

if mkdir box 2>/dev/null; then     if it could be madeif [ -f a ] && cp a b; then        if it could be copied

Here the most important idea of the chapter appears.

> Every command always returns whether it succeeded or failed

Every command you have typed has been returning 0 or not-0 behind your back. You do not usually see it, but echo $? shows it.

Handling that properly is the sixth lesson of this chapter. Today just take away that if is looking at it.

07 / 08

The manner of writing

Let us settle the shape for writing an if.

if [ -f "$1" ]; then  wc -l < "$1"fi

The places of the ; and the then are peculiar. Here is why.

if [ -f "$1" ]then  ...fi

This is how it really is. then belongs on its own line. But that runs long down the page, so joining with ; into one line became the usual thing.

; stands in for "split the line"

Indenting the inside by two is manner too.

if [ ... ]; then  indent herefi

It works without indenting, but you can see with your eye where the if ends. Other languages do the same.

When it is short you may pack it onto one line.

if [ -f "$1" ]; then wc -l < "$1"; fi

But at that point && is shorter.

[ -f "$1" ] && wc -l < "$1"

How to choose.

one thing to do        ->  && is enoughtwo or more            ->  write an ifthere is an else       ->  write an if

Choose whichever writes shorter, but go back to if when it stops reading well.

08 / 08

Now have a go

Here are the shapes for this lesson.

if [ -f "$1" ]; then ...; fi        if it is thereif [ -d "$1" ]; then ...; fi        if it is a containerif [ -z "$1" ]; then ...; fi        if it is emptyif [ "$1" = "red" ]; then ...; fi   if it is the sameif [ $# -eq 0 ]; then ...; fi       if the count is 0if grep -q x "$1"; then ...; fi     if it was found

You add lines like this.

echo 'if [ -f "$1" ]; then' >> a.shecho '  wc -l < "$1"' >> a.shecho 'else' >> a.shecho '  echo missing' >> a.shecho 'fi' >> a.sh

One line at a time with >>. Do not forget to wrap them all in singles.

Check with cat when you have written it, too. With a line missing you will be told a fi is short.

-sh: a.sh: line 5: syntax error: unexpected end of file

That means you forgot the fi. Forgetting to close is the commonest mistake there is.

And here is the conclusion for today.

> if branches on whether a command succeeded

[ is a command, grep is a command, and both are written the same way. Understand the mechanism and there is almost nothing to memorise. Let us make one.