This lesson you'll learn how to use functions in your scripts.
First off a function is simply a group of commands that you call using a single name in your script.
You can think of functions as being little scripts inside of your main script.
You can call or execute a function just like you would any other script or command on a Linux system.
There are a couple of reasons why you would want to use a functions.
The first reason is that you don't want to have duplicate code in multiple places in your script using
functions allows your code to remain dry dry.
Is a computer programming an application development principle that stands for.
Don't repeat yourself.
Functions allow you to write a block of code once and use it many times instead of repeating several
lines of code each time you need to perform a particular task simply call the function that contains
that code.
This helps in reducing the length of your scripts and also gives you a single place to change test troubleshoot
and document a given task.
All of this makes your scripts easier to maintain.
By the way there is a counter principle called wet which stands for write everything twice or we enjoy
typing or waste everyone's time.
If you catch yourself typing out this same bit of code or you find yourself copying and pasting existing
code in your script it's time to dry up that wet code and use a function.
The second reason to use functions is to break up large tasks into a series of smaller tasks.
This makes your code easier to maintain.
You can zoom into a function that only does one small thing and make your changes around that small
thing there.
And that one place.
For example if you are using a case statement to make a decision then instead of writing a whole long
block of code inside the case statement you could call a function that actually does the real work.
All right so enough philosophizing.
Let's get to work and start creating some functions.
So first off here as always I have my terminal open.
I'm going to change into our class folder of shell class.
We're still working on the locals.
Users of vagrant project.
And now I'm going to bring up this VM with vagrant up.
One way to create a function is to use the function built in command.
Here you start with the word function.
Give your function a name then surround a series of commands inside curly braces.
The other way you can create a function is to give your function a name.
Follow it with a set of parentheses then surround a series of commands inside curly braces.
Now let's jump into a script and try it out.
Let's create a function called log that will end up logging messages to the system log and optionally
displaying those messages to the user executing the script will iterate over this bit of code and keep
going as we go but we'll start out here will create a function by providing the name.
Then following that name with parentheses and then we're going to create a block of code surrounded
in curly braces here and we'll just do this for now.
Echo you call the log function and end it with a closing curly brace.
Now a color will just use the word log and that will execute it just like any other command on a Linux
system.
Now remember that scripts get read and executed from the top down.
That means you have to define a function before you can use it.
That's why we define the log of function at the top of your script here.
When a function is defined it is read into memory and it's then available for use.
The code in the function is just remembered initially and it's only executed when it gets called.
Notice that when we called the function we didn't use the parentheses.
You may have seen this type of syntax and style and other programming languages where you actually use
the parentheses but it doesn't work in shell scripts.
Simply place the name of a function on a line and it will execute that function.
So let's try it out.
Hey sure enough we get the output that says you call the log function.
Hey that's not very interesting yet but we'll get there soon enough so let's get back into our script
here.
I want to show you the other way you can define a function so how you can do that is use the keyword
function.
All that by your function name.
And then you don't need the parentheses here and will save our changes and test this as well.
So as you can see it also works so either method works.
I'm going to go back and use this particular style here.
Now let's make our log function display to the screen whatever is passed to it.
So I'll type out the code here and then we'll talk through it in just a second.
So what we're going to do here is this.
I've introduced a new show built in called Local what local does is makes a variable local in scope
to the function.
That means the value of that variable is only accessible inside that function.
So in this example if you tried to display the contents of the message a variable before or after the
function it would be blank and unset.
That's because it only exists inside the function.
By the way the local command can only be used inside a function that's the only place it makes sense.
And the reason why is that until now we've been using global variables and that means that the variable
and its contents or its value can be accessed anywhere in the script including in any function.
Now we haven't been using functions until now so it really didn't matter until this point.
By the way if you define a global variable in a function by omitting the local keyword in front of it
that global variable is not available outside that function until the function is called and executed
it's not a best practice to do that anyway so it shouldn't really be an issue.
However when you are using functions just be aware of the scope of each variable and know that it can
be a place that can deserve some attention when troubleshooting your scripts.
It's a best practice to use local variables inside your functions.
That way you won't accidentally reuse the same variable name in another function and end up with a tricky
bug.
However you'll find plenty of shell scripts that do not add here to this recommendation.
Most of the time it will not cause a problem.
Just be sure to use unique variable names throughout your script and you'll be fine.
Now let's talk about the value that is assigned to the local variable named message.
It's our old friend dollar sign and sign back from our positional parameters lesson as you recall a
dollar sign at sign expands to all the positional parameters starting from one functions act like a
script within your script and you can accept arguments which can be then accessed as positional parameters
inside the function.
For example the first argument passed to a function is dollar sign one.
The second is Dollar Sign 2 and so on.
The only difference between a function and a script here is that dollar sign is zero is still the name
of the shellscript itself and not the name of the function.
Even if you're using dollar signs zero in the function.
And that's really OK anyway because you'd never really want to use that functionality anyway.
OK let's try this bit of code out.
When we execute our script the function is called first time with Hello it print the screen the second
time the function is called with.
This is fun and that also gets displayed to the screen.
Now let's update our function to only display the message that was passed to it.
If a global variable named verbose is set to true
so will check to see if both is true and if it is then will echo that message to the screen and the
ephi there closes out our IF statement.
So let's do this let's call our function log with hello and then let's go ahead and now set verbose
equal to true.
So the first time we call a function rebozo is not set the second time we call it.
It is set to true.
Now this function is actually starting to become useful if we didn't have this function then we would
have to test for the value of the verbose variable every single time we needed to decide if we wanted
to display something to the screen or not.
This means we would end up with a lot of duplicate code throughout our script.
In any case what should happen is that the text below will not be displayed to the screen because verbose
has not been set to true at that point and the text this is Vonne will be displayed because as I just
explained verbose is set to true before log is called with that particular text passed into it.
So let's try that out.
Hey sure enough Hello was not displayed to the screen but this is fun.
Was just as we expected.
If you've ever had any formal training in computer programming you've probably heard that global variables
are evil and you should avoid them at all costs.
Really what you should try to avoid at all costs are the problems that can arise by using global variables
such as different functions changing the value of a global variable and so on.
Now I'm a pretty pragmatic person so I like to do what is best for a given situation.
In that vein let me give you an example of why avoiding global variables can be less than ideal especially
with our little use case here.
We could change this function such that it doesn't rely on the verbose global variable and instead make
it an argument that has to be passed into the function.
So what we can do is this.
We can add local verb Bowse and set that to the value that is passed into it here.
The first value that's passed into it then we'll use shift here and then at that point every thing else
stays the same.
So what this does is assigns the first value passed into the function into the local variable named
verbose.
Then the shift command shifts all those positional parameters which removes the value stored in Dallas
and one makes dollar sign to dollar sign.
One makes the dollar sign three dollar sign too and so on.
So more or less we chop off the value we assigned to verbose and what we're left with is the message
that was passed into the function.
Now we have to update how we call this function.
So here we're going to do log true to tell it that we're going to be verbose and we can get rid of this
piece of code here.
And we're also going to do this we'll say true here as well.
Now every time we use the log function we need to tell it if we want verbose to be true or false.
Of course we could set a variable and use that like this so we can do this we could say verbosity is
true and then we'll do this
OK.
In practice the verbosity is going to remain the same for our entire script because of that.
I could argue that passing it into the function every single time is causing us to repeat ourselves
without any real practical gain.
In this example I think using a global variable is fine.
However if you disagree with me that's also fine in my opinion.
Both methods are correct.
So long as they get the job done successfully.
Now let's just show that this little piece of code works so we'll see where changes and execute our
script and sure enough it executes the function and displays what we passed to it to the screen if we
were doing something like differentiating informational messages from morning messages from critical
messages then passing in that log level would make sense because it can change throughout her script.
So again use what works and what makes sense to you.
Just make sure that it actually works.
Test your scripts.
Now let's revert our code and handle the main concern of using global variables within functions which
is that a function might actually change the value of that global variable.
So we'll do this
the read only shell builtin makes a variable unchangeable or an modifiable.
This is the shells version of a constant variable which is a term you might be familiar with if you've
programmed in other languages.
There are two ways to use the read only shell built in.
One is just like we have it here on the screen which is to use the word read only.
Followed by the variable and then include the variable assignment there right after it.
The other way is to perform the variable assignment first and then use Read-Only followed by just the
variable name.
There's no need to include the assignment because it's already performed at that point.
This way you can separate the setting of the variable and marking it as read only.
Now that the verbose variable is read only it cannot be changed for the duration of the script.
That means it cannot be changed inside or outside of a function.
That eliminates a concern about a function changing the value of a global variable.
OK we've taken care of the first bit of our function that controls whether or not we display a message
to standard output.
Now let's write the last little piece that will control sending messages to the system's log.
To do that we'll use a command line utility named logger.
Let's actually take a look at that on the command line.
OK.
Logger is a executable so we can run man against that to get some information about that particular
command.
Mainly you just supply logger a message and that message is recorded and the systems log by default
on a Sentosa rail system.
Those messages will go to the var log messages file.
You can read all the details here if you like but I'm just going to point out the one bit of information
we're going to use most immediately and that is the dash t option which allows you to tag your Ciss
log message on the move down here to Dashti this tag is typically set to the name of your program or
the program that is writing to the system log file.
So let's try that out now.
Q To get out of this help and we'll do this longer.
From the command line.
All right.
And we want to look at the var log messages file and that file happens to have permission such that
we need to read that file.
And as you can see there the last line of that log file is what we sent to it with the logger command.
The format of these Assis log messages is a time stamp followed by the computer name followed by the
tag which is again most often the name of the program writing to this log.
In this case it's our username since we didn't specify a tag.
And finally the message itself.
So this time let's add a tag will do logger Dashti my script.
Tagging on.
So this time you can see that the tag reads my script and then we see the message that we passed into
that.
By the way since law can be configured to send messages off the server and to a centralized assist log
location for example.
Now I cover the details of exactly how to do this in my Linux in the real world course.
Anyway that is a good security measure because if someone were to have root access to one system in
the logs that they generated are not only stored locally where they could potentially change them or
delete them but they're also stored on a remote system where hopefully that person or that attacker
hasn't broken into that system as well.
It's just something to keep in mind it would be a reason to use the standard syslog system built into
Linux instead of for example creating your own logging service system from scratch.
OK let's add this longer functionality to our script
logger Dashti give it the name of our script.
And then we'll send the message to the logfile.
Now regardless of whether the message is displayed on standard out it will get recorded in the system
log.
So let's try it out here.
When we execute our script our messages are displayed to the screen.
Now let's see if they made it to the log file.
And sure enough they have no it's turned verbose mode off and see what that's like.
OK let's change this from true to false and execute our script.
So there's no output to standard output there's nothing on our screen.
Let's see if it made its way to the log file however
Kay says hello and this is fun so let's do that again here and then.
Yup.
It keeps adding to the log file so that is working.
Now let's go ahead and turn verbose mode back on
before we write our next function.
Let's add a quick comment to the top of this function explaining what it does.
Now we do this for our scripts and so our functions are like little scripts within a script.
So let's do that here to.
So I'll go here and say
this function sends a message to this log and to standard output if verbose is true.
Hey if you wanted to add more information you can do that.
Some people like to include things like any global variables that are used inside the function or by
the function.
Any arguments that the function accepts and anything the function might return.
Now we haven't covered returns yet but we'll get to that soon enough.
Moving on to our next to function as a reminder functions have to be defined before they are used.
So a good practice is to put all your functions at the top of your script.
The only things I would consider putting before the functions are constant global variables and sourcing
script libraries again do not call a function before it's defined.
Let's say you have a script that is going to update several files.
You've lived a life long enough to know that sometimes things go wrong.
So you want to make a backup of every single file before you modify it.
Or more accurately before your script modifies it.
That way if you have an error in your script or something unexpected happens you have a good copy of
each file to go back to.
So let's call this particular function back up underscore file
only to come in here.
All right we'll get to this return statement here in just a second.
We're going to use a variable in our function so we'll make that local.
We'll do a quick check to see if that file got passed and exists.
Again we're setting a variable so we're going to use the local keyword here
looks like I miss my clothing parentheses here.
I mean do that.
You're probably already familiar with the temp directory.
However here I am using forward slash var forward slash temp because the files in var temp will survive
a reboot while those in slash temp are not guaranteed to survive a reboot.
Typically the files in temp are cleared on boot and are also cleared more often on a running system.
I think the default for Scinto 7 is that files and temp are deleted every 10 days while the files in
var temp are deleted every 30 days.
That said I want to make sure that if I break a file and then do something like reboot the system that
I don't lose that file.
Hence the reason for me choosing the location of forward slash var Ford slash temp.
All right continuing with our variable here.
As you know the base name command removes the path to the file and just leaves us with a file name and
we don't care about its original path because we just going to put a copy of it in var temp anyway.
Also here we're using command substitution with the date command and you've used the date command with
a couple of different formats before.
But now I'm introducing the capital F format now capital F you can think of is standing for a full date
and it displays the year month day using numbers.
I love using this format because it makes sorting by date very easy once we call this function a couple
times you'll see exactly what I mean.
Also I attempted to account for multiple backups of the same file on the same day by appending the nanoseconds
when the date command was called that's represented by the percent sign followed by capital N..
We could have used a shell built in variable here to something like Dollar Sign random or dollar sign
bash PID or even Dollar Sign Dollar Sign which expands to the PID or process id of the current running
script.
So now in this function let's call our other function here.
So after we use the log function to record what we're going to do here or communicate our intentions
here then we're going to use the C-p command to actually perform a copy.
Now the dash option to the copy command is short for preserve and it preserves the files mode ownership
and time stamps.
If you don't use the dash option then the copy of the original file will have the current time stamp.
I personally like to preserve the original time stamp on the copy just in case I need to put that copy
back in place because it's a backup.
The contents of that file haven't changed so in my opinion it makes sense to keep its time stamps the
way they were instead of updating the time stamp to today.
In the grand scheme of things this is really minor but it's just something to think about each function
like each command returns an exit status by default.
The function returns the exit status of the last command it executes in the case where the file that
was passed to the function actually exists.
We'll just let C-p return whatever exit status is appropriate and use that as the exit status for our
function.
If for some reason the CB command fails it will return a non-zero exit status and thus our backup file
function will return that same non zero exit status.
Here if the file doesn't exist that means we obviously can't make a copy of it a backup copy of it.
So our function more or less fails and that's why we're going to return a non zero exit status.
Now let's go ahead and call this function in our code here.
A backup file let's back up the NC password file and lets see it in action.
K we have the output displayed to our screen because we have verbose mode on and then we also see the
output that was generated inside the backup file function which in turn call the log function to tell
us about the backup that it made.
So let's see if these are messages made it to the log file.
Hey sure enough they have.
Now we have a record of exactly what happened during that run of that script and exactly when it happened
and all that good information there is a look at the file we copied here into var temp.
You can see that the file that was in the log message here is this file here.
Obviously I was playing around with this script yesterday doing some tests and so that's why we have
these other password files there.
Now let's look at ANSI password know and see what happens when we execute the copy command with out
the dash options or do C.P ETSI.
He is a W.D. and will copy that into var temp and we'll look at the contents of our temp.
Now right now the date is January 12th and the date of that file is also January 12th.
But if you look at its password it's really January 7th.
It's the original time stamp on that file.
So again that was the difference with the dash p option.
OK we're getting really close to the end.
Just one more little block of code.
So let's jump back into our script.
So as we discussed earlier a function has a return code or exit status just like any other command on
a Linux system.
Now let's check that status.
So let's do it in the positive if the return code is zero.
That means it's success so that we can do then is say hey the file backup succeeded.
And then if it was not 0 then we'll say while backup failed and we'll exit the entire script there.
So again if the backup function succeeds we're going to get that log file messages as file backup succeeded
if it fails will log a file backup failed and exit the entire script itself with a non-zero exit status.
Now by the way inside a function it's super important to use return and not exit if the exit command
gets executed it exits the entire script no matter if that exit command is inside a function or not.
So exit.
Remember the exit is for the entire script.
Return is just for the function.
So now let's try this little extra code out
right it looks like our backup succeeded.
Let's look at the messages file.
OK.
Our file backup succeeded message ended up in the messages file to if we were to pass the backup file
function in a file that didn't exist we would trigger an error or if the disk was full or if the file
system was in a read only state when the backup was attempted it would also trigger an error state and
exit the script.
By the way this bit of code was just to show you that you can check the exit status of a function just
like you can any other command.
However in this particular case you would want to check if the backup succeeded or not every single
time you performed a backup.
And that means in practice you would most likely add this particular check or this bit of code inside
the backup file function itself.
If you wanted to over engineer this thing you could totally create three functions to do the same bit
of work.
One function would perform the backup.
One would be use to check if the backup actually succeeded in a third function would be the one that
you would actually call in your script which would in turn call the other two functions it would be
like a wrapper around those functions and it would look something like this.
So we'll just declare a function here backup file and let's say we call our other function that actually
does the backup and we'll call it perform back up and we'll just pass that right along here.
Then we want to check the backup.
This is actually going to fail when I call it because those other functions don't exist right we just
kind of made this up on the fly so I'll do this backup file and see password for example.
And sure enough when we call it it executes to perform backup but that command isn't found because again
it doesn't exist.
And then it calls check back up again it doesn't exist.
So anyway as you might have noticed you can define a function at the command line like I did right here
and you really already know this spinel right.
A shell script is just automating what you can do at the command line anyway.
So let's let's look at this type mâché back up on score file.
It tells us it has a function and it displays the code that is contained in that function or that function
definition.
This way you can write functions and have them write inside your current running shell.
I feel like we've covered a lot about functions don't you.
Before we wrap this up let's do a little refresher of the main points about working with functions in
this lesson you learned about the concept of dry which stands for don't repeat yourself.
If you find yourself writing the same bit of code in multiple places in your script it's a good sign
that you should use a function.
You also learned that by default all variables are global in scope and you learn how to use the local
keyword to define local variables that are only available within the function that they are defined
in.
You also learned how to use positional parameters within your functions.
And finally we covered how to use EXIT statuses with your functions by using the return command.