Shell scripting

The shell isn’t just the command line.

It’s also a programming language but one focused mostly on running other programs.

Baby’s first bash script

#!/bin/bash

echo hello world

The first line is called a “shebang” line, short for “sharp bang”. It tells the OS how to run this file, namely by passing it to /bin/bash.

Because in bash # is a comment character that line doesn’t have any affect when bash runs the file. Bash also ignores blank lines. Then the echo line actually does something and then the program ends.

Make hello.sh

Create a file called hello.sh and type the code from the previous slide into it.

Running a script

The simplest way to run your script is:

bash hello.sh

Try it.

Before you do anything else

sudo apt install shellcheck

This is a fantastically good linter for bash.

You will write more robust scripts and also learn a lot about how bash actually works if you run it regularly.

Making a file executable

At the terminal run:

chmod +x hello.sh

This makes the file “excutable” which tells the OS that it should run it if it is invoked like a program.

./hello.sh

Add a variable

#!/bin/bash

echo hello $1

$1 is a built in variable that gets the first argument passed on the command line.

Run this like:

./hello.sh foo

Now run shellcheck

$ shellcheck hello.sh

In hello.sh line 4:
echo hello $1
           ^-- SC2086: Double quote to prevent globbing and word splitting.

Did you mean:
echo hello "$1"

For more information:
  https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...

Fix the issue

#!/bin/bash

echo "hello $1"

Now shellcheck hello.sh should say nothing.

No news is good news.

Slightly more prostyle

#!/usr/bin/env bash

echo "hello $1"

env is the same program we used before to dump out all the environment variables. Here we are using it to find and invoke bash.

This lets us find whatever version of bash the user has in their PATH.

set -euo pipefail

Another line that’s it’s a good idea to add at the top of you scipt.

Sets some shell options that are generally more robust for scripts.

-e

Makes it so if a command invoked by the script fails then the script itself exits immediately. (Unless the command invocation is part of a control construct in which case it’s success or failure acts as a boolean.)

-u

Makes the script fail if it tries to reference an variable that has not been assigned a value, i.e. is undefined.

For instance if you write a line like rm -rf ~/$directory in your script and forgot to set directory, without -u the script would delete your whole home directory because that would turn into rm -rf ~/.

-o pipefail

Causes a pipeline to fail if any program in the pipeline fails.

Combined with -e this will cause the script itself to exit if any part of a pipeline fails.

Put it all together

#!/usr/bin/env bash

set -euo pipefail

echo "hello $1"

if

#!/usr/bin/env bash

set -euo pipefail

day="$1"
file="$2"

if grep "$day" "$file"; then
    echo "Found $day in $file"
else
    echo "No $day found in $file"
fi

Invoking grep either succeeds (the pattern is found at least once in the file) or fails (the pattern is not found or the file does not exist.)

If it succeeds that effectively “true” and failure is “false”.

if

#!/usr/bin/env bash

set -euo pipefail

day="$1"
file="$2"

if grep "$day" "$file" > /dev/null; then
    echo "Found $day in $file"
else
    echo "No $day found in $file"
fi

Redirecting output to /dev/null throws it away.

for

#!/usr/bin/env bash

set -euo pipefail
shopt -s nullglob

for f in *.txt; do
   cp "$f" "$f.bakup"
done

Loop over the files matched by the glob *.txt and copy each one to a file with the same name plus the extension .backup.

Note the shopt -s nullglob.

[ and [[

#!/usr/bin/env bash

set -euo pipefail

if [[ -e config.txt ]]; then
   echo "Have a config file."
fi

[[ is a bash builtin but it’s based on a program called test which also is installed under the name [.

man test to learn about the things you can test.

help [[ to find out about [[ and the things you can do with it that you can’t do with [.

Looping back to one thing

Recall that when you run a script it can’t change the current shell’s state.

There is, however, another way to run a script that does change the current shell:

source somescript.sh

or

. somescript.sh

This has the same effect as if you typed the commands in the file into the shell.

See what you can make