Linux Shell Scripting
The course is part of this learning path
In this course, we'll cover a range of topics designed to help you enhance your Linux scripts. We'll start off by looking at case statements, which are used to make a decision based on the value of a given variable. We'll cover functions before moving and then move on to how to process command-line options using the shell built-in getopts.
In the second part of the course, we'll look at managing users including how to disable, delete, and archive users on a Linux system. We'll then do a walkthrough exercise showing you how to delete a user, which you can follow along with.
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 about case statements and functions to make your scripts more efficient
- Process command line options using getopts
- Manage users in Linux
- 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.
In this lesson, you'll learn how to use the case statement. The case statement is the perfect way to make a decision based on the value of a given variable. This is especially true when you have a specific set of values that you're looking for. So first off, I'm going to open up a terminal on my local machine and then go into our class folder of shellclass. And from there, I'm going to go into the localusers directory. And now I'm going to start the virtual machine with Vagrant up. Now that the machine is booted, we'll connect with Vagrant SSH. And from here, we can move into our shared folder of /Vagrant. So far in this course, you've been using the if statement to perform tests and to make decisions in your scripts. Today, you're going to learn how to make decisions using the case statement. Now, before we start writing our script, let's look at the case command itself. First off, it's a shell built in. And we know this because we can type the type command type -a, and specify the command, which is case. And it says, case is a shell keyword, which is a type of shell built in. Of course, since it's a shell built-in you know that we can get some information on how to use it with a help built answer. We'll run help, case. The first line shows us the syntax of how to use the case statement. It's all squeezed together on one line so it's a bit hard to read, and I'm going to flush it out for you in the script I'm about to write so you can see this syntax very clearly. Also, this output tells us that commands will be executed based on pattern matching. So what you do with a case statement is compare a value to a pattern, and if the value matches that pattern, the commands associated with that pattern or below that pattern get executed. If the pattern isn't matched, nothing gets executed. Again, this will become pretty obvious with a couple of simple examples. So I'm going to name this script, luser-demo09. We'll start out all scripts like we do with #/bin, and then let's add a comment here about what this script is going to do. Let's start with something you already know which is the if statement. By the way, using the if statement in the way we're about to use it is not really the ideal way to handle this situation. But the reason I'm going to show you this is because, one, it builds upon what you already know. Two, you're going to see it done this way in other scripts. So it's best to know what is going on when you see that. And three, it serves as a nice comparison for the better way to handle this situation, which is to use a case statement, which of course we'll be getting to soon enough. So with that little disclaimer out of the way let's say you want to do something if the word start was passed in as the first argument to your shell script. You know that the first argument is stored in $ one, so you can do something like this. If, $ one is equal to start, then let's say we just wanna echo the word starting. Then we close out our if statement with FI. Once right and quit our changes here, make the script executable, and then test it out. So that looks like it works. So if we supply anything other than start as the first argument, nothing happens in our script. Let's go ahead and try that out. We'll try this, nothing happens. We'll try this, nothing happens. Of course, if we don't supply anything, then nothing happens. So again, that is working as we expect it to. If we want to account for other cases, we need to code for it. So let's take a quick look at the help, for the if command. So we'll do help if. What we need to use to build out our if statement to account for other strings is elif, which is short for else if. So let's get back into our script and account for stop as an argument. So I'm gonna go up here and add elif. If $ one is equal to stop, then we'll echo stopping. So if we read our code from the top down. If $ one equal start, then what we're gonna do is echo starting. Else, if $ one equal stop, then what we'll do is echo stopping. It's pretty, intuitive the way this is working out. So let's save our changes and try it out. Sure enough, we pass in stop, and stopping gets displayed to the screen. So let's go ahead and jump back in and add another elif to handle status. So here we'll go elif, $ one is equal to status. Then we'll echo status. While we're here, let's handle all other cases with an else. Let's also make this an error message by sending the output to standard error, and exit with a non zero exit status. Okay, let's try this out. So we'll do luser-demo09.sh status and it displays status to our screen, which is what we want. Let's pass this and restart. And it says, "Hey, restart is not a valid option. Please supply a valid option." And so we can check our exit status here and sure enough, it's one. And of course, if we don't display or pass anything into our script, we also get the same situation. We need to supply a valid option. So what our script is doing is taking an action based on the values stored in $ one in our script. Now, if you stopped right here, that would be absolutely fine. You made things work with the tools and skills that you had at the moment, which is what we all do anyway, right? But let's add to your skills and recreate this exact same functionality, using a case statement. Let me go ahead and comment out all that existing code first. And then we'll go ahead and build on it from here. Now, let's build out our case statement. The first line consists of the word case, followed by the value that you are going to check. In our case, that value is stored inside $ one. And finally, we're going to end the line with the word in. So we have case, our variable and in. Case $ one in. Next, what we need to do is supply a pattern followed by a closing parenthesis. For example, we want to see if $ one matches the word start. So we do this. This means if $ one is equal to start, then execute the code that follows. And here's the code that we want to execute. If you wanted to execute many commands, you would simply supply them on a separate line. To end the code block, use two semicolons. To finish the case statement, spell the word case backwards. All right, so let's try this out. So that works. Now let's go ahead, go back and add the other patterns that we're looking for to our case statement. So now we're looking for the word stop, and then what we're gonna do there is echo stopping to the screen, and to end our code block with two semicolons. And let's also look for status. So notice how we're not repeating ourselves multiple times like we did with the if statement. We supply the value that we're checking once, and then all patterns we're looking to match. So if we go back in our script, we use if $ one is this, elif $ one is that, elif $ one is something else and so on. But here in our case statement, we say, if $ one is this, that, or the other, then we're gonna do something based on that value. Again, it's much cleaner in my opinion than the longer if statement that we see above. So let's make sure that this little bit of code works. Okay, stopping argument works. Let's look at the status argument and that works as well. The only thing that is left to account for is anything that does not match the string, start, stop and status. So let's quickly reference the bash man page and get some more information on patterns, as they are used with the case statement. So I'm gonna type in man bash, and I'm gonna search for the case statement here. Just go case. Okay, here we go. It reads, "A case command first expands word, and tries to match it against each pattern in turn using the same matching rules as for path name expansion." And then it tells us to see path name expansion below. So that means we have to look at that section of the man page. And we're gonna do that now. Pathname, let's do exp. Okay, here we are. Really, what we're looking for in this section is about pattern matching. So let me just page down here until we get it. There it is, pattern matching. So, let's see here. It says, "Any character that appears in a pattern other than the special pattern characters described below matches itself." That's the type of pattern we have been using so far. For example, our first pattern in our case statement is S-T-A-R-T, which only matches that exact sequence of characters. The special characters here are asterisk, question mark, and brackets. The asterisk matches any string, including the Knoll string. Said another way, asterisk matches anything. I'm sure you've probably used this before, right? So, for example, if you want to get a list of files that end in TXT, you would run LS space asterisk TXT. Now we can do the exact same thing in our case statements. Now the question mark matches any single character. Finally, the brackets are used to match any characters contained within those brackets. And you can also specify a range of characters by separating them with a hyphen. This is all good information to know how to use. So the question mark and the brackets and all that. But for our purposes right now, we only need the asterisk for our current use case because we just wanna match anything else that wasn't already specified. So we'll get out of the man page, we'll type in queue, we'll get back to our script, by starting our editor again. So now let's match anything that is not already specified by using an asterisk as our pattern. So we'll do this. Matches anything. And then we'll say, "Supply a valid option," send that to standard error, exit our script with a non-zero exit status. Obviously one is not zero, so that's gonna work for us. And then we'll close out our statement here with semicolons. Remember that your scripts are being executed from the top down. So once a match is found, the associated code block gets executed. If you wanna make sure something matches first, we'll then put it first in your list. In this case, we are doing the opposite and using the asterisk as a catchall to match anything that hasn't already been accounted for. And we're putting that last in our lists of patterns. So let's go ahead and try out our code. So we'll do luser-demo09.sh start, that works. Let's try stop. Oops, I accidentally ended the line with a line continuation character, but this is gonna work anyway. I'll just hit enter. And sure enough, that works. Let me take that off, and hit enter, and that's what it should really look like. Excuse my typing mistake there. Anyway, let's keep trying our other options here. We wanna make sure that status gets matched. It does, or it reports back status. We'll do restart, for example, here. When we pass in restart, it says supply a valid option, because it does not match start stop status but it does match the asterisk and executes that code. Let's say we want to modify the script a bit and allow a user to provide the word status or state, and still execute the same code. So let's look at help case, and I glossed over this earlier, but in the help output here for the case statement, it says that you can use a pipe to separate multiple patterns. So we'll go back here and then we'll change status here to status, pipe, which you can think of as an or. And then we'll type in the word state that we want to match. Now, if $ one equals a status or it equals state, the code echo status will be executed. By the way, instead of using two patterns separated by a pipe symbol, we could have used S-T-A-T asterisk as the pattern. Now that would match the words, status and state. However, it's not as exact because it would match other things like statue, stateless, static, and many others. I just wanted to share another way you could approach this but I like the more exacting option we went with here. So let's try it out. So we'll do luser-demo09.sh state, and that reports a status. And of course we can do status here as well and that works. Just so you know, you can use a multiple pipes in your pattern. So let's expand our script to accept --status, and --state. So we can do this --status or --state, save our changes and try our code. So we'll do --state first and that works - -status that works. And then if we supply something that doesn't match like this, then we fall back into the asterisk pattern and get that code block there. Let's wrap up this lesson by talking about spacing and style. Just so you know, a case statement like this is really the basis of most init scripts. For example, on older Linux distributions, you would run etc/init.d/sshd status and you would be provided with the status of the SSHD service. Likewise, you would stop the service by executing etc/init.d/sshd stop and so on and so forth. Just kinda like we have laid out here. All the systems services were handled by a simple shell script that used a case statement. Anyway, let's quickly talk about spacing. What I chose to do when creating my case statement was to indent each section by two spaces. For example, the patterns were indented two spaces from the beginning of the case statement. Likewise, the commands underneath each pattern were also indented two spaces from the beginning of the pattern. If you are a four spaces type of person, you can do that. If you like using tabs instead of spaces, that would be fine as well. You can even forego the spaces altogether and have everything at the same level of indentation. But in my opinion, it's harder to read a case statement or an if statement, or really anything else without indentation. So I don't recommend that approach. If you're only executing one command following a pattern, it's a common practice to keep that command on the same line as the pattern match. And to also include the closing double semicolons, on that same line as well. Let me create a copy of the case statement to manipulate and I'll comment it out as well. So I'll just, copy that below and then I'll go ahead and comment it out. Okay, there we go. While I'm here, I'm going to clean up that status pattern. So I'll just do this. Okay? So let me go ahead and put these lines together here. Notice that I'm leaving a space after the parentheses and before the semicolon. That's not technically required by syntax, but it's a little bit cleaner in my opinion. Now I'm not going to change the code block for our catch all pattern, which includes the asterisk pattern. It's multiple commands and it could get confusing running all those commands together. Plus by the way, we haven't talked about the command separator you have to use, if you wanna execute multiple commands on a single line. And we'll be getting to that later. Now, if you look at this case statement, it's a fairly compact yet it's pretty clear. And it's pretty easy to read down the list of pattern, start, stop status, and then the asterisk for everything else. So let's make sure that this newly formatted case statement still works. Okay, it still works with start. Does it work with stop? It does, let's give it nothing which should match the asterisk pattern, and sure enough, it tells us to supply a valid option. So that still works even though we formatted it slightly differently. Before we wrap things up, I wanted to point out that you can use the case statement in conjunction with values other than $ one. For example, you could prompt the user for input and use a case statement to make a decision based on that input. You could create a simple menu system that way, for example. Well, that brings this lesson to a close. In this lesson, you learned how to use the case statement. If you find yourself writing a large if Elif statement comparing different values against the exact same variable, consider using a case statement in its place. You can check the contents of a variable against a list of patterns that you specify. You can do exact matches by specifying the exact string that you're looking for. You can also perform matches with patterns that include asterisk, question marks, and brackets. To perform the same set of actions against multiple patterns separate each pattern with a pipe. Remember to end each pattern with a closing parenthesis, and the code block for a given pattern executes until it encounters two semicolons.
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.