Book Image

Mastering Linux Shell Scripting - Second Edition

By : Mokhtar Ebrahim, Andrew Mallett
5 (1)
Book Image

Mastering Linux Shell Scripting - Second Edition

5 (1)
By: Mokhtar Ebrahim, Andrew Mallett

Overview of this book

In this book, you’ll discover everything you need to know to master shell scripting and make informed choices about the elements you employ. Grab your favorite editor and start writing your best Bash scripts step by step. Get to grips with the fundamentals of creating and running a script in normal mode, and in debug mode. Learn about various conditional statements' code snippets, and realize the power of repetition and loops in your shell script. You will also learn to write complex shell scripts. This book will also deep dive into file system administration, directories, and system administration like networking, process management, user authentications, and package installation and regular expressions. Towards the end of the book, you will learn how to use Python as a BASH Scripting alternative. By the end of this book, you will know shell scripts at the snap of your fingers and will be able to automate and communicate with your system with keyboard expressions.
Table of Contents (17 chapters)

Creating and executing scripts

With our editors primed and ready, we can now move quickly to creating and executing our scripts. If you are reading this book with some prior experience, we will warn you that we are going to start with the basics, but we will also include looking at positional parameters; feel free to move on at your own pace.

Hello World!

As you know, it is almost obligatory to begin with a Hello World script and we will not disappoint as far as this is concerned. We will begin by creating a new script, $HOME/bin/hello1.sh. The contents of the file should read as in the following screenshot:

We hope that you haven't struggled with this too much; it is just three lines, after all. We encourage you to run through the examples as you read to really help you instill the information with good hands-on practice.

  • #!/bin/bash: Normally, this is always the first line of the script and is known as the shebang. The shebang starts with a comment, but the system still uses this line. A comment in a shell script has the # symbol. The shebang instructs the interpreter of the system to execute the script. We use bash for shell scripts, and we may use PHP or Perl for other scripts, as required. If we do not add this line, then the commands will be run within the current shell; it may cause issues if we run another shell.
  • echo "Hello World": The echo command will be picked up in a built-in shell and can be used to write a standard output, STDOUT; this defaults to the screen. The information to print is enclosed in double quotes; there will be more on quotes later.
  • exit 0: The exit command is a built-in shell, and is used to leave or exit the script. The exit code is supplied as an integer argument. A value of anything other than 0 will indicate some type of error in the script's execution.

Executing the script

With the script saved in our PATH environment, it still will not execute as a standalone script. We will have to assign and execute permissions for the file, as needed. For a simple test, we can run the file directly with bash. The following command shows you how to do this:

$ bash $HOME/bin/hello1.sh  

We should be rewarded with the Hello World text being displayed on our screens. This is not a long-term solution, as we need to have the script in the $HOME/bin directory, specifically, to make running the script easy from any location without typing the full path. We need to add in the execute permissions as shown in the following code:

$ chmod +x $HOME/bin/hello1.sh 

We should now be able to run the script simply, as shown in the following screenshot:

Checking the exit status

This script is simple, but we still need to know how to make use of the exit codes from scripts and other applications. The command-line list that we generated earlier, while creating the $HOME/bin directory, is a good example of how we can use the exit code:

$ command1 || command 2 

In the preceding example, command2 is executed only if command1 fails in some way. To be specific, command2 will run if command1 exits with a status code other than 0.

Similarly, in the following extract, we will only execute command2 if command1 succeeds and issues an exit code of 0:

$ command1 && command2  

To read the exit code from our script explicitly, we can view the $? variable, as shown in the following example:

$ hello1.sh
$ echo $?

The expected output is 0, as this is what we have added to the last line of the file and there is precious little else that can go wrong to cause the failure to reach that line.

Ensuring a unique name

We can now create and execute a simple script, but we need to consider the name a little. In this case, hello1.sh will be good enough and is unlikely to clash with anything else on the system. We should avoid using names that may clash with existing aliases, functions, keywords, and building commands, as well as avoiding names of programs already in use.

Adding the sh suffix to the file does not guarantee the name will be unique, but, in Linux, where we do not use file extensions, the suffix is part of the filename. This helps you to provide a unique identity to your script. Additionally, the suffix is used by the editor to help you identify the file for syntax highlighting. If you recall, we specifically added the syntax highlighting file sh.nanorc to the nano text editor. Each of these files is specific to a suffix and subsequent language.

Referring back to the command hierarchy within this chapter, we can use a type to determine the location and type of file hello.sh:

$ type hello1.sh  #To determine the type and path
$ type -a hello1.sh  #To print all commands found if the name is NOT unique
$ type -t hello1.sh ~To print the simple type of the command  

These commands and output can be seen in the following screenshot:

Hello Dolly!

It is possible that we might need a little more substance in the script than a simple fixed message. Static message content does have its place, but we can make this script much more useful by building in some flexibility.

In this chapter, we will look at the positional parameters or arguments that we can supply to the script and in the next chapter, we will see how we can make the script interactive and also prompt the user for input at runtime.

Running the script with arguments

We can run the script with arguments; after all, it's a free world and Linux promotes your freedom to do what you want to do with the code. However, if the script does not make use of the arguments, then they will be silently ignored. The following command shows the script running with a single argument:

$ hello1.sh fred  

The script will still run and will not produce an error. The output will not change either and will print Hello World:

Argument Identifier

Description

$0

The name of the script itself, which is often used in usage statements.

$1

A positional argument, which is the first argument passed to the script.

${10}

Where two or more digits are needed to represent the argument position. Brace brackets are used to delimit the variable name from any other content. Single value digits are expected.

$#

The argument count is especially useful when we need to set the amount of arguments needed for correct script execution.

$*

Refers to all arguments.

For the script to make use of the argument, we can change its content a little. Let's first copy the script, add in the execute permissions, and then edit the new hello2.sh:

$ cp $HOME/bin/hello1.sh $HOME/bin/hello2.sh
$ chmod +x $HOME/bin/hello2.sh  

We need to edit the hello2.sh file to make use of the argument as it is passed at the command line. The following screenshot shows the simplest use of command-line arguments, now allowing us to have a custom message:

Run the script now; we can provide an argument as shown in the following:

$ hello2.sh fred

The output should now say Hello fred. If we do not provide an argument, then the variable will be empty and will just print Hello. You can refer to the following screenshot to see the execution argument and output:

If we adjust the script to use $*, all the arguments will print. We will see Hello and then a list of all the supplied arguments. Edit the script and replace the echo line as follows:

echo "Hello $*"

This will execute the script with the following arguments:

$ hello2.sh fred wilma betty barney

And this will result in the output shown in the following screenshot:

If we want to print Hello <name>, with each name on a separate line, we will need to wait a little until we cover looping structures. A for loop is a good way to achieve this.

The importance of correct quotes

So far, we have used a simple double-quoting mechanism to encase the strings that we want to use with echo.

In the first script, it does not matter if we use single or double quotes. echo "Hello World" will be exactly the same as echo 'Hello World'.

However, this is not the case in the second script, so it is very important to understand the quoting mechanisms available in bash.

As we have seen, using double quotes in echo "Hello $1" will result in Hello fred or whatever the supplied value is. Whereas, if we use single quotes in echo 'Hello $1', the printed output on the screen will be Hello $1; that is, we see the variable name and not its value.

The idea of the quotes is to protect special characters, such as a space between the two words; both quotes protect the space from being misinterpreted. The space is normally read as a default field, separated by the shell. In other words, all characters are read by the shell as literals with no special meaning. This has the knock-on effect of the $ symbol printing its literal format rather than allowing bash to expand its value. The bash shell is prevented from expanding the variable's value as it is protected by the single quotes.

This is where the double quote comes to our rescue. The double quote will protect all the characters except the $, allowing bash to expand the stored value.

If we ever need to use a literal $ within the quoted string, along with variables that need to be expanded, we can use double quotes, but escape the desired $ with the backslash (\). For example, echo "$USER earns \$4" would print as Fred earns $4 if the current user were Fred.

Try the following examples at the command line using all quoting mechanisms. Feel free to up your hourly rate as required:

$ echo "$USER earns $4"
$ echo '$USER earns $4'
$ echo "$USER earns \$4"  

The output is shown in the following screenshot:

Printing the script name

The $0 variable represents the script name, and this is often used in usage statements. As we are not yet looking at conditional statements, we will get the script name printed above the displayed name.

Edit your script so that it reads like the following complete code block for $HOME/bin/hello2.sh:

#!/bin/bash 
echo "You are using $0" 
echo "Hello $*" 
exit 0 

The output from the command is shown in the following screenshot:

If we prefer not to print the path and only want the name of the script to show, we can use the basename command, which extracts the name from the path. Adjust the script so that the second line now reads as follows:

echo "You are using $(basename $0)"  

The $(....) syntax is used to evaluate the output of the inner command. We first run basename $0 and feed the result into an unnamed variable represented by the $.

The new output will appear as seen in the following screenshot:

It is possible to achieve the same results using back quotes; this is less easy to read, but we have mentioned this as you might need to understand and modify the scripts that have been written by others. The alternative to the $(....) syntax is shown in the following example:

echo "You are using 'basename $0'"  

Please note that the characters used are back quotes and NOT single quotes. On UK and US keyboards, these are found in the top-left corner next to the number 1 key.