CSC231 Bash Tutorial 7
--D. Thiebaut (talk) 06:15, 31 March 2017 (EDT)
Updated --D. Thiebaut (talk) 20:15, 25 October 2017 (EDT)
This lab deals with if-statements in bash scripts. If statements in Bash work the same way they work in Python and Java: if somebooleanexpression then dothis else dothat.
You will need to submit your solution to Challenge 5 to Moodle. |
<showafterdate after="20171027 10:00" before="20171231 00:00">
Reference
- This Bash tutorial heavily uses material presented in the excellent set of pages "Ryan's Tutorials" at http://ryanstutorials.net/bash-scripting-tutorial/. Do not hesitate to study these tutorials if you need additional information.
Backing up your files
Just in case you mess up and erase files in your account by mistake, you will make an archive of all your files and save it in your instructor's account:
cd tar -czvf backup2.tgz * rsubmit backup backup2.tgz
That's it! An archive of all your file should now be saved and available in case of accident!
The Bash If-Statement
The Bash if-statement looks like this:
if [ someExpression ] ; then bash statement bash statement else bash statement bash statement fi
Of course, there can be as many bash statements inside the then or else block, including for loops, or other if statements.
- Make sure to end the if statement with a fi keyword.
- The else block is not necessary and can be omitted if the logic doesn't call for it.
Testing Numerical Values
- Create this bash script, make it executable, and run it a couple times:
#! /bin/bash # if0.sh #--- get input from user --- echo -n "Please enter an integer between 1 and 10 (included): " read guess #--- display number back --- echo "You have entered $guess"
- Let's modify it and test the value the user enters:
#! /bin/bash # if0.sh #--- get input from user --- echo -n "Please enter an integer between 1 and 10 (included): " read guess #--- test if number entered is below or above 5 --- if [ "$guess" -le "5" ] ; then echo "You aim low!" else echo "Above average!" fi
- Make sure you have spaces around the brackets and around the operator inside the brackets!
- The "-le" operator stands for "less than or equal to." Bash supports the following operators for comparing numbers:
- -le (less than or equal to)
- -lt (less than )
- -ge (greater than or equal to)
- -gt (greater than)
- -eq (equal to)
- -ne (not equal to)
Challenge 0 |
- Modify the teller.sh script below, so that it outputs a number of bills only if this number is greater than 0.
#! /bin/bash #set -x amount=$1 no20s=$( expr $amount / 20 ) amount=$( expr $amount % 20 ) no10s=$( expr $amount / 10 ) amount=$( expr $amount % 10 ) no5s=$( expr $amount / 5 ) no1s=$( expr $amount % 5 ) echo "" echo $no20s " \$20-bill(s)" echo $no10s " \$10-bill(s)" echo $no5s " \$5-bill(s)" echo $no1s " \$1-bill(s)"
- Here is an example of how your program should work:
cs231a@aurora ~/handout $ ./teller.sh 123 6 $20-bill(s) 3 $1-bill(s)
Nested If-Statements
- Of course, if Bash supports if statements, it will support nested if statements.
- Try this new version of the script:
#! /bin/bash # if2.sh #--- get user input --- echo -n "Please enter an integer between 1 and 10 (included): " read guess #--- test if below or above 5 --- if [ "$guess" -le "5" ] ; then #--- compute even/odd property of guess x=$( expr $guess % 2 ) #--- test if guess is odd or even --- if [ "$x" -eq "0" ] ; then echo "low even number" else echo "low odd number" fi else echo "Above average!" fi
Challenge #1: |
Write a script that prompts the user for an integer number between 0 and 100 (inclusive) and outputs a letter grade, using Moodle's default grade table:
Range | Letter Grade |
---|---|
100.00 -93.00 |
A |
92.99 - 90.00 |
A- |
89.99 - 87.00 |
B+ |
86.99 - 83.00 |
B |
82.99 - 80.00 |
B- |
79.99 - 77.00 |
C+ |
76.99 - 73.00 |
C |
72.99 - 70.00 |
C- |
69.99 - 67.00 |
D+ |
66.99 - 60.00 |
D |
59.99 - 0.00 |
F |
Note 1: You do not have to worry about real numbers here. You can do all your testing with integers.
Note 2: Feel free to test just the top 3 ranges of numbers. If you can do it for 3, you can do it for the full list!
Testing and Comparing Strings
Bash uses different operators for strings: == for equality, and != for inequality. Here is an example:
#! /bin/bash # if3.sh for name in "Smith" "Hampshire" "Umass" "Amherst" "MtHolyoke" ; do if [ "$name" == "Smith" ]; then town="Northampton" suffix="College" fi if [ "$name" == "Amherst" ]; then town="Amherst" suffix="College" fi if [ "$name" == "Umass" ]; then town="Amherst" suffix="" name="University of Mass." fi if [ "$name" == "MtHolyoke" ]; then town="South Hadley" suffix="College" fi if [ "$name" == "Hampshire" ]; then town="Amherst" suffix="College" fi echo "$name $suffix, $town, Massachusetts" done
- Try it!
- You will have noticed that the then-blocks for "Amherst" and "Hampshire" are the same, so we could simplify our code by using a logical OR operator for "Amherst" and "Hampshire". The syntax for logical operators in Bash is a bit weird, though... logical:
if [ "$name" == "Amherst" ] || [ "$name" == "Hampshire" ]; then town="Amherst" suffix="College" fi
- The OR operator is || and the AND operator is &&.
Challenge #2: |
Modify the previous script, noticing that "Smith", "Amherst", "Hampshire", and "MtHolyoke" all have suffix College, and therefore one if statement with a then and else block can be used to set the suffix. You will need other if statements for the towns.
Making Bash Scripts Friendlier
When you write a script that expects some parameters on the command line, it is a good idea to tell the user if she enters the wrong number, and to display the correct syntax for using the script. Here is an example of a friendly script that expects the names of 3 fruits on the command line. The first time the user doesn't enter anything, the second time she enters 3 strings:
cs231a@aurora ~/ $ ./if5.sh Syntax: ./if5.sh fruit1 fruit2 fruit3 cs231a@aurora ~/ $ ./if5.sh banana apple pear You entered the right number of parameters 1) banana 2) apple 3) pear
- And here is the script that does that:
#! /bin/bash # if5.sh # D. Thiebaut # Test if shell file executed with the appropriate # number of parameters if [ "$#" -ne "3" ] ; then echo "Syntax: $0 fruit1 fruit2 fruit3" exit fi echo "You entered the right number of parameters" echo "1) $1" echo "2) $2" echo "3) $3"
- Create and test this script a few times.
- Modify it so that it will work only if the user enters 5 different parameters on the command line (remember to change the syntax part as well!)
Challenge #3: |
- Modify the script that you wrote for Challenge 1, that gets a number grade from the user, so that it now gets it directly from the command line. Make your script test that the user has actually entered a grade, and if not, make your script output the correct syntax.
Testing Files
Because Bash interacts directly with Linux, it has a rich set of operators for testing files, and various file properties. See the list of operators on this page from tldp.org for a list of all of them.
Let's try one, and list all the executable files in your directory. The operator -x operator, if applied to a file, will return True if the file is executable (the files generated by ld and the files you applied chmod -x to).
You can probably figure out how to write this script, but here it is:
#! /bin/bash # if4.sh # prints executable files only # for file in * ; do if [ -x $file ] ; then echo $file fi done
- Test it in your home directory.
- Verify that it is listing all your executables.
Challenge #4: |
Given the name of a file, say in variable $file, Bash can extract its extension with this (strange looking) expression: ${file##*.}
- See how it works by trying this script in your main directory:
#! /bin/bash # challenge3_prep.sh # D. Thiebaut # displays the files and their extension in the current directory # for all files in the current directory for file in * ; do # get its extension extension=${file##*.} # print extension and file-name echo "$extension -- $file" done
- Your challenge: write a script that will list all the files in your directory, divided into 3 sections: asm files,
object files (with a .o extension), and scripts (with a .sh extension). Below is the output of the solution program
running in my handout directory:
--- o --- GameOfLife.o --- asm --- 231Lib.asm addmovExample.asm fib.asm GameOfLife.asm GameOfLifeDT.asm mystery.asm newFile.asm printRegsExample.asm prog1.asm prog2.asm prog3.asm prog4.asm quiz1.asm skel.asm teller.asm test_skel.asm --- sh --- dateTag.sh randomWord2.sh randomWord3.sh randomWord.sh runNQueens.sh runTeller.sh script1.sh script2.sh script3.sh teller.sh test1.sh
Challenge #5: |
Computing Fibonaccis: Write a shell script that prints all the Fibonacci terms less than 100. We haven't see the while loop in bash yet but you can use a for-loop that goes a large number of times, larger than the number of Fibonaccis less than 100. Then, when you have found the current Fibonacci term to be greater than 100, just exit the script.
for i in `seq ... ` ; do # figure out what expression to replace the ... with! if [ ... ] ; then # if term greater than 100 exit 0 # then exit with code 0 fi done
Note 1: Remember that you can add 2 bash variables together with $( expr $var1 + $var2 ).
Node 2: Here's a Python version of what you need to do:
#! /usr/bin/env python3 # fibonacci.py # D. Thiebaut # prints first 10 Fibonacci terms, # starting with 1, 2, 3... from __future__ import print_function fibn = 1 fibn_1 = 1 print( fibn ) print( fibn_1 ) for i in range( 100 ): # pass down values fibn_2 = fibn_1 fibn_1 = fibn # update fibn fibn = fibn_1 + fibn_2 if fibn > 100: sys.exit() # print fibn print( fibn )
Moodle Submission
- Submit your submission to Challenge 5 to Moodle. Your script should output one number on each line. The output will start with 1, 1, and end with 89. You have until Saturday 10/28/17 noon time to submit your script.
</showafterdate>
<showafterdate after="20171027 11:45" before="20171231 00:00">
Solutions to Challenges
- Challenge 0
#! /bin/bash # teller.sh # D. Thiebaut # takes the integer given on the command line and breaks it # down into a number of $20s, $10s, $5s, and $1s. amount=$1 no20s=$( expr $amount / 20 ) amount=$( expr $amount % 20 ) no10s=$( expr $amount / 10 ) amount=$( expr $amount % 10 ) no5s=$( expr $amount / 5 ) no1s=$( expr $amount % 5 ) echo "" if [ "$no20s" -ne "0" ] ; then echo $no20s " \$20-bill(s)" fi if [ "$no10s" -ne "0" ] ; then echo $no10s " \$10-bill(s)" fi if [ "$no5s" -ne "0" ] ; then echo $no5s " \$5-bill(s)" fi if [ "$no1s" -ne "0" ] ; then echo $no1s " \$1-bill(s)" fi
- Challenge 1
#! /bin/bash # challenge1.sh # D. Thiebaut # get integer grade echo -n "Enter the grade (0-100): " read grade if [ "$grade" -gt "93" ]; then echo "A" else if [ "$grade" -gt "90" ]; then echo "A-" else if [ "$grade" -gt "87" ]; then echo "B+" else if [ "$grade" -gt "83" ]; then echo "B" else echo "Grade lower than B" fi fi fi fi
- Challenge 2
#! /bin/bash # Challenge2.sh # D. Thiebaut # displays the name of the 5 colleges and where they are located for name in "Smith" "Hampshire" "Umass" "Amherst" "MtHolyoke" ; do if [ "$name" == "Amherst" ] || [ "$name" == "Hampshire" ] || [ "$name" == "Umass" ]; then town="Amherst" else if [ "$name" == "Smith" ]; then town="Northampton" else town="South Hadley" fi fi if [ "$name" == "Smith" ] || [ "$name" == "Amherst" ] || [ "$name" == "Hampshire" ] \ || [ "$name" == "MtHolyoke" ] ; then suffix="College" else name="University of Mass." suffix="" fi echo "$name $suffix, $town, Massachusetts" done
- Challenge 3
#! /bin/bash # challenge3.sh # D. Thiebaut # Test that we got a parameter, else exit if [ "$#" -ne "1" ]; then echo "Syntax: $0 grade" exit fi # save command line param in $grade grade=$1 if [ "$grade" -gt "93" ]; then echo "A" else if [ "$grade" -gt "90" ]; then echo "A-" else if [ "$grade" -gt "87" ]; then echo "B+" else if [ "$grade" -gt "83" ]; then echo "B" else echo "Grade lower than B" fi fi fi fi
Challenge 4
#! /bin/bash # listFilesByExt.sh # D. Thiebaut # list the object, asm, and script files found in the # current directory for extension in o asm sh ; do echo "---" $extension "---" for file in `ls *` ; do ext=${file##*.} if [ "$ext" == "$extension" ] ; then echo $file fi done echo "" done
</showafterdate>
<showafterdate after="20171028 12:00" before="20171231 00:00">
Moodle Challenge
#! /bin/bash # fibonacci.sh # D. Thiebaut # display the first terms of the Fibonacci series # and limits itself to the terms less than 100. # initialize fibn fibn_1fibn_2 fibn=0 fibn_1=1 fibn_2=1 # display first two terms echo $fibn_2 echo $fibn_1 # loop 100 times for i in `seq 1 100` ; do # compute nth term as sum of previous 2 fibn=$( expr $fibn_1 + $fibn_2 ) # stop if term is greater than 100 if [ $fibn -gt 100 ] ; then exit 0 fi # display term if less than 100 echo $fibn # advance to next term in the series fibn_2=$fibn_1 fibn_1=$fibn done
</showafterdate>