In the last chapter, we learned a few more useful commands in Unix and the RPi OS. These commands and tools we learned are extremely useful in writing shell scripts.
Continuing the theme of Unix and RPi OS commands of the last chapter, we will get started with shell scripting in this chapter and continue it to the next chapter. The following is the list of topics we will learn in this chapter:
After this chapter, we will be comfortable writing shell scripts on all Unix-like operating systems.
Unix File Permissions
Let us
understand this concept by example. Run the following command in the lxterminal:
The output is as follows:
pi@raspberrypi:~ $ ls -l
total 76
drwxr-xr-x 2 pi pi 4096 Aug 20 16:10 Bookshelf
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Desktop
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Documents
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Downloads
drwxr-xr-x 3 pi pi 4096 Aug 28 08:40 gnomeforpi
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Music
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Pictures
-rwxr-xr-x 1 pi pi 7980 Aug 29 17:36 prog00
-rw-r--r-- 1 pi pi 76 Aug 29 17:36 prog00.c
-rw-r--r-- 1 pi pi 22 Aug 29 18:38 prog00.py
-rwxr-xr-x 1 pi pi 8740 Aug 29 17:49 prog01
-rw-r--r-- 1 pi pi 93 Aug 29 17:48 prog01.cpp
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Public
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Templates
-rw-r--r-- 1 pi pi 3 Sep 3 16:06 test1.sh
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Videos
This is the Unix
long list format. We have already seen this. Let us understand the meaning of all the terms in the output. Let us see the following two lines:
drwxr-xr-x 2 pi pi 4096 Aug 20 16:40 Pictures
-rwxr-xr-x 1 pi pi 7980 Aug 29 17:36 prog00
As we can see, there are nine columns in the output. The first column (drwxr-xr-x and -rwxr-xr-x) tells us about the file type and permissions. The first character is d if it is a directory and – if it is a file. The rest of the nine characters tell us about the permissions. We will see them in detail soon.
The second column has a number. It shows the number of links. The third and fourth columns are the owner and group names (pi and pi in this case). The fifth column shows the size in bytes. For directories, it is always 4 k. If you are interested, you can read more about it at https://askubuntu.com/questions/186813/why-does-every-directory-have-a-size-4096-bytes-4-k.
The next three columns are the last modification time. The last column shows us the name of the directory or file.
Let us discuss permissions in detail. You must have noticed the first column has a string like
drwxr-xr-x
. As we discussed, the first character denotes the file type. The next nine characters can be divided into three groups of three characters each. They are permissions to the user who created the file/directory, user’s group, and others, respectively.
If we encounter the
– symbol anywhere, it means that permission is not granted. So the string
-rwxr-xr-x means that it is a file. The creator/owner of the file has all (read, write, and execute) permissions. The owner’s own group has read and execute permissions. And others have read and execute permissions. We can manually alter permissions. We usually use numerical representation of the strings for the permissions:
r means 4.
w means 2.
x means 1.
So, when we need to set permissions for a file/directory, we compute the sum for the owner, group, and others, respectively. For example, if I want to enable all permissions for all, then it will be rwxrwxrwx
. The numerical representation will be (4+2+1 4+2+1 4+2+1) which can be written as 777. For reasons of security, we rarely use this. The most common permission is rwxr--r--. It can be represented as (4+2+1 4+0+0 4+0+0) and written as 744. Another common permission format is rwxr-xr-x. It can be represented as (4+2+1 4+0+1 4+0+1) and written as 755. We will soon see how to grant these permissions on files and directories. We needed to understand this concept as it is very important to know it for executing programs on Unix-like operating systems.
Command: nohup
nohup
means
No Hangup. We use this command on the command prompt in cases where we do not want the program invoked to be terminated if we close the command prompt. We usually use it along with the
& operator. To demonstrate this, we need to use the desktop of the RPi OS (either directly or remotely through VNC). Open the
lxterminal. And then run the following command:
It will open the IDLE (Integrated Development and Learning Environment) editor (it is an IDE for Python 3; we will see that in the later part of the book). Now if we close the lxterminal window, it will not close the IDLE editor. However, if we invoke the IDLE with the command idle and nothing else, the IDLE will be closed if we close the lxterminal window used to invoke it.
Beginning Shell Scripting
Run the following command in the
lxterminal:
pi@raspberrypi:~ $ echo "Hello World!"
Hello World!
As we can see, it immediately prints the output. We can create a script that has a collection of these statements. The statements in the script are fed to the shell interpreter and executed one by one. Such a script is known as
shell script. Let us create one. Open any text editor and paste the command we executed there and save the file with the name
prog00.sh. Generally, we use
.sh as an extension for the shell script files. It is not necessary to have an extension. It just helps us to identify that these are shell scripts. We can easily list all the shell scripts in a directory with the following command:
Once you create the file for the shell script, we can run it as follows:
Add the command
date after the first line and run it again:
pi@raspberrypi:~ $ sh prog00.sh
Hello World!
Fri Sep 4 10:37:50 IST 2020
This is how we can write and execute simple shell scripts.
There is another way we can run the shell scripts. We need to change the permissions of the shell script with the following command:
We have already discussed the meaning of the permissions represented by 755. Now we can directly run the script using
./ as follows:
It will execute the program and print the output. We can explicitly specify the interpreter with something known as
shebang or
sha-bang
. Just add
#!/bin/bash as the first line of the script shown in Listing
6-1.
#!/bin/bash
echo "Hello World!"
date
This way the script uses the Bash shell to run when invoked directly.
User Input
We can accept input from a user in our shell script. In Listing
6-2, we are using the statement
read to read the
user input. We are reading the input into a variable and displaying its value. This is how we can read user input.
#!/bin/bash
echo 'Who am I talking to?'
read name
echo 'Nice to meet you' $name
Expressions in the Shell
Just like any programming language, we can write
expressions in the shell. The following shell script (Listing
6-3) shows various ways to write mathematical expressions in the shell.
In this script, the first, second, and fourth
expressions show us an assignment operation, and the third expression shows us an increment operation. We will frequently use expressions to assign values to variables in the shell. We can also use the
expr statement to evaluate the arithmetic operations as shown in Listing
6-4.
The first and the third expr statements are arithmetic, and the second one is a string as the operand is enclosed by double quotes. The fourth statement is an assignment statement. Run the script and see the output.
Finally, Listing
6-5 shows a way to write expressions without
let or
expr.
Run the script to see the output.
If Statement
We can have conditional statements using
if. They use
comparison operators. The following are the comparison operators in Bash:
Listing
6-6 shows usage of the
-eq operator with an
if statement
.
echo 'Please enter an integer: '
read a
if [ $a -gt 100 ]
then
echo "The number is greater than 100."
fi
Run the script and see the output.
We can even have a nested if (Listing
6-7).
echo 'Please enter an integer:'
if [ $a -gt 100 ]
then
echo 'It is greater than 100.'
if (( $a % 2 == 0 ))
then
echo 'It is an even number.'
fi
fi
Run the script and see the output.
We can write an if-else block (Listing
6-8).
echo 'Please enter an integer:'
if [ $a -gt 50 ]
then
echo 'The number is greater than 50.'
else
echo 'The number is less than or equal to 50.'
fi
Run it to see the output. We can even write the if-elif-else statement (Listing
6-9).
echo 'Please enter an integer:'
read a
if [ $a -gt 100 ]
then
echo 'The number is greater than 100.'
elif [ $a -eq 100 ]
then
echo 'The number is equal to 100.'
else
echo 'The number is less than 100.'
fi
Switch Case
We can have a
switch case construct (like C, C++, and Java) in the shell. Listing
6-10 is a sample of such construct.
echo 'Please input an integer:'
case $a in
10)
echo Ten
;;
20)
echo Twenty
;;
100)
echo Hundred
;;
*)
echo 'Default Case'
esac
Run the shell script to see the output.
Length of a Shell Variable
We can use the
# symbol
to compute the length of a variable (Listing
6-11).
a='Hello World!'
echo ${#a}
Execute the script to know the length of variables.
Command-Line Arguments
Command Line Arguments are the arguments passed to any program (in this context, a shell script) at the time of invoking that script from the command line or any other script. Most of the modern programming (C, C++, and Java) and scripting (shell, Perl, Python) languages have provision for handling command-line arguments.
In a shell script, we can use
$# and
$@ to handle the
command-line arguments as shown in Listing
6-12.
echo 'Total number of arguments:' $#
echo 'All argument values:' $@
echo 'Name of the script:' $0
echo 'First Argument ->' $1
echo 'Second Argument ->' $2
Run it as follows:
pi@raspberrypi:~ $ bash prog11.sh 1 "Hello World" 3.14 ASH
Total number of arguments: 4
All argument values: 1 Hello World 3.14 ASH
Name of the script: prog11.sh
First Argument -> 1
Second Argument -> Hello World
Function
We can even write functions in shell scripts.
Functions are reusable blocks of code that are called frequently in a program. If you have a block of code that needs to be used frequently, it makes sense to write a function around it. Listing
6-13 shows a demonstration of a simple function.
#!/bin/bash
print_message ()
{
echo 'Hello from function'
}
In the script, print_message() is a function, and we are calling it exactly one time as shown. We are first defining its body and then calling it (when we call, the brackets are not used). We can even have arguments in functions, and they work exactly the same as command-line arguments. You can read more about it at https://bash.cyberciti.biz/guide/Pass_arguments_into_a_function.
Loops in the Shell
We can write a loop using the
until statement and comparison
operator as shown in Listing
6-14.
until [ $counter -gt 5 ]
do
echo 'Counter:' $counter
((counter++))
done
Run the script and it shows the following output:
pi@raspberrypi:~ $ bash prog13.sh
Counter: 0
Counter: 1
Counter: 2
Counter: 3
Counter: 4
Counter: 5
We can even have
for loops as shown in Listing
6-15.
for i in 1 2 3 4 5
do
echo 'Looping ... number' $i
done
The output is as follows:
pi@raspberrypi:~ $ bash prog14.sh
Looping ... number 1
Looping ... number 2
Looping ... number 3
Looping ... number 4
Looping ... number 5
We can compute a factorial with a
for loop as shown in Listing
6-16.
echo 'Enter a number'
read num
for((i=2;i<=num;i++))
{
fact=$((fact * i))
}
We can write the same
program with a while loop as shown in Listing
6-17.
#!/bin/bash
echo 'Enter a number:'
read num
while [ $num -gt 1 ]
do
fact=$((fact * num))
num=$((num - 1))
done
Run all these programs to see the loops in action.
Comparing Strings
We can
compare strings in the shell with string comparison operators. Listing
6-18 shows string comparison in action.
if [ $a = $b ]
then
echo "$a = $b : a is equal to b"
else
echo "$a = $b : a is not equal to b"
fi
if [ $a != $b ]
then
echo "$a = $b : a is not equal to b"
else
echo "$a = $b : a is equal to b"
fi
if [ -z $a ]
then
echo "-z $a : length of a is zero"
else
echo "-z $a : length of a is not zero"
fi
if [ -n $a ]
then
echo "-n $a : length of a is not zero"
else
echo "-n $a : length of a is zero"
fi
if [ $a ]
then
echo "$a : string is not empty"
else
echo "$a : string is empty"
fi
We can change the values of strings stored in variables a and b to experiment with different outcomes.
File Operations
We can check
files for various conditions with the following operators:
-r checks if the file is readable.
-w checks if the file is writeable.
-x checks if the file is executable.
-f checks if the file is an ordinary file.
-d checks if the file is a directory.
-s checks if the file size is zero.
-e checks if the file exists.
Listing
6-19 shows the usage of all these operators.
if [ -r $file ]
then
echo 'File has read access.'
else
echo 'File does read access.'
fi
if [ -w $file ]
then
echo 'File has write permission.'
else
echo 'File does not have write permission.'
fi
if [ -x $file ]
then
echo 'File has execute permission.'
else
echo 'File does not have execute permission.'
fi
if [ -f $file ]
then
echo 'File is an ordinary file.'
else
echo 'File is not an ordinary file.'
fi
if [ -d $file ]
then
echo 'File is a directory.'
else
echo 'File is not a directory.'
fi
if [ -s $file ]
then
echo 'File size is not zero.'
else
echo 'File size is zero.'
fi
if [ -e $file ]
then
echo 'File exists.'
else
echo 'File does not exist.'
fi
Run the script to see the output.
Summary
In this chapter, we have gotten started with shell scripts in the shell. As discussed earlier, shell scripts can be very useful tools for programmers who are tasked to complete various activities. We have explored shell scripts in quite detail. However, there is more to shell scripting than we have covered. If you are entrusted with tasks related to Unix-like systems, then you can use shell scripts at your work to get the desired results. Mastery in scripting requires a lot of practice in real-life tasks.
In the next chapter, we will explore I/O redirection techniques and learn about a useful utility known as crontab. We will demonstrate that with a real-life project.