Shell Script

One of the coolest part of UNIX command is that we can easily script series of commands for automating tasks or simply to avoid retyping a series of commands.

Once again, this part of the tutorial is not mandatory to do the projects. But having at least a basic understanding of shell scripting is advised. If you do not plan on using the shell for other tasks, you can go over this part quickly, not focusing on the details.

Create a Simple Script

First, save the following code into a file named “hello.sh”:

#!/bin/bash
echo "hello world"

The first line of the script indicate which program will execute the script. In this case, it is “bash”.

Next, we need to give the script permission to execute. This is done with the following command:

$ chmod u+x hello.sh

Note

The u stands for “user”, u+x means “add the execute permission to the user”. It is more secure than simply using +x, which would give the permission to everyone.

Finally, to execute the script:

$ ./hello.sh
hello world

If you modify the script as follows, output will also be changed:

#!/bin/bash
echo "hello world"
echo "good morning"
echo "good afternoon"
echo "good evening"

Let’s execute it again. And that case, you will have:

$ ./hello.sh
hello world
good morning
good afternoon
good evening

Variables

Variables in shell scripts are defined by the following syntax:

$ n=5
$ echo $n
5
$ h="Hi you"
$ echo "The equivalent for $h in french is Salut toi"
The equivalent for Hi you in french is Salut toi

Important

Do not put spaces around the equal sign when defining a variable.

These are local variables. You have already seen some environment variables (set by the system) like HOME, PATH, etc.

The shell also has its own shell variables. For example, RANDOM is a variable that contains a random number.

$ echo $((RANDOM % 5)) > rands.txt
$ echo $((RANDOM % 5)) >> rands.txt
$ echo $((RANDOM % 5)) >> rands.txt
$ echo $((RANDOM % 5)) >> rands.txt
$ echo $((RANDOM % 5)) >> rands.txt
$ cat rands.txt
0
1
1
0
4

Note

  • The > operator is used to place the standard output of a command into a file (replacing all of its content).

  • The >> operator is used to append the standard output of a command to a file.

  • The $((...)) syntax is used to perform arithmetic operations.

Mathematical operations

To perform mathematical operations, you can either do it directly in the shell:

$ echo $((5 * 10))
50
$ n=5
$ echo $((n * 10))
50
$ echo $(((n * 10) % 5))
0

Or use some external tools like bc:

$ echo "5 * 10" | bc
50

The Exit Code

Each command or group of commands returns an exit code. The ? variable is a shell variable that contains the exit code of the last command:

  • If the command was successful, the exit code is 0.

  • If an error occurred, the exit code is different from 0.

Note

Each program decides itself which exit code to return, therefore the meaning of “an error occurred” is not always the same. For example, if grep did not find the string you were looking for, it will return 1, even though it is not really an error.

$ echo "Hello INFO0940"
Hello INFO0940
$ echo $?
0
$ cp /etc /etc
cp: /etc is a directory (not copied).
$ echo $?
1

Conditions

It is possible to use conditions in shell scripts. Let’s see some example and explain the new notions for each.

Example 1:

#!/bin/sh
file="$1"
if grep "hello" $file --quiet
then
    echo "hello is present !"
fi

You can launch this script with the following command:

$ ./script.sh ANY_FILE_YOU_WANT
  • $1 is the first argument given to the script. ($0 is the string corresponding to the script/command itself.)

  • --quiet is an option for grep which will not display the result of the search.

  • if will check the return code of the given command. If it is 0, the condition is true. In this case, grep will only return 0 if the string is present in the file.

Example 2:

#!/bin/sh
grep "hello" file.txt > /dev/null
if [ $? -eq 0 ]
then
    echo "hello is present !"
fi
  • /dev/null is a special file that discards all data written to it.

  • [ is actually not part of the shell syntax, but a command that returns 0 if the condition is true.

Example 3:

#!/bin/sh
if [ $1 -lt 10 ]
then
    echo "$1 is smaller than 10"
else
    echo "$1 … thats too much"
fi
  • -lt is a comparison operator which means “less than”. (it is an argument of the [ command. See man [.)

Note

[ is posix compliant (meaning it will work on any shell). If you prefer, you can use [[ which is part of the shell syntax. [[ is more intuitive and has some additional features, but is less portable.

Loops

Let’s now see how to use for and while loops.

Example 4:

#!/bin/sh
for n in $(seq 10)
do
    echo "$n"
done
  • seq 10 will generate a sequence of numbers from 1 to 10.

  • for will create a variable n that will take the value of each word after in. It would have been the same to use for n in 1 2 3 4 5 6 7 8 9 10

Example 5:

#!/bin/sh
for file in ./*
do
    stat $file
done
  • stat will display information about the given file.

Example 6:

#!/bin/sh
n=0
while [ $n -lt 10 ]
do
    echo "$n"
    n=$((n + 1))
done

Online Resources

If you want to learn more about shell scripting, here are some resources:

Exercise

If you want some practice, here is an exercise for you. (Otherwise, you can jump to the last section: Getting Kernel Information)

Let’s build a shell script which computes the total size of files in a folder specified by the first argument of the script. We only want to consider files (not directories).

We provide you some hints to help you.

Expected output:

$ ./filesize.sh DIRECTORY_NAME
Total size of files in the directory is 8 KB

Hint 1: Getting argument(s)

See hint
#!/bin/bash

echo "argument 1" $1
echo "argument 2" $2

Hint 2: Getting the size of a file in KB

See hint
$ du -s FILE_NAME

or

$ ls -la FILE_NAME

You will see that ls and du do not give the same size. The size given by ls is the exact size of the file, while the size given by du is the size of the file on disk (which is not the same).

Why is it not the same? You will understand this during the theoretical lecture on Mass Storage (lecture 8).

Hint 3: Using loop

See hint
#!/bin/bash

nb_words=0
for word in $(echo -n "I sorted my tea bags using an infusion sort algorithm")
do
  nb_words=$((nb_words+1))
done
echo "Number of words: $nb_words"

Hint 4: Printing only files in a directory

See hint
#!/bin/bash

files=`find . -maxdepth 1 -type f`
for f in ${files[@]}
do
  echo $f
done

or

#!/bin/bash

for f in $(find . -maxdepth 1 -type f)
do
  echo $f
done

Hint 5: Arithmetic computation

See hint
#!/bin/bash

one=1
two=2

ret=$(($one+$two))
echo "${one} + ${two} is ${ret}"

ret=$(($one*$two))
echo "${one} * ${two} is ${ret}"

Hint 6: showing N-th column

See hint
#!/bin/bash

cat /proc/cpuinfo
echo
echo "----- only showing 1st column -----"
echo
cat /proc/cpuinfo | awk '{ print $1 }'

Solution:

This is only one possible solution. There are many ways to solve this problem.

See answer
#!/bin/sh

total_size=0

for file in $(find $1 -maxdepth 1 -type f)
do
    file_size=$(ls -la $file | awk '{print $5}')   # or file_size=$(du $file | awk '{print $1}')
    total_size=$((total_size+file_size))
done

echo "Total size of files in the directory is $total_size KB"

Note

It is not necessary to use exit 0 at the end of the script, as the script will return the exit code of the last command executed (and if it was successful, it will return 0).

Note that to be rigorous, you should check if an argument is provided to the script and if it is indeed a directory. This can be done by placing the following code before your loop:

See answer
if [ $# -ne 1 ]
then
    echo "Please provide an argument."
    echo "Usage: ./filesize.sh DIRECTORY_NAME"
    exit 1
fi

if [ ! -d $1 ]
then
    echo "The first argument should be a directory."
    echo "Usage: ./filesize.sh DIRECTORY_NAME"
    exit 2
fi