Exercise: Remote Software Install
Start course
3h 7m

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.

Learning Objectives

  • 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

Intended Audience

  • 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.


It's time to write another Shell Script. And so, let's talk about the requirements that we're going to be using to actually create this script. The first thing we know is that, we need to write a script that installs the apache web server. So we come up with a name and we just decided to call it So you can do something like install Apache on server 13 or install apache on web 99 or what have you. So that's kinda how we came up with that name, just like I was saying, we're going to pass an argument to our script. That's gonna be a host and we have to provide at least one host or otherwise our script should send an error message to the user and then exit with an exit status of one. In the case that a user does supply multiple hosts or if we supply multiple hosts on the command line we wanna go through each one of those hosts and install apache on them. Not only are we going to install apache, we're going to start and enable the service as well. Plus we're going to create an index.html file in the web server DocumentRoot that contains the server's host name. And then we want to make sure that the web server is actually accessible and that it responds. And if it doesn't, we want to give the user an error message and ensure the script exits with a non zero exit status. When we're processing these hosts, what we're going to do is actually ping it before we try to ssh to it and install any software. And then if we don't get a ping response, we're just gonna assume the host is down and not even try to install the software on it. And we're going to display an error message about that. And then of course, we're gonna make sure at the end our script exits with a non-zero exit status. So if one host is down that doesn't mean we have to quit the entire script. We can just say, hey, this particular host is not available let me go ahead and do my work on the ones that are available. Another thing we're going to do is make sure that the script is executed without superuser or root privileges. We want this script to run as the user themselves and then when they ssh to the remote system we're going to assume that sudo is configured and they have route permissions that way. And of course, we're going to make our script, have a usage statement. So we can kinda tell the user how to use our script just in case, if they give us any kind of bad data or don't supply an argument and so on. And if everything goes well, if apache gets installed correctly and the web server is responding and the user didn't do anything crazy then we're going to exit our script with a zero exit status indicating a successful normal run of our script. Okay, so here I am already logged into admin01, we're using this multinet setup that we have created in a couple of projects ago and here we have admin01, virtual machine running and server01 and server02 as well. So I'm just gonna go ahead and go into the vagrant folder and start creating this script. And again, we decided to name it,, you know, about the shebang. Let's give our goal at the top of the script here a little one or two line explanation about what this thing does and why it exists. All right, that's short and sweet and installs apache and it assumes that if you're running this script, you have sudo privileges on the host that you specify. So the first thing we want to do here is make sure that the script is not being executed with superuser privileges. So here we're checking for the UID being zero. And if it is zero, we're gonna tell them not to execute this script. Of course, we're going to send that message to standard error since it is an error message. And then we need to give them some usage so what we can do here is create a usage function. So we'll just do that here and we'll go ahead and come back to the top of our script and backfill this a function. So we'll just create a function here called usage. Now we're going to teach them how to use our script. We need at least one host but these other hosts, HOSTN and more are optional. Okay, that takes care of our usage statement, so that should be good. Now let's check to make sure the user supplies at least one argument, which is one host, and if they don't then we're gonna give them this usage statement as well. Okay, if the number of arguments is less than one, then give them usage. When we talked about the, we wanted to make sure that we could just install apache on every host that was up, and if one host was down just to make a note of it and then continue on. But if there is a host that's down, we need to know about it in some way so that we can exit the script with a non-zero exit status, which tells the user that, hey, something went wrong, and in this particular case, what went wrong was at least one host was not able to be accessed over the network. So what we can do is actually just create a default exit status, and then change this exit status along the way, if we need to. And if we get to the end and this exit status has never been changed then we'll exit with that status, which is zero. So let me put a comment above here to talk about it. Now, what we can do is loop through the servers. Let's tell the user what server we're working on or what server we're attempting to work on. And now let's do our ping test here, we'll just send one packet and we'll throw away the output because we really don't care about it. The reason we don't care about the output is because we're just concerned with the exit status, which we'll check here. So if it's anything other than zero, then we're just gonna assume that that server is down. Now we're going to override our default exit status, and we're going to set that as one, and like I said before if this host is down, we're not gonna do anything with it. If we can't get to it, there's no need, you know, attempting five or six ssh commands or however many ssh commands it's gonna take to work on the server when it's not even up. So we're just gonna skip this server and continue on. If the ping command succeeds, we're gonna assume the server is up, and then we can continue on by installing the httpd package. We can do that, ssh into the server, using sudo yum install with the -y option and the package name. Now let's create an index.html file, and actually gonna put this command in quotes. So what we're going to do here is get the host name variable that bash gives us, but we're gonna make sure we get that host name variable on the remote host. So that's why we're doing this echo command over there. And instead of before, or by using the server name. So for example, a user could give us a IP address for the server and we ssh via the IP address but the server name or the host name variable may be different on the inside. So, that's why we're letting bash set that variable and we're going to get it. And we're gonna do that with the echo command here. So we'll just echo the host name and then we're going to append it to this file. And actually if you pin something to a file that doesn't exist, it creates the file, right? So we're just going to create the index.html file. And we don't necessarily need to see the output of the echo to the screen. So we're just gonna go ahead and send that to dev/null. Now what we can do is start the web server. So let's ssh over there, sudo systemctl start, then let's enable it on boot. And finally, what we wanna do is test that we can actually get to the service that the web server is providing. Now we check the exit status of the curl command here, and if it's anything other than zero, then we know we have a problem. Actually, that's an error message, so let me send that to standard air. Again, if we encounter this situation then we know something is wrong, so let's capture that in our exit status variable here. And then let's go ahead and continue on. And the reason why we're going to continue is that we don't want to report that the installation was successful if we can't get to the web server or the web service so that's why we're doing it like that. So now let's report to the user that we actually finished the installation process if we get to this point. That completes our loop, and then finally, we're going to exit our script with this exit status variable that we created at the very beginning of our script. I'm just gonna go back to the very top of my script here and start looking for some common mistakes, some spelling issues, mismatch quotes, or mismatched brackets and so on just to make sure I don't have any issues or hopefully if I do, I can spot them before I go ahead and run my script. So, let's just read down this script and see what we see here. I'm paying close attention to these if statements with these brackets, I'm looking for matching quotes and so on making sure I have my curly braces in the right place. Let's see where else it could be an issue. Kinda noticing the syntax highlighting to have it available. Okay, I notice an issue here, I have systemctl misspelled, 'cause these should line up. Okay. Okay, hopefully that was the only issue I had in my script we're about to find out. So I've saved my script, I'm gonna add the executable bid on it and then I'm going to start testing it. So the first thing I'm going to test is I'll check about root privileges. So let me just go ahead and run it as sudo and see what it says. All right, that's good news, it says, do not execute the script as route. And we have a exit status of one which is what we want in this particular situation. So now let's just run it as our normal user here but don't give it any arguments. And this time we also get a usage statement, which is good. And we also get a non-zero exit status which is exactly what we want. All right, I'm gonna be brave here and just run this script on both of my servers at once we have server01 and server02 actually, I'm just gonna make sure that those are up really quickly. Ping -c 1 server01. Whoops, I forgot my one. Okay, that looks good. Ping -c 1 server02. Okay, no packet loss, so that looks good. So I'm just gonna go ahead and run this script against both of those servers. And if I break 'em both, well, I'll just fix them both, so let's see what happens here. It says it finished the installation on server one and it starting the installation on server two. So, so far it looks good. Okay, it says it's finished on the second server as well. Now let's check our exit status here. All right, we get an exit status of zero. So, according to our script, everything went okay. Let's see if that's the case. I'm going to send an http requests to server01 using the curl command here. See what it returns. Sure enough, it returns its host name, which is good sign, server02 responds to curl and returns its host name as well. So that looks great. So let's simulate the condition where one of the systems is offline. So let me just exit out of here and let's just do this vagrant halt server02. Let's just virtually hit the power button on server02. Okay, let's get back into our admin01 server. And let's see what happens when we try to execute this script against a server that is actually down. Okay, reports that server two is down and the exit status reflects that as well. So that brings us to the end of this Shell Scripting project. Now, hopefully it's primed your brain and give you some ideas of things that you can do to even make this script better. For example, you could test the return codes of the ssh commands, or you can even create a script that's based around this and do something more complex not only install one package that starts a service but you can install multiple packages, do multiple configurations, start many services and so on. For example, you could install and deploy an entire lamp stack or any other type of stack as well.

About the Author
Learning Paths

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 Nothing gives him more satisfaction than knowing he has helped thousands of IT professionals level up their careers through his many books and courses.

Covered Topics