Linux Shell Scripting
The course is part of these learning paths
In this course, we will explore how to set up a network of virtual machines and how to implement SSH key authentication and execute commands on remote systems. We'll look at how to install and remove software from local and remote systems. You'll learn about continue and break statement and their benefits and use cases. You learn how to automate processes on Linux through the use of cron jobs and examine running processes.
This course is part of the Linux Shell Scripting learning path. To follow along with this course, you can download all the necessary resources here.
- Learn how to create a network of virtual machines and how to configure SSH key authentication and execute commands on remote systems via SSH
- Learn how to install and remove software packages both on your local system as well as on remote systems
- Understand continue and break statements in loops and what they're used for
- Understand what cron is and how to use it to schedule the running of scripts in Linux at various intervals
- Learn how to examine running processes on a Linux system and how to determine their process IDs
- Anyone who wants to learn Linux shell scripting
- Linux system administrators, developers, or programmers
To get the most out of this course, you should have a basic understanding of the Linux command line.
So far in this course, you've been running the scripts you've written manually by hand but there are times when you need a shell script to run on its own, without anyone there to start it. For example let's say you have a script that restarts a process when it's down. You can either update your script so that it works like a service by creating an infinite loop and configuring that script to start on boot. That is one way to handle it. However, another way would be to run the script let's say every few minutes, for example every five minutes. So at the top of the hour it would run, do its checks, restart any failed services if it needed to, and the script would then stop the execution, then five minutes after the hour it would do it all over again, then 10 minutes after the hour and so on every five minutes. Now that's the perfect use case for using a scheduler or using a scheduling service. The built-in and most popular job scheduler on Linux is cron. It's fairly easy to configure and use, plus it's flexible. Not only can you make a script run every few minutes or every hour, for example you can run a command every Monday at 7:00 a.m or other time schedules of your choosing. You tell cron when and what to do by using a crontab, which is short for cron table. The crontab is made up of six columns. The first five being time specification fields and the last column, the command to execute. The first column is used for minutes, the second four hours, the third for day of the month, the fourth four month and the fifth for day of the week. In practice, most cron jobs run once a day, once an hour or once every few minutes. Now that means you typically only have to worry about the first two columns. The specified command will only be executed when all of the time specification fields match the current date and time. Typically one or more of the time specification fields will contain an asterisk which matches any time or any date for that field. If you want the usr/local/bin/run-backup.sh script to execute every morning at 3:00 a.m, set the minutes column to zero and the hour column to three then use asterisks for the remaining columns. So when the hour is three and the minutes is zero usr/local/bin/run-backup.sh is executed. If you want something to execute at the top of every hour, just use zero in the first column and asterisks for the rest of the time fields. If you want to run a job every five minutes use asterisk/five in the first column and fill in the remaining time fields with an asterisk. The Ford slash is used to specify step values. So asterisk Ford/five means every fifth minute. By the way, any output generated by a command via cron is emailed to the owner of that cron job. Now that's local email on the Linux system and not internet email. If you want to forward email off of your Linux system you can do that outside of cron. What I typically do for cron jobs is have them write all of their output to a file using redirection. That's demonstrated on the last example here on your screen. Let's go ahead and write a simple little shell script and execute it via cron and see how it behaves. So here I'm on the admin01VM and I'm going to move into the vagrant shared directory and then I'm going to go ahead and create our script. I'm gonna name it multinet-demo04.sh. Of course I need a shebang. What I'm going to make this shell script do is display the current time on standard output as well as write that time to a log file. So let's go ahead and create a variable for our log file. And we'll just name this tmp/time.txt and we need the current time so let's just store that in a variable as well. Take the output of the date command and we'll use the capital T format. Now let's create a message that we're going to display. Let's do this. Let's say the current time is time and then we'll use the echo command to display that message to standard output which is typically the screen by default, but it's not gonna be that when we're doing cron jobs. So we're doing this for a reason, so hold with me there. So we're gonna take the output of this command and pipe it into T, which can... with -a option of pinned that data to the log file. So really what happens here is eco generates some standards output, T takes that output and appends it to the log file as well as displays it on standard output. So I just write and quit here, give this script some executable permissions and run it and see what happens. It says the current time is 1347. And if we look at our log file here has the same bit of information. Let's just go ahead and run it again, now it says 19 seconds. And now we have two entries in our log file. So what happens is every time this script executes we get another line in Ford/tmp Ford/time.txt. So now let's create a cron job for this script and to create cron job you use crontab -e for editing your crontab. When you do this, you're placed into an editor and I'm going to add a comment here. It says run every minute. And by the way, comments in crontab start with a pound sign. So now let's use our job specification here. We need five fields for the time. So that's one, two, three, four, five. So this means it's gonna match every minute of every hour of every day, of every day of the week of every month. So this runs absolutely every minute. So now what we need to do is give cron the command or commands we want it to run. So we'll do this vagrant/multinet-demo04.sh and we're going to write and quit, we're gonna save our changes here. Now because with the vagrant user, when we ran the crontab command we were editing vagrants crontab, which means the cron job will also execute as the vagrant user. If you want a job to run as the root user, for example while you at first switched the root user and then run cron tab -e or you could use sudo cron tab -e because sudo executes commands with super-user root privileges. So in that instance you would be editing roots crontab as well. Now you can look at the current crontab for the current user by running crontab -l for list. And here you can see it's exactly what we specified earlier. By the way, the crontabs are nothing more than files and those flat files are stored in var spool cron. So let's look there and see what's over there var spool cron. Just one file and it's named for the user which is vagrant and that's who owns that crontab and who that crontab will be executed as. So let's look at the contents of that var/spool/cron/vagrant. Permission denied, I actually need to do this sudo cat /var/spool/cron/vagrant. It says the exact same thing that we specified, it's exact same output with crontab -l. I just wanted you to know where those files live on the system. And we'll get back to this in just a minute. So by the way we see a message here that says you have some mail. So this is kind of interesting and we're gonna talk about this too. So we can see if our cron job was executed by looking at the tmp/time.txt file here, tmp/time.txt and hit enter. Then sure enough, actually it's been executed a couple of times since we've executed the command on the command line. So it looks like our cron job is running. As I mentioned earlier, any output from the cron job is mailed locally to the owner of that cron job. So we can check our local system mail with the mail command. Just type mail and it looks like we have some messages so you can just do read a message, you just supply the number and hit enter. So let me just read the most recent message. I'll just type three and hit enter. And if you look at the bottom of the screen here you can... Let me scroll down here, just hit the space bar. And this is the body or the output of the cron job which is the body of the email. The current time is 13:51:01. So this is what happens with standard output is that by default, it gets sent to the local mail of the user. I'm just gonna delete these mail messages here. I can do d three to delete the second one. I can use for headers to list the headers and I'm just going to go ahead and delete one and two as well. So now I don't have any mail and I can quit with queue. Personally. I don't like having cron send me local mail. So what I like to do instead is redirect all the output from my cron jobs to a file. So let's get back in here in our crontab and change it. Sudo crontab -e to edit our cron job. So let's take all the output that's generated by this ampersand greater than sign and let's say, we're going to put it in a file called tmp to this multinet demo04.sh. I'll just call it .cron log. Now the name doesn't matter here we're just doing redirection like you would on the command line and so you can supply any name there's nothing special about .cron log whatsoever. It's just something I came up with and I kinda use from time to time. So again, nothing special we're just using redirection like we would any other time with bash. Ultimately what this means is any output generated by this cron job is going to go to that file. So let's go ahead and save our changes to a crontab -l to make sure that they're reflected and they are. And then what we can do is just wait until the job runs to see if that file gets created. So we could do this ls -l/tmp and it starts with m, so let's just do this mu* There's no file there. Let's see what time it is. We have just a few more seconds, 15 more seconds or so before that file gets created. So I'm just actually gonna wait here. The cron job should execute here in about two seconds. It's the top of the hour, excuse me, it's the next a minute 13:54:00. Now let's see if our file is there and sure enough it is. And so now let's look at the contents of that file. The standard output of that cron job went to that file that we specified due to redirection. And by the way, that was just standard output. But by us using the ampersand greater than sign that would also catch any standard error as well. Let's get back into our crontab here with crontab -e. Now notice that I used a single arrow for redirection. So the file will get overridden every time the cron job is executed. If you don't want that then use ampersand and two greater than signs and that will append the output of that file. So that file will just keep growing and growing and growing. Now for some jobs it's okay just to have the most recent output and for others, you might want to save all the output. Now the decision is yours and it depends on what your needs are. Let's say you don't want to see any of the output, however, we can just redirect it all to dev null. So instead of specifying a file, we'll just specify dev null. So now what will happen is any of these standard output or standard error that gets generated is going to the bit bucket, it's going to dev null, it's gonna get tossed away. You'll never see it. It'll never be anywhere on the system. And that can be fine, just know that once that data is gone, it's gone. You can't find it. It's not gonna be anywhere. So if you're fine with not seeing any of the output, you can do that. Let's talk about environments. When you interactively log into a system, you get one set of configuration for your environment. Most notably your path environment variable is configured. When you're running a job in cron, the environment is more than likely different. It's typically non-existent really. That means you'll end up with a default path, which is probably just Ford/bin and maybe Ford/usr/bin. As a quick reminder, the path variable is used by bash to find which command to execute. When you type ls, for example, bash looks for it and the first path specified in the path environment variable. And if it finds it there, great, it executes it. If it doesn't, it looks at the second path and so on. And if it doesn't find it in any of those paths it returns an error saying command, not found. So let's see what path we get with cron by default on this system. Let's create a file called multinet-demo04b.sh And we're just gonna do this, so shebang and super simple. We're gonna do nothing more than just echo the path. We'll set the permissions here to be executable. And let's go ahead and put this in our crontab here. So this is gonna be 04b.sh and let's just send its output to tmp/b. So let's use tail -F/tmp/b. Now, by the way, tail -F will wait for a file if it doesn't exist, to exist and then it'll start following it. It will just display each line as it's written to the file. So, as you can see here at first, it didn't exist but the cron job got executed and now tmp/b has appeared and we're following at the end of that file. And the output of our simple echo path command is Ford/usr/bin:Ford/bin. So this means if your script is calling any commands outside of Ford/user/bin and Ford/bin you need to account for that in some way. One way to do that is to adjust the path inside of the script itself. So let me hit control + C to exit out of the tail command here and let's go back and edit our script. So path is simply a variable and it acts like any other variable. So we can do things like use variable reassignment. So we know we get a default path so let's go ahead and do this. Let's include the default path first, if you'd like and then we can append to it, let's say usr/sbin and also sbin. You could have also done this. If you wanted the path that you specified to show up before the default path, that's fine as well. You can do this whatever path you want. Say usr/local/bin and so on. But I'm not gonna do that, I'm just going to append usr/sbin and sbin to our path. So let me save the changes to this script and then let's see what happens when this is being executed. So we'll do this, follow tmp/b and wait for the next minute to come around and see what the output is from our cron job. Now you can see that it's not only usr/bin and bin it's that plus what we've appended to our variable which is Ford/sbin and Ford/sbin. Hit control + C here to get out of that. So now if we had any commands that were in usr/sbin or sbin they would get executed just a fine. Now another way to handle this is to set the path as part of the command. So let's do this. Let's do crontab -e. And what we can do here is this, I'll set path equal to the current path and then append usr/local/sbin to it. And let's do that. Let's make sure I got this right path:usr/local/sbin. That looks good. Now let's see what happens when this cron job gets executed at the top of the next minute. So now we have the output usr/bin:bin: usr/local/sbin:usr/sbin:sbin So let's review how we got this. Let's do a crontab -l. So what happens is the path is set to the, whatever the current path is, which happens to be usr/bin:bin and then we append usr/local/sbin to it which is accounts for the first three paths we see. And then when we actually execute the script itself it further appends usr/sbin and :sbin as well. So that's how end up with that full long path that's getting displayed there. So now I'm not saying that you should mix and match and combine these two methods necessarily, I'm just demonstrating that there are a couple of ways to manipulate the path especially when dealing with cron jobs. So there's yet another way to handle this situation which is to use full path two commands in your script. For example, if you want to execute a command called fuser that is located in Ford/usr Ford/sbin, you could use Ford/user Ford/sbin Ford/fuser each time you call the command in your script. Personally I would recommend just adding usr/sbin to your path in some fashion, like we talked about just now but it is an option and you might see someone else do this in their scripts. And since I brought it up, the fuser command shows what processes have a given file open and just for fun, here's how it works. So which fuser, sure enough, it's in usr/sbin/fuser. So that means, again we need to make sure that usr/sbin is in our path. So let's do this. fuser, let's see what process or processes have var/log/messages open. It says PID six one three. So we can look at that PID by using a full listing and a PID to specify which particular process ID we wanna look at. Six one three, hit enter and sure enough rsyslogd which is the syslog service here on our system has var/log/messages open. I didn't mention this, but of course to see how your current path is configured while you're logged in, you can simply echo its content. So we could do this echo and dollar sign path and hit enter. So as you can see here, usr/sbin was in our path, and that's why we were able to just run fuse with command line and it would execute. By the way you can use this as a clue to set your path inside your scripts or inside your cron jobs as well, so that in mind. Let's get back to editing the crontab. So you might've noticed just when I executed crontab -e the VI or the editor started. If you want to use a different text editor you need to set the editor environment variable with the command you want content to execute when it starts. We can do this on the command line just like we did for path in the previous cron job example. So we could type in editor and then let's specify nano as our editor, and then, actually let's do this. And then we can use our command crontab -e. And so, as you can see now we are in the nano editor. Let me just hit control + X to exit out of that. You could also set your editor environment variable for your environment like this, export editor equals nano. And then when you run crontab -e, it'll execute nano because it looks at the editor environment variable to know what editor to start. If you wanted to make this a permanent change where nano always starts as your default editor for crontab you would need to add that editor environment variable setting to your personal initialization files sometimes called .files. For example, you could put this configuration in your .bash_profile file which is in your home directory. So while we're here editing our crontab with nano let me demonstrate the syntax checking that crontab provides. So I'm going to intentionally leave out a time specification field and then save my changes. By the way, it's totally fine to have blank lines in your crontab as well. So I'm gonna add a blank line here, and I'm only going to specify four fields and then I'm gonna give a command. Let's just run, touch/tmp/tests, for example. Now I'm gonna hit control + X to save my changes. It says, do you wanna save the buffer? Yes, I do. And it gives us this temporary file to write our changes to. Now do not change the name of this temporary file when you're saving it, this is on purpose. Just accept the defaults. What the crontab command does is have you edit a temporary file, and then if all is well after you're done editing that file and this syntax checking is complete it takes that temporary file and replaces the existing crontab file with it. So I'm going to hit enter here and it says do you wanna retry that same edit? Because that's an invalid syntax. And of course I do. So I'll just type Y and hit enter. And it dumps you back into the editor editing that same temporary file. So your current cron jobs or current crontab is still intact, it's not broken. And so this is a little bit of a safety mechanism here. And so this could be one reason you would want to use the crontab -e command because it checks the syntax versus just manually going in and editing Ford/var Ford/pool Ford/cron and then the crontab for each user there. I'm not actually going to really fix this job, I'm going to delete it, which I guess is one way of fixing it, now that I think about it. So let me do this. I'll hit control + K, to cut that line out control + X to save my changes. Yes, that's fine. Again, do not change the temporary file, just hit enter and our crontab is saved. If you ever configure a cron job and it doesn't do what you expect or it's not working or it's not getting executed for some reason I would do the following, first I would perform a crontab -l and carefully look at the time specification to make sure it should be executing at a given time. Sometimes you think it's supposed to execute every five minutes but maybe you've accidentally configured it so that it executes every five hours, that's a common mistake. Next I would copy and paste the command as displayed by crontab -l. So we'll do this crontab -l. For example, I would do this, I would just copy and paste this command and then execute it on the command line to make sure that it runs on its own without any involvement in cron. Of course, if you get an error with your script on the command line without cron you're sure as heck going to get an error with it via cron. The next thing I would do would be to check the mail for the user that the cron job is listed under. And I would make sure that there is no output that's coming from the cron job that I could look at. And if there is, then I would address those issues. One common issue is not having the proper permissions on the script and getting a permission denied error as output from cron when it attempts to actually execute the script. Another thing you might see are command not found errors due to an improper path which we've just talked about. Next I would send all the output to a log file so I could see the contents of that log file. So if we do this crontab -e just like we're doing here we're sending all of the output to a log file and then we could look at the log file on desk to see what's happening. And finally, I would look at the cron log to see if the job is executing or if there are any messages there. So let me get out of here. And on this particular system the log file for cron is var/log/cron. Let me just cat it here. On other Linux distributions, it might be var/log/cron.log or cron/log or it just might write to var/log/messages or var/log/syslog, depending on the syslog configuration. That brings us to the end of this lesson where you learned how to use cron to schedule jobs as well as how to avoid and fix some of the most common issues when you are working with cron.
Jason is the founder of the Linux Training Academy as well as the author of "Linux for Beginners" and "Command Line Kung Fu." He has over 20 years of professional Linux experience, having worked for industry leaders such as Hewlett-Packard, Xerox, UPS, FireEye, and Amazon.com. Nothing gives him more satisfaction than knowing he has helped thousands of IT professionals level up their careers through his many books and courses.