Linux Shell Scripting
The course is part of this learning path
In this course, you'll learn about the different types of input and output and how to redirect and control those types of input and output. We'll then walk through an exercise that focuses on creating users on a Linux system - this exercise builds upon the exercise that we did in the Password Generation and Shell Script Arguments course.
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 the different types of input and output and how to redirect and control those types of input and output
- Gain practical experience of how to create users on 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.
Before we can cover redirecting standard error, we need to talk about file descriptors. A file descriptor is simply a number that represents an open file. For us humans it's easier for us to reference files by name but it's easier for computers to reference them by number. By default, every new process starts with three open file descriptors. They are file descriptor zero, which is standard input, file descriptor one, which is standard output and file descriptor two, which is standard error. You may be thinking, hold on by default standard input comes from my keyboard, but my keyboard isn't a file. And by default standard output and standard error are displayed to my screen, but my screen isn't a file either. On one level that's true, but Linux represents practically everything as a file. Perhaps a more accurate description of a file descriptor is that it's a way that a program interacts with files or to other resources that work like files. These other resources include devices such as keyboards, terminals, and so on. This abstraction of treating almost everything like a file allows you to do some really powerful things, like take the standard output of one command that would normally be displayed to your screen and use it as input to another command. Of course, I'm talking about pipes in this case. So file descriptors are like pointers to sources of data or places that data can be written. Things like keyboards, file, screens and so on. Okay, back to IO. So far in this lesson, we've been using the implicit way of redirecting input and output. For example, when we redirect standard input into a command file, descriptor zero is assumed. Let's look at the contents of a file on our system. Now let's use that file as standard input, let's say to the read command. So we're going to read into the variable X, the contents of etc/centos -release. Now, if we echo dollar sign X, we'll see the contents of that file. So sure enough, that input redirection worked and that's the implicit way to do it. If you want to be as explicit, specify the file descriptor number right before the redirection operator. So the previous redirection is the same thing as this. Read X zero less than sign etc/centos release and hit enter. And if we echo X, sure enough, it does the same thing. The reason it's the same is because if you don't supply a file descriptor, then zero is assumed for standard input. Notice that there is no space between the file descriptor zero in this case, and the redirection operator the less than sign in this case. This is very important. If you leave a space, then what you thought was a file descriptor number that you were specifying, ends up being an argument to the command before the redirection operator. So quickly, this is wrong. Read X zero with a space when you intend to do redirection. And we should see an error. And sure enough zero is not a valid identifier. That is actually coming from the read command. It thinks we specified read X zero. And let's give it some bogus data here and then hit enter. And sure enough, we get the same situation. Zero is not a valid identifier. However, this is correct. Read X zero without a space less than sign etc/centos-release. Now, if we look at the value of X, sure enough it's assigned to the contents of that file. Let's be explicit with standard output redirection. First, let's do this, echo you ID to the file that's called a uid, cat the contents of that file. Sure enough 1000, that's our UID. No big deal. So that was the implicit way. If no file descriptor is given, then file descriptor one is assumed when using the greater than symbol. The previous command is the exact same thing as this if we specify a one as a file descriptor right before the greater than sign. Hit enter, and if we look at the contents of that file it's the same thing, because those two forms of redirection are the same. Again, do not use a space when supplying a file descriptor. Let's see what happens when you do that with the echo command here. So we'll do UID and then one and specify a space. Now this is the wrong way to do this. I just wanna make that clear. Now let's use the greater than symbol to the UID file. So now if we cat the contents of UID, we see we get 1000 which is what the UID variable expands to, and one, because one was treated as an argument to echo. So if we do this without redirection, we'll see that that is what is displayed to our screen. By convention normal program output is sent to standard output, while error messages are sent to standard error. When you're working at the command prompt, you may not even notice the difference because again by default they are both displayed to the screen. Let's make a program generate an error. First, let's get some more information about the head command. If we read the synopsis, we see that file is in brackets and there's also three periods or an ellipsis, after that which means we can supply multiple files to the head command. And here's what that looks like. We'll just do head dash n one to read the first line of the etc password file. And we'll also read the first line of the etc host file. So when we do this, head tells us which file it's displaying, what lines from, shows the etc password file. The first line from that, then it shows etc host file and the first line from that. Now let's use a file that doesn't exist. So we'll supply something, and we'll name it fake file and hit enter. So it says head cannot open forward fake file for reading, there's no such file or directory. Even though you can't tell by looking at the output, there is both standard output and standard error generated here. Let's redirect standard output to a file. Run the same command, the greater than sign, we'll call this head.out. Since standard error was not redirected it was sent to the screen. And of course that error message is that file cannot be found. And the normal standard output is saved into this file, head.out. Let's redirect standard error, and the way to do that is to specify it's file descriptor of two. So we use the same command head dash n one/etc/password etc/host, this file that doesn't exist and then specify a file descriptor two. Without any spaces, we'll use the greater than sign and then give a file to redirect this standard error two. We'll just call this head.err. So now what comes to our screen is the standard output while the standard error goes to the file. So let's look at the file. Okay, that's where the error message went. Let's go ahead and get rid of these files real quick, head.out and head.err. We can even get real fancy here and redirect standard output to one file while redirecting standard error to another. So let's do this head dash n one/etc/password/etc/hosts, some file that doesn't exist. We'll redirect standard output to head.out and standard error to head.err, okay? No output was displayed to the screen. Let's look at the standard output 'cause it's in this file, and now we can look at the standard error because it's saved in this other file. You can probably see that this could be helpful in some situations. If you were just concerned about errors that were being generated, well, you could redirect all the error messages coming from standard error into a file for example. By the way a pending output is probably what you would expect, using two greater than signs assume standard out, but even when you use a specific file descriptor such as file descriptor two for standard error, it's still opens to the file. So let's execute our command again here. What we're going to change is adding another greater than symbol, so we have two greater than, greater than and then a path to a file. So we'll hit enter and let's do this again. We'll hit enter a couple of other times here and then cat the contents of head.err, and sure enough we have several repetitions of the error message because we repeated that file multiple times using append instead of overwrite. What if you wanna send standard output and standard error to the same place? There are a couple of different ways to accomplish this. I'm going to show you the older syntax first and then the new syntax right after that. So again, we'll just use our example command here. We're going to send the output to head.both, two greater than ampersand one and hit enter. So if I cat head.both, you're going to see the standard output and the standard error both in that one file. So why does that work? Well, first off you understand that greater than sign, head.both is redirecting standard output to the head.both file. That's still the same. A number that comes directly before a redirect operator is a file descriptor. So two greater than means redirect standard error. The new piece is the ampersand symbol. Normally with redirection a file follows the redirection operator. However, if you wanna use a file descriptor instead of a file name, use the ampersand symbol. So instead of redirecting standard error to a file, it is being redirected to standard input. If you were to omit the ampersand, then one would be treated as a file named one. So the command you see here on your screen head dash n one, etc password etc host fake file, greater than head.both two greater than ampersand one sends the standard output of head to the file named head.both and appends the standard error to the standard output. Since standard error is redirected to standard output and standard output is redirected to head.both, all output will be written to head.both. I'd like to point out here that spacing is important as well. Do not use a space after the redirection operator and the ampersand. Okay, let's get rid of this file head.both. If that seems a little confusing and hard to follow or too complicated, no problem. You're not alone. Since this is a common practice, new syntax was added to bash to redirect both standard input and standard output. That syntax is ampersand greater than and then a path to a file. So let's do our same command here, head dash n one, a couple of files that actually exist and one that doesn't. And then we're going to use this new syntax, ampersand greater than sign, and there's no spaces there and then provide a path to a file, and that path is just going to be our current directory at head.both. That's much prettier and probably easier to understand in my opinion. And if we look ahead both sure enough, the standard output and standard error both end up there. Again using double greater than signs appends to the file. So let's prove that real quick. We'll just do this adding another greater than sign here. So it's ampersand greater than greater than, hit enter. And now if we cat head.both, we'll see two sections of output because we've ran the command twice, and the second time we appended to head.both. If you think back to when you first started using pipes you learned that a pipe takes the standard output of one command and uses it as standard input for another command. This means that standard error doesn't flow through the pipe. Let's demonstrate this. First, let's take a look at the cat man page. Wanna point out this dash in option that numbers all output lines. So if we go back to our head command here, without any redirection, it looks like it specifies six lines. That's the password routes blank line etc hosts, one that starts with a 127 and then heads. So one, two, three, four, five, six. However, if we pipe this to cat and use the dash n option, cat only counts five lines. You see first standard error is displayed to the screen because it was not passed through the pipe. The standard output of the head command was passed through the pipe as the input to the cat command. The cat command numbered each file it received a standard input. It's important to know that about pipes that you're only getting the standard output going through the pipe. Now this could be exactly the way you want things to work when you're working with pipes, or it might not be. If you want to force all the output of a command through the pipe, then you need to append the standard error to the standard input. So one way we can do this is specify a file descriptor of two which is for standard error, and then redirect that into ampersand one which represents file descriptor one which in turn represents standard output. So now that standard error is going to standard output and all the standard output goes through the pipe, then cat should count all of our lines. Let's hit enter and see if that happens. Sure enough, it counts all six of our lines. Now there's a shorthand for this, it's pipe ampersand. So instead of supplying all this, if we supplied pipe ampersand it combines the standard error and standard output into one and then passes that through the pipe. So we get the same result. Okay, we've covered a lot here on the command line. Let's get back to our script and really do a mini recap of what we've been talking about and let's document it in our script so we have something to look back to if we want to later. So I'm going to edit our file, and we're just going to be really repeating some of the examples above, but this time using file descriptors. So we'll redirect standard in to a program using file descriptor zero. We'll just display this back. And now what we'll do is redirect standard out to a file using file descriptor one overwriting that file. So let's do head dash n three etc/password. We'll be explicit here and use one and then give the path to the file. Okay, let's just stop right here and save our changes and execute our script and see what happened so far. More or less we get a repeat of what happens at the top of the script. We get line containing the first line of data, and we get the contents of temp data containing three lines as well, even though we use the explicit form of redirection by supplying file descriptors. So again, it works the exact same way, implicit or explicit. Let's go ahead and continue editing our script here. By the way, standard error is abbreviated STDERR, and standard error is our file descriptor two. So we're going to use this error file a couple of times. Let's create a new variable as well as a new file for this, error file or ERR file. Let's give this temp/data.err. We'll give this file something that's going to generate an error, file that doesn't exist. We'll redirect that error message into the error file. Okay, let's save our changes, execute our script, and we get the standard output to our screen. Let's see what the contents of this temp/data.err file are. Sure enough, that's the error message that was generated by the head command that we redirected into that file. So that's doing exactly what we specified. Let's get back into our script and keep on going here. Now let's redirect the standard out and standard error to a file. We'll use our same head command here. Only this time we're going to use the new syntax, the ampersand greater than sign and send that to a file. And now we'll just display the contents here. Okay, save our changes and execute our script. And sure enough, everything is displayed to our screen there. So the standard output as well as standard error were both combined. Now let's redirect standard output and standard error through a pipe. We'll print a blank line and then we'll use our head command here. We'll use the new syntax if you will, the latest syntax of pipe ampersand, and we'll pipe that to the cat command with the dash n that's going to number the standard input that cat receives. So we'll save our changes and execute our script. And sure enough, the error message of cannot open fake file gets passed through the pipe to cat and gets numbered. So we've appended standard error to standard output. Let's do the opposite which is to append standard output to standard error. So if we take something like the echo command and just echo the word error, pipe that into cat with the dash n option, it just shows one line because one line came through as standard input into cat. So now let's do the same thing here, echo "error" but this time let's use redirect symbol, the ampersand which specifies a file descriptor and we'll use file descriptor two and pipe that to cat. Now remember that the ampersand that follows the redirection operator is used when you want to use a file descriptor instead of a file name, and to be super clear, this is exactly the same as this, one greater than ampersand two. Now the reason cat didn't put a line number in front of error is because the output of error never got to cat because it was redirected to standard error. So echo error generated standard error. So we forced echo to really generate standard error. Now, why would you ever wanna do this? Mainly so that you can make your scripts conform to the standard convention of sending error messages to standard error. If you find yourself writing an exit command in a script and echoing some information just before that, that's a really good sign that you need to send the output of those echo commands to standard error. So let's put a demonstration of how to send output to standard error. We'll be using this in our future script of ours. So we'll do echo. This is standard error, and then do this, greater than ampersand two. Write our changes and execute our scripts. Now let's execute our script and redirect standard error to a file that we'll call err. So now if we cat err, the standard error generated by our script is in that file. Before we wrap up this lesson, I wanna cover the null device. The null device is a special file that throws away whatever is sent to it. Some people call this the bit bucket. So if you don't want to see output on your screen and you don't wanna save that output to a file either, then redirect that output to the null device which is located at /dev/null. So here's an example, head dash n one/etc/ password etc/host/fake file. We'll just run this command to remind ourselves what this does. Now let's discard the standard output. So we'll just send the standard output to dev null. So also we're left with is standard error. Now we can do the same thing here and just throw away the standard error by doing two greater than and then the path to the null device. Okay, now we don't see any error messages. Now let's throw away all the output generated by the command, and we know how to do that because we can use this ampersand greater than sign to combine standard output and standard error, and we'll redirect that to dev null. So null output is generated. You might wonder when you would wanna do such a thing. Well, here is the use case. If you're executing a command in your script and you don't want the user running the script to see that output, then send it to dev null. And remember, if you need to know that a command succeeded or not, simply check it's exit status. You don't need the output to determine if it succeeded or failed. So we can do this echo $? And we get an exit status of one which is non-zero, so something went wrong, but if we make the command succeed for example, take away that file that doesn't exist, and then we check the return status, we get an exit status zero, so we know that the command succeeded. And now let's finish up our little script here by adding these examples. Let's discard standard out. And then let's discard standard error. So we'll send file descriptor two to dev null, and finally we'll just do an example here of discarding both standard out and standard error. We'll put a message here too, to show that we're doing that. And one thing I like to do is clean up after myself. I don't like to leave temporary files just laying around, so I remember that we created two files that we're going to remove here. We'll remove file, and then we also created an error file that we can remove. And by the way, this would be a good place to send to dev null. So for some reason the rm command fails maybe we really don't care because it's a temporary file. We can just throw that output away if we want to. So let's do that as an example in this case. Okay, we're almost done here. We'll just exit on this file and then execute it and see what happens. So here's the example where we discarded standard output and we're only left with the error message. We did the reverse by discarding standard error, so that only left the standard output, and then finally we discarded both standard out and standard error and of course nothing was displayed to the screen. In this lesson, you learned a lot about input and output redirection. You learn that each new process starts with three open file descriptors. They are file descriptor zero for standard input, file descriptor one for standard output, and file descriptor two for standard error. You learned that the greater than sign redirects standard output to a file and that it creates the file if it doesn't exist, or if that file does exist, it truncates that file overwriting it. You learn that you have to use double greater than signs to append to a file without overriding it. If no file descriptor is specified for output redirection, file descriptor one is assumed. That's why you had to specify all the file descriptor two to redirect standard error. You learn that you can redirect both standard input and standard error to a file by using the ampersand a greater than sign syntax. If you want to redirect standard input and standard error through a pipe, use the pipe ampersand syntax. From there you moved on to redirecting standard input from a file into a command by using the less than sign. Finally, you learned how to use the null device by redirecting output to dev null. I know we covered a lot in this lesson. If this is your first time being exposed to input and output redirection, I would actually encourage you to watch this video again. Let it soak in for now and maybe come back to it tomorrow or in a day or two.
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.