Table of Contents for
Linux Shell Scripting Bootcamp

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Linux Shell Scripting Bootcamp by James Kent Lewis Published by Packt Publishing, 2017
  1. Cover
  2. Table of Contents
  3. Linux Shell Scripting Bootcamp
  4. Linux Shell Scripting Bootcamp
  5. Credits
  6. About the Author
  7. Acknowledgement
  8. About the Reviewer
  9. www.PacktPub.com
  10. Customer Feedback
  11. Preface
  12. What you need for this book
  13. Who this book is for
  14. Conventions
  15. Reader feedback
  16. Customer support
  17. 1. Getting Started with Shell Scripting
  18. Demonstrating the use of scripts
  19. Summary
  20. 2. Working with Variables
  21. Validating parameters using conditional statements
  22. Comparison operators for strings
  23. Environment variables
  24. Summary
  25. 3. Using Loops and the sleep Command
  26. Screen manipulation
  27. Indenting your code
  28. Using the for statement
  29. Leaving a loop early
  30. The sleep command
  31. Watching a process
  32. Creating numbered backup files
  33. Summary
  34. 4. Creating and Calling Subroutines
  35. File redirection
  36. Command piping
  37. Subroutines
  38. Using parameters
  39. Making a current backup of your work
  40. Summary
  41. 5. Creating Interactive Scripts
  42. Summary
  43. 6. Automating Tasks with Scripts
  44. Summary
  45. 7. Working with Files
  46. Reading files
  47. Reading and writing files
  48. Reading and writing files interactively
  49. File checksums
  50. File encryption
  51. Summary
  52. 8. Working with wget and curl
  53. wget and recursion
  54. wget options
  55. curl
  56. Summary
  57. 9. Debugging Scripts
  58. Automatic backups
  59. More syntax errors
  60. Logic errors
  61. Using set to debug scripts
  62. Summary
  63. 10. Scripting Best Practices
  64. ssh and scp
  65. Find and use a good text editor
  66. Environment variables and aliases
  67. ssh prompt
  68. Testing an archive
  69. Progress indicator
  70. Creating new commands from a template
  71. Alerting the user
  72. Summary
  73. Index

Chapter 5. Creating Interactive Scripts

This chapter shows how to read the keyboard in order to create interactive scripts.

The topics covered in this chapter are:

  • How to use the read built-in command to query the keyboard.
  • The different ways to use read.
  • The use of traps (interrupts).

The reader will learn how to create interactive scripts.

The scripts we have looked at up to this point have run without much user interaction. The read command is used to create scripts that can query the keyboard. The code can then take action based on the input.

Here is a simple example:

Chapter 5 - Script 1

#!/bin/sh
#
# 5/16/2017
#
echo "script1 - Linux Scripting Book"

echo "Enter 'q' to quit."
rc=0
while [ $rc -eq 0 ]
do
 echo -n "Enter a string: "
 read str
 echo "str: $str"
 if [ "$str" = "q" ] ; then
  rc=1
 fi
done

echo "End of script1"
exit 0

And here is the output when run on my system:

Chapter 5 - Script 1

This is a good one to run on your system. Try several different strings, numbers, and so on. Notice how the returned string contains whitespace, special characters, and so on. You don't have to quote anything, and if you do those will be returned as well.

You can also use the read command to put a simple pause into your script. This will allow you to see the output before it scrolls off the screen. It can also be used when debugging which will be shown in Chapter 9, Debugging Scripts.

The following script shows how to create a pause when the output gets to the last line of the screen:

Chapter 5 - Script 2

#!/bin/sh
#
# 5/16/2017
# Chapter 5 - Script 2
#
linecnt=1                    # line counter
loop=0                       # loop control var
while [ $loop -eq 0 ]
do
 echo "$linecnt  $RANDOM"    # display next random number
 let linecnt++
 if [ $linecnt -eq $LINES ] ; then
  linecnt=1
  echo -n "Press Enter to continue or q to quit: "
  read str                   # pause
  if [ "$str" = "q" ] ; then
   loop=1                    # end the loop
  fi
 fi
done

echo "End of script2"
exit 0

And here is the output when run on my system:

Chapter 5 - Script 2

I pressed Enter twice, and then Q and Enter on the last one.

Let's try something a bit more interesting. This next script shows how to fill an array with values taken from the keyboard:

Chapter 5 - Script 3

#!/bin/sh
#
# 5/16/2017
#
echo "script3 - Linux Scripting Book"

if [ "$1" = "--help" ] ; then
 echo "Usage: script3"
 echo " Queries the user for values and puts them into an array."
 echo " Entering 'q' will halt the script."
 echo " Running 'script3 --help' shows this Usage message."
 exit 255
fi

x=0                          # subscript into array
loop=0                       # loop control variable
while [ $loop -eq 0 ]
do
 echo -n "Enter a value or q to quit: "
 read value
 if [ "$value" = "q" ] ; then
  loop=1
 else
  array[$x]="$value"
  let x++
 fi
done

let size=x
x=0
while [ $x -lt $size ]
do
 echo "array $x: ${array[x]}"
 let x++
done

echo "End of script3"
exit 0

And the output:

Chapter 5 - Script 3

Since this script does not require any parameters I decided to add a Usage statement. This will display if the user runs it with --help and is a common feature in many system scripts and programs.

The only thing new in this script is the read command. The loop and array variables were discussed in an earlier chapter. Note again that, with the read command what you type is what you get.

Now let's create a complete interactive script. But first we need to check the size of the current terminal. If it is too small, the output of your script may become garbled and the user may not know why or how to fix it.

The following script contains a subroutine that checks the size of the terminal:

Chapter 5 - Script 4

#!/bin/sh
#
# 5/16/2017
#
echo "script4 - Linux Scripting Book"

checktermsize()
{
 rc1=0                       # default is no error
 if [[ $LINES -lt $1 || $COLUMNS -lt $2 ]] ; then
  rc1=1                      # set return code
 fi
 return $rc1
}

rc=0                         # default is no error
checktermsize 40 90          # check terminal size
rc=$?
if [ $rc -ne 0 ] ; then
 echo "Return code: $rc from checktermsize"
fi

exit $rc

Run this on your system with different-sized terminals to check the result. As you can see from the code, it's okay if the terminal is larger than needed; it just can't be too small.

Note

A word about terminal sizes: When using the tput cursor movement commands remember that it is row then column. However, most modern GUIs go by column then row. This is unfortunate as it is very easy to get them mixed up.

Now let's look at a full interactive script:

Chapter 5 - Script 5

#!/bin/sh
#
# 5/27/2017
#
echo "script5 - Linux Scripting Book"

# Subroutines
cls()
{
 tput clear
}

move()                       # move cursor to row, col
{
 tput cup $1 $2
}

movestr()                    # move cursor to row, col
{
 tput cup $1 $2
 echo -n "$3"                # display string
}

checktermsize()
{
 rc1=0                       # default is no error
 if [[ $LINES -lt $1 || $COLUMNS -lt $2 ]] ; then
  rc1=1                      # set return code
 fi
 return $rc1
}

init()                       # set up the cursor position array
{
 srow[0]=2;  scol[0]=7       # name
 srow[1]=4;  scol[1]=12      # address 1
 srow[2]=6;  scol[2]=12      # address 2
 srow[3]=8;  scol[3]=7       # city
 srow[4]=8;  scol[4]=37      # state
 srow[5]=8;  scol[5]=52      # zip code
 srow[6]=10; scol[6]=8       # email
}

drawscreen()                 # main screen draw routine
{
 cls                         # clear the screen
 movestr 0 25 "Chapter 5 - Script 5"
 movestr 2 1 "Name:"
 movestr 4 1 "Address 1:"
 movestr 6 1 "Address 2:"
 movestr 8 1 "City:"
 movestr 8 30 "State:"
 movestr 8 42 "Zip code:"
 movestr 10 1 "Email:"
}

getdata()
{
 x=0                         # array subscript
 rc1=0                       # loop control variable
 while [ $rc1 -eq 0 ]
 do
  row=${srow[x]}; col=${scol[x]}
  move $row $col
  read array[x]
  let x++
  if [ $x -eq $sizeofarray ] ; then
   rc1=1
  fi
 done
 return 0
}

showdata()
{
 fn=0
 echo ""
 read -p "Enter filename, or just Enter to skip: " filename
 if [ -n "$filename" ] ; then       # if not blank
  echo "Writing to '$filename'"
 fn=1                       # a filename was given
 fi
 echo ""                     # skip 1 line
 echo "Data array contents: "
 y=0
 while [ $y -lt $sizeofarray ]
 do
  echo "$y - ${array[$y]}"
  if [ $fn -eq 1 ] ; then
   echo "$y - ${array[$y]}">>"$filename"
  fi
  let y++
 done
 return 0
}

# Code starts here
sizeofarray=7                # number of array elements

if [ "$1" = "--help" ] ; then
 echo "Usage: script5 --help"
 echo " This script shows how to create an interactive screen program."
 exit 255
fi

checktermsize 25 80
rc=$?
if [ $rc -ne 0 ] ; then
 echo "Please size the terminal to 25x80 and try again."
 exit 1
fi

init                         # initialize the screen array
drawscreen                   # draw the screen
getdata                      # cursor movement and data input routine
showdata                     # display the data

exit 0

Here is some example output:

Chapter 5 - Script 5

There is a lot of new information here, so let's take a look. First the subroutines are defined, and you can see we included the checktermsize subroutine from the preceding Script 4.

The init routine sets up the cursor placement array. It's good programming practice to put initial values in a subroutine, particularly if it is going to be called again.

The drawscreen routine displays the initial form. Note that I could have used the values in the srow and scol array here, however, I didn't want the script to look too cluttered.

Look very carefully at the getdata routine because this is where the fun begins:

  • First the array subscript x and control var rc1 are set to 0.
  • In the loop the cursor is placed at the first position (Name:).
  • The keyboard is queried and the user's input goes into the array at sub x.
  • x is incremented and we go to the next field.
  • If x is equal to the size of the array we leave the loop. Keep in mind that we start counting at 0.

The showdata routine displays the array data and then we are done.

Tip

Note that if the script is run with the --help option the Usage message is displayed.

This is just a small example of an interactive script to show the basic concepts. In a later chapter we will go into this in more detail.

The read command can be used in a number of different ways. Here are a few examples:

read var
Wait for input of characters into the variable var.
read -p "string" var
Display contents of string, stay on the line, and wait for input.

read -p "Enter password:" -s var
Display "Enter password:", but do not echo the typing of the input. Note that a carriage return is not output after Enter is pressed.

read -n 1 var

The -n option means to wait for that number of characters and then continue, it does not wait for an Enter press.

In this example it will wait for 1 char and then go. This can be useful in utility scripts and games:

Chapter 5 - Script 6

#!/bin/sh
#
# 5/27/2017
#
echo "Chapter 5 - Script 6"

rc=0                         # return code
while [ $rc -eq 0 ]
do
 read -p "Enter value or q to quit: " var
 echo "var: $var"
 if [ "$var" = "q" ] ; then
  rc=1
 fi
done

rc=0                         # return code
while [ $rc -eq 0 ]
do
 read -p "Password: " -s var
 echo ""                     # carriage return
 echo "var: $var"
if [ "$var" = "q" ] ; then
  rc=1
 fi
done

echo "Press some keys and q to quit."
rc=0                         # return code
while [ $rc -eq 0 ]
do
 read -n 1 -s var            # wait for 1 char, does not output it
 echo $var                   # output it here
 if [ "$var" = "q" ] ; then
  rc=1
 fi
done

exit $rc

And the output:

Chapter 5 - Script 6

The comments in the script should make this one pretty self explanatory. The read command has a few more options, one of which will be shown in the next script.

Another way to query the keyboard is by using what is called a trap. This is a subroutine that is accessed when a special key sequence is pressed, such as Ctrl + C.

Here is an example of using a trap:

Chapter 5 - Script 7

#!/bin/sh
#
# 5/16/2017
#
echo "script7 - Linux Scripting Book"

trap catchCtrlC INT          # Initialize the trap

# Subroutines
catchCtrlC()
{
 echo "Entering catchCtrlC routine."
}

# Code starts here

echo "Press Ctrl-C to trigger the trap, 'Q' to exit."

loop=0
while [ $loop -eq 0 ]
do
 read -t 1 -n 1 str          # wait 1 sec for input or for 1 char
 rc=$?

 if [ $rc -gt 128 ] ; then
  echo "Timeout exceeded."
 fi

 if [ "$str" = "Q" ] ; then
  echo "Exiting the script."
  loop=1
 fi

done

exit 0

Here is the output on my system:

Chapter 5 - Script 7

Try running this one on your system. Press some keys and see the response. Press Ctrl + C a few times as well. When done press Q.

That read statement needs some further explanation. Using read with the -t option (timeout) means to wait that many seconds for a character. If one is not input in the allotted time it will return a code with a value greater than 128. As we have seen before, the -n 1 option tells read to wait for 1 character. So this means we are waiting 1 second for 1 character. This is another way read can be used to create a game or other interactive script.

Note

Using a trap is a good way to catch an accidental press of Ctrl + C which could cause data to be lost. One word of caution however, if you do decide to catch Ctrl + C make sure your script has some other way to exit. In the simple script above the user must type a Q to exit.

If you get yourself into a situation where you can't exit a script you can use the kill command.

For example, if I had needed to stop script7 the directions would be follows:

 guest1 $ ps auxw | grep script7
 guest1   17813  0.0  0.0 106112  1252 pts/32   S+   17:23   0:00 /bin/sh ./script7
 guest1   17900  0.0  0.0 103316   864 pts/18   S+   17:23   0:00 grep script7
 guest1   29880  0.0  0.0  10752  1148 pts/17   S+   16:47   0:00 kw script7
 guest1 $ kill -9 17813
 guest1 $

In the terminal where script7 was running you will see it has stopped with the word Killed in it.

Note, be sure to kill the right process!

In the example above, PID 29880 is my text editor session where I am writing script7. Killing that would not be a good idea :).

Now for some fun! The next script allows you to draw crude pictures on the screen:

Chapter 5 - Script 8

#!/bin/sh
#
# 5/16/2017
#
echo "script8 - Linux Scripting Book"

# Subroutines
cls()
{
 tput clear
}

move()                       # move cursor to row, col
{
 tput cup $1 $2
}

movestr()                    # move cursor to row, col
{
 tput cup $1 $2
 echo -n "$3"                # display string
}

init()                       # set initial values
{
 minrow=1                    # terminal boundaries
 maxrow=24
 mincol=0
 maxcol=79
 startrow=1
 startcol=0
}

restart()                    # clears screen, sets initial cursor position
{
 cls
 movestr 0 0 "Arrow keys move cursor. 'x' to draw, 'd' to erase, '+' to restart, 'Q' to quit."
 row=$startrow
 col=$startcol

 draw=0                      # default is not drawing
 drawchar=""
}

checktermsize2()             # must be the specified size
{
 rc1=0                       # default is no error
 if [[ $LINES -ne $1 || $COLUMNS -ne $2 ]] ; then
  rc1=1                      # set return code
 fi
 return $rc1
}

# Code starts here
if [ "$1" = "--help" ] ; then
 echo "Usage: script7 --help"
 echo " This script shows the basics on how to create a game."
 echo " Use the arrow keys to move the cursor."
 echo " Press c to restart and Q to quit."
 exit 255
fi

checktermsize2 25 80         # terminal must be this size
rc=$?
if [ $rc -ne 0 ] ; then
 echo "Please size the terminal to 25x80 and try again."
 exit 1
fi

init                         # initialize values
restart                      # set starting cursor pos and clear screen

loop=1
while [ $loop -eq 1 ]
do
 move $row $col              # position the cursor here
 read -n 1 -s ch

 case "$ch" in
  A) if [ $row -gt $minrow ] ; then
      let row--
     fi
     ;;
  B) if [ $row -lt $maxrow ] ; then
      let row++
     fi
     ;;
  C) if [ $col -lt $maxcol ] ; then
      let col++
     fi
     ;;
  D) if [ $col -gt $mincol ] ; then
      let col--
     fi
     ;;
  d) echo -n ""             # delete char
     ;;
  x) if [ $col -lt $maxcol ] ; then
      echo -n "X"            # put char
      let col++
     fi
     ;;
  +) restart ;;
  Q) loop=0 ;;
 esac
done

movestr 24 0 "Script completed normally."
echo ""                      # carriage return

exit 0

This was fun to write and a bit more fun to play with than I expected it to be.

One thing we haven't covered yet is the case statement. This is similar to an if...then...else but makes the code easier to read. Basically, the value that was input to the read statement is checked for a match in each case clause. If it matches, that stanza is executed and then control goes to the line after the esac statement. It also does this if there is no match.

Try this script, and remember to make the terminal 25x80 (or 80x25 if that is how your GUI works).

Here is just one example of what can be done with this script:

Chapter 5 - Script 8

Well okay I guess this shows that I am not much of an artist. I will stick to programming and writing books.