 I mentioned earlier that in some contexts commands can be run in what's called a sub-shell. To illustrate, here's what happens in what we can think of as the normal case, where we have the shell, and it runs a command process by simply forking itself, and while the parent process, the original shell, waits, the child process, the child shell, execs the command executable. When a command is executed in a sub-shell, however, the shell, again, forks itself per usual, but then it's the sub-shell which forks itself and waits while its child fork execs the actual process executable. Again, the reason why it's important to understand when this happens is because that sub-shell, any changes made in that process do not affect the parent shell from which it was spawned. So say if we assigned a variable in a sub-shell, the change won't be seen in the shell from which it was spawned. For reasons which are at this point fairly obscure and I can't really explain here, you may wish to execute a command list in a sub-shell, and we can do this with what's considered an operator a pair of parentheses. So here if I write this command list and put them in parentheses, that will execute not in the shell itself, but in a newly spawned sub-shell thereof. So here in this example, the first command in the parentheses is an invocation of the built-in CD, and CD as I've explained will change the current working directory of the shell. But because we're running in a sub-shell here, this will only affect that sub-shell, not the shell from which it was spawned. So that actually perhaps is one reason why you might wish to use these parentheses to run something in a sub-shell, because you want to invoke commands which will affect the shell, but you want the effects of those changes only to be seen in a limited scope. A seemingly similar syntax that uses curly braces instead of parentheses will execute a command list in the current shell rather than a sub-shell. The syntax for this though is actually quite different because the curly brace here, the begin curly brace, that's really a built-in command. The curly brace, that's just the name of the built-in. Because this is a built-in and not an operator, there has to be a space after the opening curly brace and before the end curly brace, which is really just an argument to the built-in that signifies the end of the command list. You may notice that superficially this curly brace built-in resembles the curly brace syntax we see, say, in a function, but really they're actually quite separate things. They're not inherently related. When you use the function built-in and you write the curly braces for it, they're not the same thing as this built-in, even though they pretty much sort of have the same effect. In any case, you're probably wondering what the hell is the purpose of this, because if I want to write a sequence of commands, I don't have to write it as a command list like this. I can just write them one after the other. So when would I ever use this? Well the answer here is if you use redirection on this curly brace built-in, that will effectively redirect all of the commands in the command list. So it's simply a nice convenience when you wish to apply the same redirections to a series of commands. If you're wondering what the exit status of this curly brace built-in is, it's simply the exit status of the last command executed in the command list. Now where sub-shells most commonly come into play is with the use of the ampersand terminator. If we terminate a pipeline with the ampersand rather than the semicolon or a newline, then that pipeline runs in the so-called background, as opposed to the usual foreground. What it means to run on the background is that first of all, the shell will not wait for the pipeline to complete. Secondly, it means the commands of the pipeline are run from a sub-shell, and it is the sub-shell which will wait for the commands of the pipeline. Additionally, anything running in the background should not read from the terminal from which it was spawned, because that would interfere with the continuing operation of the shell. When we start something in the background from the shell, we want it to execute while we are continued to allow using the shell. We don't want it to interfere with the use of the shell. So it shouldn't read the terminal. In some cases, you may also prefer that something running in the background shouldn't write to the terminal either, because that might interfere with your use, or at least it might just confuse you. So you may choose to redirect the standard out of a pipeline running in the background so that you don't see any of its output. But it's a matter of preference whether or not you allow something running in the background to write to your terminal. Reading from your terminal, however, that is verboten, because then it wouldn't really be running in the background. Consider an example use of the ampersand terminator. Here, the foo command will be run in the background, and even before it finishes running, the shell will then invoke bar. The shell will then wait for bar, however, because bar is terminated by a semicolon, and then it will run fizz, and again wait for fizz, because it's terminated by a semicolon. But then once fizz is completed, the shell will run buzz in the background, so the shell will invoke buzz, but not wait for it to complete. Just like we can apply redirections to the whole of a command list in parentheses or curly braces, we can terminate these with an ampersand to run them in the background. And the interesting thing here is that because running in the background always involves a subshell, effectively the behavior here is precisely the same. While the curly brace command doesn't normally involve a subshell here because it's running in the background it does. To make the foreground-background distinction more useful, modern unixes have introduced a feature called job control, which involves organizing processes first into groups called jobs, and those so-called jobs in turn get organized into what are called sessions. A processes membership in a job and a session actually is a property of the process itself, kept track of by the operating system, and there are system calls, which we didn't mention in the previous unit, for setting a processes membership in a job and a job's membership in a session. We won't go into those details because there are system calls that are used pretty much only by shells, so unless you're going to write your own shell you won't ever have to deal with them, but the concept here of how the processes get organized into jobs and sessions is relevant if you're going to use the shell. The usual arrangement is that when we open a terminal window and inside that terminal window you have a shell running, that shell represents the start of a new session, and that session starts out with one job containing just the shell process. When the shell then runs a pipeline in the foreground, the processes of that pipeline run as members of the existing job, the same job as the shell. Anytime though the shell creates a sub-shell, that sub-shell runs as a new separate job. So say if we run a pipeline in the background, that creates a new sub-shell, and all the processes of that pipeline and that sub-shell run in a new separate job. Now associated with a session is what's called its controlling terminal. This will be the terminal which the original shell of the process is using as standard input and output. At any moment in time only one job in a session is running in the foreground. All other jobs are marked as running in the background. Whereas processes in the foreground job can freely read and write the controlling terminal, processes running in a background job get sent to the signal sig-t-t-i-n when they attempt to read the controlling terminal. So with this feature called job control that modern Unix has have, processes running in the so-called background are actually prevented from reading from standard input of the terminal. The other special thing about the foreground job is that when the user at the terminal types control z, this sends the signal sig-t-stp, which means terminal stop. This signal is sent to all the processes in the foreground job and the default behavior when a process receives sig-t-stp is to suspend execution. What control z also does is send the signal sig-cont as in continue to processes in a selected background job, therefore resuming them if they had been suspended and that background job is also then moved into the foreground. So quite simply control z will suspend the foreground job and move another background job into the foreground. Usually when the user has control z at the terminal, they want to suspend whatever job is running and get back to their shell. So normally the background process that gets resumed is the one with the shell in it. To manage the jobs running in the session of our shell, we have three built in commands. First jobs, which will list all the background jobs and their job numbers, which are numbers assigned to uniquely identify each job. Then we have the bg command as in background, which will nominally what it does is it moves a job into the background, but what it really does is it effectively resumes any job that's been suspended because it sends the sig-cont to all the processes in a background job you specified by number. This is most typically useful because say we invoke a command, but then it takes too long and we want to get back to our shell. So we hit control z, that suspends whatever program was running that puts it in a background job, but it's suspended and we want to then resume it. So we write bg and it's job number two so it can continue on in the background while we get to work with our shell. Lastly, we have the command fg as in foreground, whose name correctly implies that it moves a job from the background to the foreground and in the process it will also send sig-cont to all the processes of that job. So with control z and these three commands, you can control the jobs running in your session. The very last thing we'll talk about in this unit are shell scripts. A shell script is a file of shell commands using the bash syntax and the idea is we can execute this file just like we can say execute a file of JavaScript code or a file of Python code. So assuming we've written such a file, a file that consists of a list of shell commands, observing all the rules of shell syntax, if we wish to execute this file, we can do so with the built in command source. The source command is invoked with the name of a file as argument and what it will do is read through that file and execute the commands in the current shell. So what we get from this is the same effect as if we were to read through that file and manually enter at the command line each command one by one. So for example, what actually happens when we start up a new shell in a new terminal window is bash will invoke the commands in a file in your home directory called dot bash RC. RC here stands for run commands. The idea of the dot bash RC file is it's a place we can put any sort of customizations we want for our shell and because this file is run every time we start a new shell, we don't have to manually enter the commands in this file each time to get our shell the way we want it. Like say, very typically in a dot bash RC file, you'll see lines giving default values to certain environment variables. In any case, it's important to understand that this shell script, this dot bash RC file is run with the source command such that all the commands in that file are executed in the shell itself, not some sub shell thereof. Now, sometimes we don't want a shell script to affect our current shell. We want it to run instead in a sub shell. To do this, we can simply invoke the bash interpreter as a program and pass to it as argument the name of our shell file. The bash interpreter in most unixes will be found at the path slash bin slash bash. And so here we are starting bash and telling it to run the file foo.sh in the current working directory. Be clear that shell scripts don't have to end in .sh but it is a common convention. To make invoking the script more convenient, we can use a bit of Unix magic called the shebang. If we start the first line of our script file with the number sign character followed by an exclamation mark followed by the absolute path to a program, usually an interpreter, in this case, the bash interpreter slash bin slash bash. But if we were writing a Python script we could put slash bin slash Python. And if we were writing a Perl script we can put slash bin slash Perl and so forth. But in any case, what the special line does is it allows us to invoke our script file which is a text file as if it were a binary executable. It turns out that the exec system call doesn't have to be passed a binary executable if you pass it a text file that begins with these two special characters, these ASCII characters. Exec will then execute the program specified by the file path in this case slash bin slash bash and then pass the script file as argument to that program. So if we put the shebang line at the top of our file foo.sh we can then invoke the script as if it were like an executable. We can write dot slash foo.sh and that tells the shell to execute a file in the current working directory called foo.sh. Now actually there is one last detail here. If we're going to be executing the script file as if it's an executable then Unix requires that we have execute permission on this file. To give the file execute permission we use the chmod program which is a standard Unix utility. Notice it not coincidentally has the same name as the system call which sets permissions on files. And here when we pass it an argument u plus x that tells chmod to turn on execute permission for the user that owns this file foo.sh. So having done that assuming we're running a shell with the same user account as the owner of the foo.sh file we can run the command dot slash foo.sh to run this script. And again remember that in this case we're running the script in a sub shell not the current shell. So that's everything we're going to cover in this unit about bash but what did I not cover? Well I only talked about a handful of built-ins whereas in total there are about 70 of them nor did I mention many of the standard Unix utility programs. We mentioned LS but that's pretty much it. There are over a hundred utilities a few dozen of which are used quite commonly and we only talked about one or two. So if you're going to want to use the shell you're definitely going to want to learn to use more of the Unix utilities and also you're going to want to learn at least a handful more of the built-ins. And aside from those two major areas there's a handful of other features that I didn't discuss like for example it's possible to have values which aren't strings but are actually arrays and there are other features like aliases and there's what's called the history mechanism and also when I talked about substitutions and expansions I glossed over some of the more complicated forms. Those are all what I would consider rather advanced uses of the shell and you can probably get by for years without ever using them but they are features which you may eventually wish to look into.