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. Seeman [
.)
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 variablen
that will take the value of each word afterin
. It would have been the same to usefor 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:
Play with shell: https://www.learnshell.org
Learn shell scripts: https://www.shellscript.sh
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