CMPS 12A Introduction to Programming Lab Assignment 7 In this assignment you will write a bash script that interacts with the user and does some simple calculations, emulating the functionality of programming assignment 1. In particular you will learn to use shell variables, the read command and pipelines. You'll also learn how to capture the output of a program as a string. Begin by reading the following articles on these topics. Bash shell variables: https://ryanstutorials.net/bash-scripting-tutorial/bash-variables.php The read command: https://ryanstutorials.net/bash-scripting-tutorial/bash-input.php Unix pipes: https://www.tutorialspoint.com/unix/unix-pipes-filters.htm The bc command: https://www.gnu.org/software/bc/manual/html_mono/bc.html#sec1 Log on to the Unix timeshare, create a subdirectory within your cs12a directory called lab7 and cd into it. Type the following at the command line. (Remember that represents the command prompt.) var1=abc var2=123 To create a shell variable, just assign a value to an identifier as above. The rules for valid identifiers are basically the same as for Java. Identifiers must consist only of letters, underscores and digits, and may not begin with a digit. The assignment statement in bash does not allow spaces on either side of the = sign, so var1= abc var1 =abc var1 = abc will all fail (try it and see the error messages.) In bash (as in other Unix shells), the syntax for reading from a variable differs from the syntax for writing to a variable. To read the value in a variable, prefix its name with the $ sign. echo $var1 $var2 Recall that the echo command just copies its command line arguments to stdout. Try doing just echo var1 var2 and see what happens. Shell variables have no data type as we understand it in Java. All variables store character sequences (strings), so the assignments var1="abc" and var2="123" would be entirely equivalent to those given above. Variables within quotes are expanded by the command interpreter. Try the following example. echo $var1 $var2 echo "$var1 $var2" In the first case, echo has two command line arguments, and intervening space is ignored. In the second, there is just one command line argument, and the intervening space is part of that argument. If you want echo to print a literal "$" sign, precede it with a backslash "\". 1
echo "var1 is a variable, type \$var1 to extract its value $var1" var3="the expression \$var2 expands to $var2" echo $var3 To see why the echo command is essential in all of this, try the following. $var1 bash: abc: command not found... The expression $var1 is expanded to abc, then interpreted as a command that does not exist. As you can see, the shell sees everything as a command to be performed, or as an argument to that command. In addition to any variables you may define, the shell maintains a number of variables to help manage your login session. These are called environment variables. It is good practice to not change these values unless you know what you are doing. For instance do echo $HOME to see the full path of your home directory. The command printenv gives a complete listing of all environment variables and their current values. The read command can be used from within a script to take the value of a variable from stdin. The following script, called HelloName, is an interactive version of the HelloWorld program. It prompts for the users name, reads it from stdin and assigns it to a variable using read, prints a message, then prints out the values of some environment variables. This script, along with several others, is posted under Examples/lab7 on the course webpage. #! /bin/bash #-------------------------------------------------------- # HelloName #-------------------------------------------------------- echo "Enter your name:" read name echo "Hello, $name!" echo "Here are values the of some environment variables." echo "USER=$USER" echo "LOGNAME=$LOGNAME" echo "HOME=$HOME" echo "HOSTNAME=$HOSTNAME" echo "PWD=$PWD" echo "EDITOR=$EDITOR" echo "HISTSIZE=$HISTSIZE" echo "HISTCONTROL=$HISTCONTROL" echo "PATH=$PATH" Observe that we don't use the $ prefix on the variable name within the read command, because we are writing to the variable, not reading from it. On the very next line however, we do use $ to extract the value of the variable. We often wish to take the output of one shell command as the input of another. The mechanism for doing this is called a pipe. The following shell syntax does exactly that. 2
cmd1 cmd2 More precisely, the stdout stream of cmd1 becomes the stdin stream for cmd2. We can keep going for any number of commands. For instance cmd1 cmd2 cmd3 cmd4 ls wc pipes the output of ls (list contents of current directory) to wc (word count). Do man wc to learn more about this useful command. Try the above combination from a non-empty directory such as your home directory, since lab7 may not contain anything yet. In such a pipeline (as the above syntax is called), command line arguments are placed after their respective command. Try for instance ls -a wc -c It's also very useful to take the output of a command and save it as a string. To do this, surround the command with $( ), then assign the expression to a variable. var4=$( ls -a wc -c ) echo "the number of characters is $var4" now=$(date) echo "today is $now" The bash shell is capable of performing some primitive arithmetic operations, but is not really suited to the task of doing direct calculations. However, there are a number of standard tools in Unix for doing arithmetic, one of which is the bc (basic calculator) command. First, observe that if you just type bc, you enter an interactive session with the calculator, and are able to do decimal arithmetic of arbitrary precision. bc 1+1 2 293874928374928734928374*93874509283740598273405987 27587364691990850896048871721228022755405467775138 quit The link at the beginning of this document goes to a fairly concise manual for bc, which is actually a complete programming language. We will not be writing scripts in this language. Instead we will run bc from within a bash script to do our calculations as needed. We begin by running a few examples from the command line. Consider the following calculation of π, accurate to 60 decimal places. pi=$( echo "scale=60;4*a(1)" bc -l ) echo $pi 3.141592653589793238462643383279502884197169399375105820974944 Note that bc reads its input from stdin and writes to stdout. Thus the pipe " " above sends the stdout of echo, which is the string "scale=60;4*a(1)", into the stdin of bc. The semicolon ";" in the input 3
string separates two bc statements. The first statement "scale=60" tells bc to perform its calculation with 60 decimal digits of accuracy. The second statement "4*a(1)" is an expression to be evaluated, namely 4 arctan(1), which is a formula for π. Notice the command line argument "-l" to bc (that's the letter l, not the number 1). This option tells bc to use its library of built in functions, which includes the inverse tangent function as a(). Without this option, function a() would not be recognized by bc (try leaving it out.) The stdout of bc is then captured by $( ) and assigned to the shell variable pi. Study the bash scripts Area and Circle, which use bc to do numerical calculations. Both are posted in Examples/lab7 on the course webpage. Also study the example HMS, which emulates the similarly named program in Java. Recall that this program takes a non-integer number of seconds, rounds it to the nearest integer, then presents that value as three integers giving hours, minutes and seconds, respectively. There is no rounding function built into bc, so we must create one out of program statements. To do this, we must extract the integer part and fractional part of a decimal fraction. The following bc session (with comments) may be of some use in understanding the calculations in HMS. bc scale # built in variable giving number of digits of accuracy 0 # default value is 0 x=1.23456 # assign a value to variable x scale=4 # all calculations will now have 4 digits after decimal x/1 1.2345 # quotient by 1 is value of x to 4 digits x1.00006 # remainder by 1 is what's left over x 1.23456 # original value of x still has 5 digits scale=0 # back to default scale, which is 0 x/1 1 # 0 digits means we get the integer part x1.23456 # gives what's left over, i.e. the fractional part quit What to turn in Create a bash script that emulates the pa1 program Lawn.java. As before, it will prompt for and read the length and width of a lot, prompt for and read the length and width of a house on that lot, calculate and print out the lawn area (using 5 digits of accuracy), prompt for the mowing rate, calculate the mowing time in seconds (using 5 digits of accuracy), round that time to the nearest second, print out an equivalent number of hours, minutes and seconds. An example session with your script follows. Lawn Enter the length and width of the lot, in feet: 100.5 80.1 Enter the length and width of the house, in feet: 50 30.35 The lawn area is 6532.55 square feet. Enter the mowing rate, in square feet per second: 1.25 The mowing time is 1 hours, 27 minutes and 6 seconds. 4
Observe that you need not attempt to correctly pluralize your last line of output, unlike in pa1. Submit your shell script Lawn to the assignment name lab7 by the due date. Unfortunately there's not much time left for extensions on this one, so don't waste any time. 5