Difference between revisions of "CSC231 Bash Tutorial 8"

From dftwiki3
Jump to: navigation, search
(Bash Functions Returning Values)
(Challenge 3)
Line 244: Line 244:
 
<br />
 
<br />
 
<br />
 
<br />
 +
Create a new script with a function that will receive one argument, a string, and will return the number of lines in Ulysses.txt that contain the string.  If you have removed your Ulysses.txt file, you can use ''getcopy'' to get a fresh copy of it.
 
<br />
 
<br />
 +
Examples of use:
 
<br />
 
<br />
 +
 +
cs231a@aurora ~/handout $ ./funcChallenge3.sh chocolate
 +
The word chocolate appears 6 times in Ulysses.txt
 +
 +
cs231a@aurora ~/handout $ ./funcChallenge3.sh Buck
 +
The word Buck appears 167 times in Ulysses.txt
 +
 +
cs231a@aurora ~/handout $ ./funcChallenge3.sh wine
 +
The word wine appears 71 times in Ulysses.txt
 +
 +
cs231a@aurora ~/handout $ ./funcChallenge3.sh water
 +
The word water appears 224 times in Ulysses.txt
 +
 
<br />
 
<br />
 
<br />
 
<br />

Revision as of 14:08, 1 November 2017

--D. Thiebaut (talk) 13:13, 1 November 2017 (EDT)


Bash Functions


There are two ways of declaring functions in bash, illustrated in the code below:

#! /bin/bash
# func1.sh
# D. Thiebaut
# prints some messages

printSomething() {
    echo "Hello there!"
}

function printSomethingElse {
    echo "Hello again!"
}

printSomething
printSomething
printSomethingElse


  • Create the script above, make it executable, and run it, to see how it works.
  • Add a call to printSomething inside the printSomethingElse function, just to see if functions can actually call functions... Does bash accept nested calls?


Passing Arguments


  • Inside a function, $1 will refer to the first parameter passed to the function, $2 will refer to the second argument, etc.
  • You do not put the parameters inside the parenthesis, when declaring the function.
  • Here is an example, with both style functions:


#! /bin/bash
# func1.sh
# D. Thiebaut

printBar() {
    echo "------------------------"
}

function printName {
    echo "Hello $1"
}

printAge() {
    echo "Your age: $1"
}


printBar
printName "Kathleen McCartney"
printAge  61
printBar



Challenge 1

QuestionMark1.jpg


  • Add a new function to func2.sh called printInfo(). The new function takes 2 parameters and calls printName and printAge to print both. Here is an example of how to call it (that will be the only function call in the main part of the script):
     printInfo  "Kathleen McCartney" 61

and the output will be the same as the previous version of func2.sh:
------------------------
Hello Kathleen McCartney
Your age: 61
------------------------





Challenge 2

QuestionMark2.jpg


  • Below is an incomplete bash script that implements the teller machine script we saw earlier. It prompts the user for an integer, and takes the number as a dollar amount that is broken into a number of $20-bills, $10-bills, $5-bills and $1-bills. You need to replace the XXXXXX symbols by the appropriate expression(s)...


#! /bin/bash
# funcTeller.sh
# D. Thiebaut
# Gets a number from the user and breaks it down
# into a number of $20, $10, $5, and $1

if [ "$#" -ne 1 ] ; then
    echo "Syntax  $0 nnnn"
    echo "where nnnn is a positive dollar amount"
    exit 0
fi


amount=$1

function printBills {
    if [ XXXXX -ne "0" ]; then
       echo "$1 $2-bill(s)"
    fi
}
 
function breakAmount {   
    no20s=$( expr XXXXX / 20 )
    amount=$( expr  $amount % 20 )
    no10s=$( expr  $amount / 10 )
    amount=$( expr  $amount % 10 )
    no5s=$( expr  $amount / 5 )
    no1s=$( expr  $amount % 5 )

    printBills $no20s XXXXX
    printBills XXXXX "10"
    printBills XXXXX "5"
    printBills $no1s XXXXX
}

breakAmount $amount


  • Here is an example of how it works:


cs231a@aurora ~/handout $ ./funcTeller.sh
Syntax  ./funcTeller.sh nnnn
where nnnn is a positive dollar amount
 
cs231a@aurora ~/handout $ ./funcTeller.sh 1234
61 20-bill(s)
1 10-bill(s)
4 1-bill(s)

cs231a@aurora ~/handout $


Bash Functions Returning Values


Bash has a strange (weird ?) way of implementing functions returning values. Let's observe the following example, that will print the following output:

1  2
2  4
3  6
4  8
5  10

Here's the code for this script:

#! /bin/bash
# func3.sh
# D. Thiebaut
# display 5 ints and their double.

function doubleIt {
    return $( expr $1 \* 2 )
}

for i in 1 2 3 4 5 ; do
    echo -n $i " "
    doubleIt $i
    echo $?
done


  • The way the code above works, is that you call the function doubleIt first, passing it $i, then on the next line, you use $? to access the returned value of the function. $? is the standard way bash accesses the status of the previous command that was executed, or the previous function that was called. We will always use $? to access the value returned by the function called on the previous line. That's the way bash works.



Bash function can only return integers! They cannot return strings or other quantities that could be useful... :-(



Another Example


The script below returns the number of ".asm" and ".sh" files contained in your current directory:

#! /bin/bash
# func4.sh
# D. Thiebaut
# return the number of files with a given extension

function countFiles {
   num=`ls *.$1  2> /dev/null | wc -l `
   return $num
}

countFiles "asm"
echo "Number of asm files: " $?

countFiles "sh"
echo "Number of bash scripts: " $?

for ext in "o" "c" ; do
    countFiles $ext
    echo "Number of files with $ext extension: " $?
done


output


cs231a@aurora ~/handout $ ./func4.sh
Number of asm files:  34
Number of bash scripts:  10
Number of files with o extension:  3
Number of files with c extension:  0

  • Note the redirection to /dev/null in the function; this is something new. By using "2> /dev/null" we are sending all error messages generated by the ls command to /dev/null, which on Linux systems is a file that has always length zero. You can copy or redirect huge outputs to /dev/null, and it will absorb everything without ever growing. Think of it as a trash can that incinerate everything you put in, making the trash can always empty. I used this trick because when there are no files with the given extension, the ls command generates an error message, like this one:
ls: cannot access *.c: No such file or directory

Sending the error messages to /dev/null makes the output look neater.



Challenge 3

QuestionMark3.jpg



Create a new script with a function that will receive one argument, a string, and will return the number of lines in Ulysses.txt that contain the string. If you have removed your Ulysses.txt file, you can use getcopy to get a fresh copy of it.
Examples of use:

cs231a@aurora ~/handout $ ./funcChallenge3.sh chocolate
The word chocolate appears 6 times in Ulysses.txt

cs231a@aurora ~/handout $ ./funcChallenge3.sh Buck
The word Buck appears 167 times in Ulysses.txt

cs231a@aurora ~/handout $ ./funcChallenge3.sh wine
The word wine appears 71 times in Ulysses.txt
cs231a@aurora ~/handout $ ./funcChallenge3.sh water
The word water appears 224 times in Ulysses.txt




Solutions


Challenge 1


#! /bin/bash
# funcChallenge1.sh
# D. Thiebaut

printBar() {
    echo "------------------------"
}

function printName {
    echo "Hello $1"
}

function printAge {
    echo "Your age: $1"
}

function printInfo {
    printBar
    printName $1
    printAge $2
    printBar
}

printInfo "Kathleen McCartney" 61


Challenge 2


#! /bin/bash
# funcTeller.sh
# D. Thiebaut
# Gets a number from the user and breaks it down
# into a number of $20, $10, $5, and $1

if [ "$#" -ne 1 ] ; then
    echo "Syntax  $0 nnnn"
    echo "where nnnn is a positive dollar amount"
    exit 0
fi


amount=$1

function printBills {
    if [ "$1" -ne "0" ]; then
       echo "$1 $2-bill(s)"
    fi
}
 
function breakAmount {   
    no20s=$( expr $1 / 20 )
    amount=$( expr  $amount % 20 )
    no10s=$( expr  $amount / 10 )
    amount=$( expr  $amount % 10 )
    no5s=$( expr  $amount / 5 )
    no1s=$( expr  $amount % 5 )

    printBills $no20s "20"
    printBills $no10s "10"
    printBills $no5s "5"
    printBills $no1s "1"
}

breakAmount $amount