User Tools

Site Tools


haas:spring2020:unix:labs:lab6

Corning Community College

CSCS1730 UNIX/Linux Fundamentals

Lab 0x6: Shell Scripting Concepts

~~TOC~~

Objective

To become familiar with the basics of shell scripting.

Reference

Background

As we've explored and familiarized ourselves with the various tools available to us on the command-line, our capabilities have increased to enable solving of increasingly complex and detailed problems. What were once impossible or data-entry intensive tasks have become more doable, feasible, and even time-saving.

With shell scripting, we continue that trend, by increasing our abilities to solve problems by delegating the actual grunt work (processing, calculation, data entry, routine task(s)) to the computer, freeing us up to solve more meaningful tasks.

Much like a play or movie script, a shell script is basically a collection of commands, algorithms, and logic that is, by default, read from start to finish, that unfolds the tale of a particular story.

In our case, we're telling the story of a particular task or problem to solve, and the computer plays it out for us. We are the playwright.

Shell scripts can range from a simple progression of a few commands, to a sophisticated algorithm, to something which automates regular/routine work, to even a fully interactive experience to be used by some end user.

Shell scripts and their abilities provided by their corresponding scripting languages differ from programming languages in that we are operating on an entirely different (and higher) level.

Where languages like machine code and assembly language are among the lowest level languages, and offer the best performance and intrinsic capabilities exposing the greatest details of the hardware, higher level languages abstract away pieces of that detail, taking capabilities away from us, but enabling other solutions that would have been tedious to implement having to take into account so many details.

Programming languages like C and C++ offer some middle ground… C especially is considered a “hybrid” language, having both low and high level features, and although there are still things that machine or assembly language could outperform, C offers more sanity.

Higher level languages like Java and Visual Basic abstract away more details, enabling some solutions to be easily coded, but making others more difficult as it removes the ability for the user to expose themselves to the details of the machine.

Scripting languages are typically among the very top of the high level language scale, removing pretty much all underlying detail considerations, and possessing typically the most “human readable” syntax, enabling some quick and easy solutions to problems.

Again, the sacrifice we make going higher level is an inevitable loss in performance and efficiency for the benefit of having to deal with less detail getting in the way of directly coding a solution.

The lower we go, we gain that performance and efficiency at the cost of more detail, which slows down overall development, but if the solution calls for performance and/or efficiency, a lower level tool is the tool for the job.

A simple script

A shell script is really as simple as a literal script for the computer to follow. We begin by illustrating a simple script listing files in our home directory, checking available disk space, and getting a list of who is currently on the system:

ls ~
df
who

These commands should go in a regular text file, perhaps named something like script1.sh or script1.

Although UNIX/Linux doesn't require file extensions, the traditional convention is to suffix a shell script with the extension “.sh”

This is done more for end user recognition of the presence of a shell script. If desired, an extension need not be given, and it would not affect the operation of the script at all.

Running our script

With the above commands in script1.sh, and us saved and exited out of our text editor, it now comes time to run our script:

lab46:~$ ./script1.sh
-bash: ./script1.sh: Permission denied
lab46:~$ 

Well, not quite yet. There's one additional detail we need to take into account, and that is we desire to run our script, but will the system allow us to run it?

1. Thinking about how we'd make our script runnable:
a.View the permissions of script1.sh. How did you do it?
b.What are the permissions?
c.How could you change the permissions to allow the script to be runnable?
d.What did you type?
e.Test it by attempting to run the script again. Did it work?

As you will see from a successful setting of permissions, the script should now run when invoked, and basically provide the output of the commands therein, in the order listed. The computer just processed those commands for us, without us having to type them in.

The benefits of this become apparent when we can just run the script again, and again, and again. And the computer follows those same set of instructions diligently each time.

Scripting Philosophy

Although pretty much any command or capability is fair game for inclusion into a script, we will focus on some common basics that will get you started.

The beauty of shell scripting is that it opens the door to near limitless possibilities. In some cases, there simply cannot be just one solution to a problem. Just as with programming, we now enter into a realm that requires unstructured thought and creativity in order to accomplish structured tasks, and such begins a more effective utilization of computing resources.

The intent for shell scripting is to free us from manually performing tasks. Whenever you find yourself doing a similar task over and over again on the computer, stop and think of ways the task could be automated, potentially freeing you of some or all of it. Our job turns more to managing the computing resources, and the more time you spend scripting, the more you'll be amazed at the problems you're now able to accomplish.

And also remember– ANYTHING you are able to type or concoct on the command-line is able to be a part of a shell script. The command-line therefore makes for an excellent prototyping environment, trying things out to verify their operation before embedding within a script.

Simple I/O

Two commands will be among our simplest for input and output:

  • echo
  • read

echo

The echo command, as we've used in the past, is a simple and effective means of displaying variable contents and strings.

For example:

lab46:~$ echo "Hello, World!"
Hello, World!
lab46:~$ 

We can use echo in many cases when we need to display something.

There are two common uses for echo, the above example and the following:

lab46:~$ echo -n "Hello, World!"
Hello, World!lab46:~$ 

With the addition of the “-n” argument to echo, we disable the automatic newline character at the end of the string. We see its immediate impact in the second example as the string is displayed, then immediately followed by the prompt.

In our scripts, disabling the newline may help enable more stylized output, or helping to display variable data inline amongst some other command incantation.

read

We will use read to get input from the user. It basically accepts a variable (or variables) to place input into.

For example:

lab46:~$ read name
bob
lab46:~$ echo $name
bob
lab46:~$ 

or…

lab46:~$ read num1 num2
4 7
lab46:~$ echo $num2
7
lab46:~$ echo $num1
4
lab46:~$ 

The read command is a quick and effective way of obtaining input from STDIN.

arithmetic

One of the classic ways to do arithmetic operations in the Bourne shell is with the let keyword.

As there are no data types in shell variables, we must indicate when we wish to use data in a particular way. The let keyword enables this, by treating variable contents as if they are numeric.

Care should be taken to ensure that data utilized in a let statement IS actually numeric, otherwise errors could take place.

To illustrate the use of the let keyword, try the following:

lab46:~$ read num1 num2
6 4
lab46:~$ let num1=$num1+$num2
lab46:~$ echo $num1
10
lab46:~$ 

The let keyword enables the simple arithmetic operations (certainly addition, subtraction, and multiplication), but is rather limited in its abilities. If you plan to do more in-depth calculations, you should use a tool that is designed to effectively calculate numbers (write a program in a lower-level language, use a tool such as bc(1), or use some other facility that has expanded arithmetic capabilities).

But again, we're aiming for an introduction, and an exposure to traditionally classic functionality. bash(1) in particular has made a lot of enhancements that may improve greatly upon the capabilities we are covering, so it would behoove you to familiarize yourself with the functionality of the particular shell you're working with.

2. To play with the let statement, create age.sh and have it do the following:
a.Prompt the user for their birth year.
b.Store the current year, as determined by date(1), in a variable.
c.How did you store the current year, as output by date(1), in a variable?
d.In your script, subtract the birth year variable from the current year variable. Display the result.
e.How did you perform the subtraction?
f.Provide your script.

shabang

Before we go any further, it is important to point out a potential problem we could face, and a solution that was implemented to avoid that problem.

In our first script we wrote above, when we ran it, it ran under our current shell (which by default is bash(1)). There exist many shells, and it is rather easy to switch to a different one. Different shells have different shell syntax, and if you end up using any bash(1) specific code in your script, someone who does not run bash(1) could end up having problems running your script.

To avoid this problem, the shabang facility was created, which launches a particular shell environment to run your script within.

If you write in bash(1), you want to ensure that an instance of bash is run.

The format of the shabang is as follows:

#!/path/to/shell -options

In the case of bash, we'd want to do the following, to have as the very first line in our scripts:

#!/bin/bash

This (the SHH character followed by the BANG character), will launch the specified command, then all script contents will be run within the environment of that command.

selection

Often, a common evaluation to make in processing a task is to compare two (or more) values, and to execute a portion of code conditionally dependent upon the result of the comparison.

Whenever we use the words “if”, “then”, and/or “else”, we are making a choice (or selection)… something happens as the result of that condition.

Pretty much every programming and scripting language implements the venerable if statement, and most notably so, as it forms the basis for much of your script's ability to solve tasks.

if

To use an if statement, we first need a condition to evaluate.

We'll start off by having the following script logic, in a file called script2.sh:

#!/bin/bash
 
echo -n "Pick a number: "
read num1
 
echo -n "Pick another number: "
read num2

In the code so far, we've prompted the user for two separate numbers. We've stored those responses in the variables $num1 and $num2.

Now, what we'd like to do is perform a comparison to see which of the two numbers if higher. For starters, we'll compare to see if $num1 is less than $num2, so append the following to script2.sh:

if [ "$num1" -lt "$num2" ]; then
    echo "$num2 is greater than $num1"
fi

Here we have among the simplest of if statements. It is actually a symbiotic relationship with the [(1) utility (yes, the square brackets are commands on the system– look up their manual page).

In the if, we evaluate “$num1” and “$num2”, seeing if the left parameter is less than the parameter on the right. If the result of this comparison is true, we run the code within the if body, that is, the echo line.

When our if statement, and all its supporting code has concluded, we must terminate the if code block with the keyword fi (just the word “if” spelled backwards). This forms the syntax for our simple singular if statement.

We can of course be more capable. We will now expand our if statement to include an else if, or secondary if, within the same if block. Replace the above if block with the following in your script:

if [ "$num1" -lt "$num2" ]; then
    echo "$num2 is greater than $num1"
elif [ "$num2" -lt "$num1" ]; then
    echo "$num1 is greater than $num2"
fi

As you can see, else if statements in bash are elif, and we follow it up with another comparison, this time comparing to see if $num2 is less than $num1. We then print out a corresponding statement if that condition is true.

Finally, we should address the third and final condition that can take place in our current example.. and that is if both numbers are equal. If you look at the logic of our two conditions, you see we are comparing less than conditions ONLY… if both are equal, neither of the first two if conditions will be true.

So, we will follow this up with a catch-all statement, the else. So replace the if block with the following in your script:

if [ "$num1" -lt "$num2" ]; then
    echo "$num2 is greater than $num1"
elif [ "$num2" -lt "$num1" ]; then
    echo "$num1 is greater than $num2"
else
    echo "$num1 and $num2 are equal in value"
fi

A couple things to keep in mind regarding if statements:

  • within an if block, you can have multiple elif statements. You're not just limited to one.
  • within an if block, you can ONLY have one if, and one else
  • within an if block, there is only one terminating fi.
  • there NEED to be spaces surrounding the [ and ] commands from conditional logic.
  • there NEED to be spaces between each element of the condition.
  • the double quotes aren't required, but are a good means of preventing more serious errors if either variable is NULL.
  • the semi-colon is a command separator. It actually has nothing to do with the if block (merely allows us to have the then on the same line as the if; some prefer it on the next line.

So feel free to utilize if blocks in your scripts, to enable whatever comparisons necessary.

Obviously, we aren't just limited to the less than comparison. There are a number of comparisons at our disposal. A list of some common ones (check manual pages for a more complete list):

Symbol Description
-lt is less than
-gt is greater than
-eq is equal to
-le is less than or equal to
-ge is greater than or equal to
-ne is not equal to
3. To play with the if statement, create guess1.sh and have it do the following:
a.After the shabang line, put the following line: pick=$(($RANDOM % 20))
b.Prompt the user to guess a number, between 1 and 20. Store the response in a variable.
c.With only 4 guesses total, compare the user's guess against the value in $pick.
d.If the user guesses correctly, display a message indicating so. If not, tell them.
e.What do you suppose the pick=$(($RANDOM % 20)) line does?
f.Provide your script.

iteration

To finish our introductory journey, we'll take a look at iteration structures, which allow us to repeat a task some number of times.

In programming languages, these typically take the form of for and while loops, and sure enough, both are present to us in shell scripting. In addition, bash has a number of additional iteration structures that could be useful in certain situations. The bash manual page would be a good reference for this information.

For this lab, we will only be taking a look at the for iteration structure.

numeric for loop

In bash, there are two forms of for loops, the numeric for loop, and the list-based for loop. We will take a look at both.

First up, the numeric for loop.

This loop, for those who have experience programming in other languages, will recognize similarity to existing knowledge. The numeric for loop has 3 values:

  1. the starting condition
  2. the looping condition (ie it will keep looping as long as that condition is true)
  3. the stepping value

We will start with a simple script to display the numbers 1-10 to the screen, one per line:

#!/bin/bash
 
for((i=1; i<=10; i++)); do
    echo "$i"
done

The i++ is shorthand for the equation: i=i+1. Either could be used in that field.

If you'd like to alter the stepping value, put in the appropriate equation.

4. Looking at our numeric for loop script above, modify it to do the following:
a.Instead of one per line, put the count all on one line, each separated by a space. How did you do this?
b.Instead of going from 1 to 10, go from 20 to 2, stepping 2 values per iteration. How did you do this?

list-based for loop

In addition to the numeric for loop, we have the list-based for loop. This is still a loop, it still iterates a number of times, but it does so not on a condition to evaluate, but instead a list of provided values.

Check out the following script:

#!/bin/bash
 
echo -n "First color is: "
for color in red orange yellow green blue indigo violet; do
    echo "$color"
    echo -n "Next color is: "
done

Go ahead and run this script, analyze the output, and make sure everything in it makes sense. Be sure to ask questions.

5. Looking at our list-based for loop script:
a.How many times does the loop iterate?
b.What is the value of $color the fifth time through?
c.Modify the script so that for the case of the color violet, it prepends the text “Final color is: ” instead of “Next color is: ”… output for all the other colors remains unchanged.
d.How did you do this?

Scripts

With your scripting knowledge, please write for me the following scripts, and answer any pertinent questions:

6. Write a script to do the following:
a.Creates 8 files: file1 file2 file3 file4 file5 file6 file7 file8
b.Place the number of the filename within the file (file5 has a 5 in it, etc.)
c.Prompt the user to pick a number between 1 and 8
d.Have your script insert that number in a new file of that numbered name, shifting all the other file names out of the way.
e.When you are done, there should be 9 files, two of which contain the same number.
f.Please put your script on your Opus or Portfolio page.
7. Write a script to do the following:
a.Display a 4 line histogram of all the files in a given directory, sized to fit a standard screen.
b.Prompt the user for a directory, capture it into a variable and change to that directory in your script.
c.List and count all the files that begin with lowercase a-g, store that number in a variable.
d.List and count all the files that begin with lowercase h-m, store that number in another variable.
e.List and count all the files that begin with lowercase n-s, store that number in another variable.
f.List and count all the files that begin with lowercase t-z, store that number in another variable.
g.Cap each number at a max of 60, if it goes above just truncate it to 60, and keep that result for each.
h.Display a row heading (which section of the alphabet) followed by the histogram for that line. Use asterisks.
i.Your script should output the directory it is processing, total files, and then 4 lines of histogram.
j.Please put your script on your Opus or Portfolio page.

Sample output for the above script could resemble:

lab46:~$ ./myscript
Please enter directory to process: /some/dir
Processing ...

Directory: /some/dir
Total Files: 42

(a-g):***************
(h-m):********
(n-s):*****************
(t-z):**

lab46:~$ 

Aside from the requirement to use asterisks, you are free to format the output of your script as you see fit. The requested information needs to be clearly visible.

Conclusions

This assignment has activities which you should tend to- document/summarize knowledge learned on your Opus.

As always, the class mailing list and class IRC channel are available for assistance, but not answers.

haas/spring2020/unix/labs/lab6.txt · Last modified: 2014/02/23 11:15 by 127.0.0.1