Shell Scripting - Linux Administration (2016)

Linux Administration (2016)

Shell Scripting

A script is a command line program that contains a series of commands. The commands contained in the script are executed by an interpreter. In the case of shell scripts, the shell acts as the interpreter and executes the commands listed in the script one after the other.

Anything you can execute at the command line, you can put into a shell script. Shell scripts are great at automating tasks. If you find yourself running a series of commands to accomplish a given task, and know you will need to perform that task again in the future, you can—and probably should—create a shell script for that task.

Let's look at a simple shell script. The name of this script is script1.sh.

#!/bin/bash

echo "Scripting is fun!"

Before you try to execute the script, make sure that it is executable.

$ chmod 755 script1.sh

Here is what happens when you execute the script.

$ ./script1.sh

Scripting is fun!

$

The Shebang

You'll notice that the first line of the script starts with #! followed by the path to the bash shell program, /bin/bash. The number sign is very similar to the sharp sign used in music notation. Also, some people refer to the exclamation mark as a "bang." So, #! can be spoken as "sharp bang." The term Shebang is an inexact contraction of "sharp bang."

When a script's first line starts with a shebang, what follows is used as the interpreter for the commands listed in the script. Here are three examples of shell scripts, each using a different shell program as the interpreter.

#!/bin/csh

echo "This script uses csh as the interpreter."

#!/bin/ksh

echo "This script uses ksh as the interpreter."

#!/bin/zsh

echo "This script uses zsh as the interpreter."

When you execute a script that contains a shebang, what actually happens is that the interpreter is executed and the path used to call the script is passed as an argument to the interpreter. You can prove this by examinging the process table.

Let's start this script, sleepy.sh, in the background and look at the process table.

The contents of sleepy.sh:

#!/bin/bash

sleep 90

Let's execute it in the background and take a look at the processes.

$ ./sleepy.sh &

[1] 16796

$ ps -fp 16796

UID PID PPID C STIME TTY TIME CMD

jason 16796 16725 0 22:50 pts/0 00:00:00 /bin/bash ./sleepy.sh

$

You can see that what is actually running is /bin/bash ./sleepy.sh. Let's use a full path to the script.

$ /tmp/sleepy.sh &

[1] 16804

$ ps -fp 16804

UID PID PPID C STIME TTY TIME CMD

jason 16804 16725 0 22:51 pts/0 00:00:00 /bin/bash /tmp/sleepy.sh

$

Sure enough, /bin/bash /tmp/sleepy.sh is being executed. Also, you can see that /bin/bash is executing the sleep command, which is the first and only command command in the shell script.

$ ps -ef| grep 16804 | grep -v grep

jason 16804 16725 0 22:51 pts/0 00:00:00 /bin/bash /tmp/sleepy.sh

jason 16805 16804 0 22:51 pts/0 00:00:00 sleep 90

$ pstree –p 16804

sleepy.sh(16804)───sleep(16805)

$

If you do not supply a shebang and specify an interpreter on the first line of the script, the commands in the script will be executed using your current shell. Even though this can work just fine under many circumstances, it's best to be explicit and specify the exact interpreter to be used with the script. For example, there are features and syntax that work just fine with the bash shell that will not work with the csh shell.

Also, you don't have to use a shell as the interpreter for your scripts. Here is an example of a Python script named hi.py.

#!/usr/bin/python

print "This is a Python script."

Let's make it executable and run it.

$ chmod 755 hi.py

$ ./hi.py

This is a Python script.

$

For more information on python programming and scripting, see my book Python Programming for Beginners at http://www.linuxtrainingacademy.com/python-book.

Let's get back to shell scripting.

Variables

You can use variables in your shell scripts. Variables are simply storage locations that have a name. You can think of variables as name-value pairs. To assign a value to a variable, use the syntax VARIABLE_NAME="Value". Do not use spaces before or after the equals sign. Also, variables are case sensitive, and, by convention, variable names are in all uppercase.

#!/bin/bash

MY_SHELL="bash"

To use a variable, preceed the variable name with a dollar sign.

#!/bin/bash

MY_SHELL="bash"

echo "I like the $MY_SHELL shell."

You can also enclose the variable name in curly braces and preceed the opening brace with a dollar sign. Syntax: ${VARIABLE_NAME}.

#!/bin/bash

MY_SHELL="bash"

echo "I like the ${MY_SHELL} shell."

Here is the output of the script:

I like the bash shell.

The curly brace syntax is optional unless you need to immediately precede or follow the variable with additional data.

#!/bin/bash

MY_SHELL="bash"

echo "I am ${MY_SHELL}ing on my keyboard."

Output:

I am bashing on my keyboard.

If you do not encapsulate the variable name in curly braces, the shell will treat the additional text as part of the variable name. Since a variable with that name does not exist, nothing is put in it's place.

#!/bin/bash

MY_SHELL="bash"

echo "I am $MY_SHELLing on my keyboard."

Output:

I am on my keyboard.

You can also assign the output of a command to a variable. To do this, enclose the command in parentheses and precede it with a dollar sign.

#!/bin/bash

SERVER_NAME=$(hostname)

echo "You are running this script on ${SERVER_NAME}."

The output of the command hostname is stored in the variable SERVER_NAME. In this sample output, the server name is linuxsvr.

You are running this script on linuxsvr.

You can also enclose the command in back ticks. This is an older syntax that is being replaced by the $() syntax. However, you may see this in older scripts.

#!/bin/bash

SERVER_NAME=`hostname`

echo "You are running this script on ${SERVER_NAME}."

Valid variable names

Variable names can contain letters, digits, and underscores. They can start with letters or underscores, but cannot start with a digit. Here are examples of valid variable names.

FIRST3LETTERS="ABC"

FIRST_THREE_LETTERS="ABC"

firstThreeLetters="ABC"

Here are some examples of invalid variable names.

3LETTERS="ABC"

first-three-letters="ABC"

first@Three@Letters="ABC"

Tests

Scripts are designed to replace the need for a person to physically sit at a keyboard and type in a series of commands. What if you have a task you want to automate, but it requires different actions based on different circumstances? Since a person may not be around to make decisions when the script needs to run, we'll need to test for those conditions and have the script act accordingly.

To create a test, place a conditional expression between brackets. The syntax is: [ condition-to-test-for ]. You can test for several types of situations. For example, you can compare if strings are equal, if a number is greater than another one, or if a file exists. This test checks to see if/etc/passwd exists. If it does, it returns true. In other words, the command exits with a status of 0. If the file doesn't exist, it returns false. I.e., the command exits with a status of 1.

[ -e /etc/passwd ]

If you are using the bash shell, you can run the command help test to see the various types of tests you can perform. You can also read the man page for tests: man test. Here are of some of the more common tests you can perform.

File operators:

-d FILE True if file is a directory.

-e FILE True if file exists.

-f FILE True if file exists and is a regular file.

-r FILE True if file is readable by you.

-s FILE True if file exists and is not empty.

-w FILE True if the file is writable by you.

-x FILE True if the file is executable by you.

String operators:

-z STRING True if string is empty.

-n STRING True if string is not empty.

STRING True if string is not empty.

STRING1 = STRING2

True if the strings are equal.

STRING1 != STRING2

True if the strings are not equal.

Arithmetic operators:

arg1 –eq arg2 True if arg1 is equal to arg2.

arg1 –ne arg2 True if arg1 is not equal to arg2.

arg1 –lt arg2 True if arg1 is less than arg2.

arg1 –le arg2 True if arg1 is less than or equal to arg2.

arg1 –gt arg2 True if arg1 is greater than arg2.

arg1 –ge arg2 True if arg1 is greater than or equal to arg2.

The if Statement

Now that you know how to determine if a certain condition is true or not, you can combine that with the if statement to make decisions in your scripts.

The if statement starts with the word if and is then followed by a test. The following line contains the word then. Next is a series of commands that will be executed if the tested condition is true. Finally, the if statement ends with fi, which is if spelled backwards. Here is the syntax.

if [ condition-true ]

then

command 1

command 2

...

fi

Here is an example:

#!/bin/bash

MY_SHELL="bash"

if [ "$MY_SHELL" = "bash" ]

then

echo "You seem to like the bash shell."

fi

It is a best practice to enclose variables in quotes to prevent unexpected side effects when performing conditional tests. Here is the output of running the script:

You seem to like the bash shell.

You can also perform an action if the condition is not true by using an if/else statement. Here is what an if/else statement looks like.

if [ condition-true ]

then

command 1

command 2

...

else #

command 3

command 4

...

fi

Let's update the script to perform an action if the statement is not true.

#!/bin/bash

MY_SHELL="csh"

if [ "$MY_SHELL" = "bash" ]

then

echo "You seem to like the bash shell."

else

echo "You don't seem to like the bash shell."

fi

Here is the output. Because [ "$MY_SHELL" = "bash" ] evaluated as false, the statements following else were executed.

You don't seem to like the bash shell.

You can also test for multiple conditions using elif. The word elif is a contraction for "else if." Like if, follow elif with a condition to test for. On the following line, use the word then. Finally, provide a series of commands to execute if the condition evaluates as true.

if [ condition-true ]

then

command 1

command 2

...

elif [ condition-true ]

then

command 3

command 4

...

else #

command 5

command 6

...

fi

Here is an updated script using elif:

#!/bin/bash

MY_SHELL="csh"

if [ "$MY_SHELL" = "bash" ]

then

echo "You seem to like the bash shell."

elif [ "$MY_SHELL" = "csh" ]

then

echo "You seem to like the csh shell."

else

echo "You don't seem to like the bash or csh shells."

fi

Output:

You seem to like the csh shell.

The for Loop

If you want to perform an action on a list of items, use a for loop. The first line of a for loop starts with the word for followed by a variable name, followed by the word in and then a list of items. The next line contains the word do. Place the statements you want to execute on the following lines; then end the for loop with the word done on a single line.

for VARIABLE_NAME in ITEM_1 ITEM_2 ITEM_N

do

command 1

command 2

...

done

Esentially, what happens is that the first item in the list is assigned to the variable and the code block is executed. The next item in the list is then assigned to the variable and the commands are executed. This happens for each item in the list.

Here is a simple script that shows how a for loop works:

#!/bin/bash

for COLOR in red green blue

do

echo "COLOR: $COLOR"

done

Output:

COLOR: red

COLOR: green

COLOR: blue

It's common practice for the list of items to be stored in a variable as in this example.

#!/bin/bash

COLORS="red green blue"

for COLOR in $COLORS

do

echo "COLOR: $COLOR"

done

Output:

COLOR: red

COLOR: green

COLOR: blue

This shell script, rename-pics.sh, renames all of the files that end in jpg by prepending today’s date to the original file name.

#!/bin/bash

PICTURES=$(ls *jpg)

DATE=$(date +%F)

for PICTURE in $PICTURES

do

echo "Renaming ${PICTURE} to ${DATE}-${PICTURE}"

mv ${PICTURE} ${DATE}-${PICTURE}

done

Here's what happens when you run this script:

$ ls

bear.jpg man.jpg pig.jpg rename-pics.sh

$ ./rename-pics.sh

Renaming bear.jpg to 2015-03-06-bear.jpg

Renaming man.jpg to 2015-03-06-man.jpg

Renaming pig.jpg to 2015-03-06-pig.jpg

$ ls

2015-03-06-bear.jpg 2015-03-06-man.jpg 2015-03-06-pig.jpg rename-pics.sh

$

Positional Parameters

Positional parameters are variables that contain the contents of the command line. These variables are $0 through $9. The script itself is stored in $0, the first parameter in $1, the second in $2, and so on. Take this command line as an example:

$ script.sh parameter1 parameter2 parameter3

The contents of $0 is "script.sh", $1 is "parameter1", $2 is "parameter2", and $3 is "parameter3".

This script, archive_user.sh, accepts a parameter which is a username:

#!/bin/bash

echo "Executing script: $0"

echo "Archiving user: $1"

# Lock the account

passwd –l $1

# Create an archive of the home directory.

tar cf /archives/${1}.tar.gz /home/${1}

Comments

Anything that follows the pound sign is a comment. The only exception to this is the shebang on the first line. Everywhere else in the script, when a pound sign is encountered it marks the beginning of a comment. Comments are dutifully ignored by the interpreter as they are for the benefit of us humans.

Anything that follows the pound sign is ignored. If a pound sign starts at the beginning of a line the entire line is ignored. If a pound sign is encountered in the middle of a line, the information to the right of the pound sign is ignored.

Here is what the output looks like when we execute the archive_user.sh script:

$ ./archive_user.sh elvis

Executing script: ./archive_user.sh

Archiving user: elvis

passwd: password expiry information changed.

tar: Removing leading `/' from member names

$

Instead of referring to $1 throughout the script, let's assign its value to a more meaningful variable name.

#!/bin/bash

USER=$1 # The first parameter is the user.

echo "Executing script: $0"

echo "Archiving user: $USER"

# Lock the account

passwd –l $USER

# Create an archive of the home directory.

tar cf /archives/${USER}.tar.gz /home/${USER}

The output remains the same.

$ ./archive_user.sh elvis

Executing script: ./archive_user.sh

Archiving user: elvis

passwd: password expiry information changed.

tar: Removing leading `/' from member names

$

You can access all the positional parameters starting at $1 to the very last one on the command line by using the special variable $@. Here is how to update the archive_user.sh script to accept one or more parameters.

#!/bin/bash

echo "Executing script: $0"

for USER in $@

do

echo "Archiving user: $USER"

# Lock the account

passwd –l $USER

# Create an archive of the home directory.

tar cf /archives/${USER}.tar.gz /home/${USER}

done

Let's pass multiple users into the script.

$ ./archive_user.sh chet joe

Executing script: ./archive_user.sh

Archiving user: chet

passwd: password expiry information changed.

tar: Removing leading `/' from member names

Archiving user: joe

passwd: password expiry information changed.

tar: Removing leading `/' from member names

$

Getting User Input

If you want to accept standard input, use the read command. Remember that standard input typically comes from a person typing at the keyboard, but it can also come from other sources, like the output of a command in a command pipeline. The format for the read command is read -p "PROMPT" VARIABLE_NAME. This version of the archive_user.sh script asks for the user account.

#!/bin/bash

read –p "Enter a user name: " USER

echo "Archiving user: $USER"

# Lock the account

passwd –l $USER

# Create an archive of the home directory.

tar cf /archives/${USER}.tar.gz /home/${USER}

Let's run this script and archive the mitch account.

$ ./archive_user.sh

Enter a user name: mitch

Archiving user: mitch

passwd: password expiry information changed.

tar: Removing leading `/' from member names

$

Summary

The first line in a shell script should start with a shebang followed by the path to the interpreter that should be used to execute the script.

To assign a value to a variable, start with the variable name, followed by an equals sign, followed by the value. Do not use a space before or after the equals sign.

You can access the value stored in a variable by using $VARIABLE_NAME or ${VARIABLE_NAME}. The latter form is required if you want to precede or follow the variable with additional data.

To assign the output of a command to a variable, enclose the command in parentheses and precede it with a dollar sign. VARIABLE_NAME=$(command)

Perform tests by placing an expression in brackets. Tests are typically combined with if statements.

Use if, if/else, or if/elif/else statements to make decisions in your scripts.

To perform an action or series of actions on multiple items, use a for loop.

To access items on the command line, use positions parameters. The name of the program is represented by $0, the first parameter is represented by $1, and so on. To access all the items on the command line starting at the first parameter ($1), use the special variable $@.

You can place comments in your scripts by using the pound sign.

Accept user input by using the read command.

Quiz

1. The first line of a shell script typically starts with a shebang followed by the path to an interpreter that will be used to execute the commands in the script.

1. True

2. False

2. Which of the following variables is valid?

1. 3LETTERS="ABC"

2. first-three-letters="ABC"

3. first@Three@Letters="ABC"

4. FIRST3LETTERS="ABC"

3. What is the value of "$1" given the following command line:

$ ./add-user.sh tom richard harry

1. ./add-user.sh

2. tom

3. richard

4. harry

4. Which is the proper way to assign a value to a variable?

1. VAR="VALUE"

2. VAR = "VALUE"

5. Which of the following will assign the output of the hostname command to the variable HOSTNAME?

1. HOSTNAME=$(hostname)

2. HOSTNAME=`hostname`

3. All of the above.

Quiz Answers

1. A

2. D

3. B

4. A

5. C