Write a script

INPUT · Slides

Exit status

01 / 08

What was coming back where you could not see

Since chapter 4 you have typed this many times.

~ $ echo $?0

$? is the number the command just before handed back.

0            it went wellanything else  it did not

And you have used && and ||.

mkdir box && cd box            if it went well, the nextcd box || echo "no good"       if it did not

It came up in lesson 3 with if, too.

if grep -q red f; then ...     branching on grep's success

All one and the same mechanism. Today you put it in order.

> Every command hands back one number as it ends

This number is called the exit status. You do not usually see it, but it is always flowing.

Three things today.

1. read the number that comes back ($?)2. hand one back from your own tools (exit)3. writing that stands up to failure (set -e and so on)

In chapter 12 you make things run while you are not watching. For that you need tools that can notice their own failures. Today is the groundwork.

02 / 08

The rule that 0 means success

You may have thought it odd. Usually 1 means yes.

0           success1 or more   failure

It is the other way round because there are many reasons for failing.

0    success (there is only one way)1    an ordinary failure2    used wrongly3    the file is not there...  each tool may decide

Success is one thing, but failures vary. So 0 is given to success and all the rest are left for failure. A neat decision.

There are two commands you can try.

~ $ true; echo $?0~ $ false; echo $?1

true is the command that only ever succeeds, false the one that only ever fails. They do nothing, but they are used for practice and for building.

One caution. $? only remembers the one just before.

~ $ false~ $ echo $?1~ $ echo $?0        <- 0, because that echo succeeded!

Read it twice and it is gone. So if you are going to use it, put it in a variable.

RC=$?

Now you can use it as often as you like.

03 / 08

What && and || really are

These two are only looking at the exit status.

A && B      if A is 0, do BA || B      if A is not 0, do B

&& joins on success, || joins on failure.

~ $ true && echo goodgood~ $ false || echo badbad

That explains what you have been typing all along.

mkdir box && cd box  | if mkdir hands back 0, cd  | if it failed, do not cd (important!)

Not going on after a failure is the value of it. Joined with ;, this happens.

mkdir box; cd box  | it tries to cd even when mkdir failed

Here are the three joins together.

JoinMeaning
;do the next, never mind success
&&on success, do the next
`\\`on failure, do the next

You can combine them.

cp a b && echo "copied" || echo "failed"

This if it went well, that if it did not. No if needed.

Though it gets hard to read when long, so with three or more, use an if.

04 / 08

A pipe reports only the last

A place to come unstuck if you do not know it.

~ $ false | true; echo $?0~ $ true | false; echo $?1

Only the last command's success comes back. A failure partway is invisible.

There are times this hurts.

cat none.txt | grep red > result.txt  | cat has failed (there is no such file)  | but grep ran, so 0 comes backyou think it went well

A failure you cannot notice. The weakness of pipes from chapter 4.

Three guards.

1. Check beforehand

[ -f none.txt ] || exit 1cat none.txt | grep red

The "guard at the door" of lesson 3. The plainest.

2. Do not use a pipe

grep red none.txt > result.txt

The cat was not needed at all. The "useless cat" of chapter 4. Fewer pipes, less of this problem.

3. Check what came out

[ -s result.txt ] || echo "the result is empty"

Look at whether the result is empty. The -s of lesson 3.

A problem you can avoid if you know it. Remember: "a pipe's $? is only the last".

05 / 08

Handing one back from your own tools

Your own scripts must hand one back properly too.

#!/bin/shexit 3
~ $ ./a.sh; echo $?3

The number you wrote in exit comes straight back. And if you write none?

#!/bin/shecho x
~ $ ./b.sh; echo $?0

The success of the last line comes back. The echo succeeded, so 0.

A guide to the numbers.

0     it went well1     an ordinary failure2     used wrongly

0 and 1 alone are often enough. You only split it finer when the caller wants to tell them apart.

One pitfall: you cannot use large numbers.

~ $ sh -c 'exit 300'; echo $?44

300 became 44! What you can use is 0 to 255.

300 - 256 = 44

What comes back is the remainder after dividing by 256. So use small numbers to be safe.

And it joins up with chapter 8.

128 + the signal number = the number when it was killed
Ctrl-C (INT is 2)     ->  130kill -9 (KILL is 9)   ->  137

See 130 or 137 and you know it was stopped. So keeping your own numbers small avoids the mixing.

06 / 08

Stop on failure (set -e)

A script carries straight on even after a failure.

#!/bin/shcd /nowhererm -r ./*          <- it runs in the place you were!

Frightening. The cd failed and yet the rm runs.

Two ways to prevent it.

1. Join with &&

cd /nowhere && rm -r ./*

2. Write set -e

#!/bin/shset -ecd /nowhererm -r ./*          <- it never gets here

set -e is the instruction "if any of it fails, stop there".

~ $ ./d.sh; echo rc=$?rc=1

The echo after the false did not run.

There are three of the family.

WrittenWhat it does
set -estop on a failure
set -ustop on using a variable that is not there
set -xshow the lines being typed (debugging)

set -u is strong too.

#!/bin/shset -urm -r "$TARGET"/*     <- if TARGET is empty, it stops here

It prevents the "forget the argument and it becomes rm -r /*" from lesson 2.

So it has become the manner to begin like this.

#!/bin/shset -eu

Think of it as the first line of a dangerous script.

07 / 08

Showing (set -x) and tidying up (trap)

When a script does not do what you thought, use this.

#!/bin/shset -xecho visible
+ echo visiblevisible

The lines being typed are shown with a +. You see what the variables expanded to as well, so the cause turns up at once.

set -x       show from hereset +x       stop here

Start with - and stop with +. Bracket only the odd part.

One more tool, for tidying up.

#!/bin/shtrap "echo tidying" EXITecho main
maintidying

trap is the instruction "be sure to do this as you end".

What is it for? Clearing away what you made partway.

#!/bin/shtrap "rm -f /tmp/work.$$" EXITecho x > /tmp/work.$$... some work ...

Fail partway and no rubbish is left. $$ is your own process number (chapter 8).

You can name the signals of chapter 8, too.

trap "echo stopped" INT     when Ctrl-C happenstrap "echo tidying" EXIT    however it ends

EXIT is the handy one, because it is passed on success and failure alike.

The knowledge of chapter 8 has become a tool for writing scripts.

08 / 08

Now have a go

Here are the shapes for this lesson.

echo $?              look at the success just beforeRC=$?                put it awaytrue / false         only succeed / only failA && B               if it succeededA || B               if it failedexit 1               end as a failureset -e               stop on a failureset -u               stop on a missing variableset -x               show the lines typedtrap "..." EXIT      always do it as you end

Three pitfalls to remember.

1. $? is only the one just before (read twice and it is gone)2. a pipe's $? is the last command's3. exit takes 0-255 (300 becomes 44)

And today's conclusion.

> A tool that hands back success can be joined to other tools

A script that properly returns exit 1 can be used like this.

./checkup.sh && echo "nothing wrong"./checkup.sh || echo "trouble!"

Your tool can join the line of &&. One of the family with grep and mkdir.

This matters a great deal in chapter 12.

running while you are not watching  | you need to know afterwards whether it failed  | leave it in the exit status

One lesson left in chapter 11: functions, giving a procedure a name. Let us type.