From b4b56de6dbee70e71cdf78f8d81a8fe167ab355d Mon Sep 17 00:00:00 2001 From: Greg Wilson Date: Mon, 27 Jan 2014 09:17:28 -0500 Subject: [PATCH] Seeding intermediate material on the bash shell --- bash/intermediate/01-perm.md | 23 ++ bash/intermediate/02-ssh.md | 298 ++++++++++++++++++++++ bash/intermediate/03-var.md | 259 +++++++++++++++++++ bash/intermediate/04-job.md | 168 ++++++++++++ bash/intermediate/{README.md => guide.md} | 4 +- bash/intermediate/index.md | 23 ++ bash/intermediate/reference.md | 7 + 7 files changed, 780 insertions(+), 2 deletions(-) create mode 100644 bash/intermediate/01-perm.md create mode 100644 bash/intermediate/02-ssh.md create mode 100644 bash/intermediate/03-var.md create mode 100644 bash/intermediate/04-job.md rename bash/intermediate/{README.md => guide.md} (55%) create mode 100644 bash/intermediate/index.md create mode 100644 bash/intermediate/reference.md diff --git a/bash/intermediate/01-perm.md b/bash/intermediate/01-perm.md new file mode 100644 index 000000000..f99d53043 --- /dev/null +++ b/bash/intermediate/01-perm.md @@ -0,0 +1,23 @@ +--- +layout: lesson +root: ../.. +title: Permissions +level: intermediate +--- +
+## Objectives +* FIXME +
+ +## Lesson + +
+## Key Points +* FIXME +
+ +
+## Challenges + +1. FIXME +
diff --git a/bash/intermediate/02-ssh.md b/bash/intermediate/02-ssh.md new file mode 100644 index 000000000..d012b6b33 --- /dev/null +++ b/bash/intermediate/02-ssh.md @@ -0,0 +1,298 @@ +--- +layout: lesson +root: ../.. +title: Working Remotely +level: intermediate +--- +
+## Objectives +* FIXME +
+ +## Lesson + +Let's take a closer look at what happens when we use a desktop or laptop +computer. The first step is to log in so that the operating system knows +who we are and what we're allowed to do. We do this by typing our +username and password; the operating system checks those values against +its records, and if they match, runs a shell for us. + +As we type commands, the 1's and 0's that represent the characters we're +typing are sent from the keyboard to the shell. The shell displays those +characters on the screen to represent what we type, and then, if what we +typed was a command, the shell executes it and displays its output (if +any). + +![Direct Shell Usage](img/web/direct-shell-usage.png) + +What if we want to run some commands on another machine, such as the +server in the basement that manages our database of experimental +results? To do this, we have to first log in to that machine. We call +this a [remote login](gloss.html#remote-login), and the other computer a +remote computer. Once we do this, everything we type is passed to a +shell running on the remote computer. That shell interacts runs those +commands on our behalf, just as a local shell would, then sends back +output for our computer to display: + +![Remote Shell Usage](img/web/remote-shell-usage.png) + +The tool we use to log in remotely is the [secure +shell](gloss.html#secure-shell), or SSH. In particular, the command +`ssh username@computer` runs SSH and connects to the remote computer we +have specified. After we log in, we can use the remote shell to use the +remote computer's files and directories. Typing `exit` or Control-D +terminates the remote shell and returns us to our previous shell. In the +example below, we use highlighting to show our interaction with the +remote shell. We can also see that the remote machine's command prompt +is `moon>` instead of just `$`, and that it took Vlad a couple of tries +to remember his password: + +~~~ +$ pwd +/users/vlad + +$ ssh vlad@moon +Password: *** +Access denied +Password: ******** +moon> pwd +/home/vlad +moon> ls -F +bin/ cheese.txt dark_side/ rocks.cfg +moon> exit + +$ pwd +/users/vlad +~~~ + +The secure shell is called "secure" to contrast it with an older program +called `rsh`, which stood for "remote shell". Back in the day, when +everyone trusted each other and knew every chip in their computer by its +first name, people didn't encrypt anything except the most sensitive +information when sending it over a network. However, that meant that +villains could watch network traffic, steal usernames and passwords, and +use them for all manner of nefarious purposes. SSH was invented to +prevent this (or at least slow it down). It uses several sophisticated, +and heavily tested, encryption protocols to ensure that outsiders can't +see what's in the messages going back and forth between different +computers. A [later chapter](security.html) will talk about how this +works, and how secure it really is. + +`ssh` has a companion program called `scp`, which stands for "secure +copy". It allows us to copy files to or from a remote computer using the +same kind of connection as SSH. The syntax is a simple mix of `cp`'s and +`ssh`'s. To copy a file, we specify the source and destination paths, +either of which may include computer names. If we leave out a computer +name, `scp` assumes we mean the machine we're running on. For example, +this command copies our latest results to the backup server in the +basement, printing out its progress as it does so: + +~~~ +$ scp results.dat vlad@backupserver:backups/results-2011-11-11.dat +Password: ******** +results.dat 100% 9 1.0 MB/s 00:00 +~~~ + +Copying a whole directory is similar: we just use the `-r` option to +signal that we want copying to be recursive. For example, this command +copies all of our results from the backup server to our laptop: + +~~~ +$ scp -r vlad@backupserver:backups ./backups +Password: ******** +results-2011-09-18.dat 100% 7 1.0 MB/s 00:00 +results-2011-10-04.dat 100% 9 1.0 MB/s 00:00 +results-2011-10-28.dat 100% 8 1.0 MB/s 00:00 +results-2011-11-11.dat 100% 9 1.0 MB/s 00:00 +~~~ + +Now suppose we want to check whether we have already created the file +`backups/results-2011-11-12.dat` on the backup server. Instead of +logging in and then typing `ls`, we could do this: + +~~~ +$ ssh vlad@backupserver ls results +Password: ******** +results-2011-09-18.dat results-2011-10-28.dat +results-2011-10-04.dat results-2011-11-11.dat +~~~ + +SSH has taken the arguments after our username and the name of the +computer we want to run on and passed them to the shell on the remote +computer. Since those arguments are a legal command, the remote shell +has run `ls results` for us and sent the output back to our local shell +for display. + +### Creating and Managing Keys + +Typing in our password every time we want to access a remote machine is +more than a minor annoyance. Imagine what would happen if we wanted to +run a program on a remote machine for all combinations of three +different parameters. We want to do something like this: + +~~~ +for density in {20..29} +do + for viscosity in 0.70 0.71 0.72 0.73 0.74 + do + for temperature in 0.001 0.002 0.003 0.004 0.005 + do + ssh vlad@fastmachine ./simulation -x -d $density -v $viscosity -v $temperature + done + done +done +~~~ + +If we actually try to do this, though, we will have to sit at our +keyboard and type in our password 250 times. What we want is a way to +authenticate ourselves to the remote computer automatically. + +We can do this using a technique borrowed from [public key +cryptography](gloss.html#public-key-cryptography). More specifically, we +will create a [key pair](gloss.html#key-pair) consisting of a [public +key](gloss.html#public-key) and a [private key](gloss.html#private-key). +These keys have two interesting properties: + +1. Anything that one encrypts, the other can decrypt. For example, if + we encrypt our password with the private key, only the public key + can decrypt it, while if we encrypt the contents of a file with the + public key, only the private key can decrypt it. +2. Given one key, it is practically impossible to find the other, where + "practically impossible" means "can't be done in the expected + lifetime of the universe using any computer we can conceive of" + (though quantum computing may one day change that—consult your + nearest wild-eyed physicist for details). + +Once we have created a key pair, we can put the public key on the remote +machine we want access to, and keep the private key on our local +machine. So long as they are where SSH expects them to be, it will use +them instead of asking us for a password. + +The first step is to create the key pair, which we do using +`ssh-keygen`: + +~~~ +$ ssh-keygen -t rsa +Generating public/private rsa key pair. +Enter file in which to save the key (/users/vlad/.ssh/id_rsa): ↵ +Enter passphrase (empty for no passphrase): ↵ +Your identification has been saved in /users/vlad/.ssh/id_rsa. +Your public key has been saved in /users/vlad/.ssh/id_rsa.pub. +The key fingerprint is: d3:1a:27:38:aa:54:e8:a5:03:db:79:2f:b2:c3:c9:3d +~~~ + +The `-t rsa` option tells `ssh-keygen` to create an RSA key; there are +other types, but this one is the most commonly used. The "↵" character +indicates a carriage return: we want to put the key in the default +location so that SSH will know where to find it, and we don't want a +passphrase (since the whole point is to be able to log in without typing +a password), so we just type enter in response to both questions. + +Let's look in the `.ssh` directory under our home directory: + +~~~ +$ cd +$ ls .ssh +id_rsa id_rsa.pub +~~~ + +The first file, `id_rsa`, contains our private key. Never put this on a +remote machine, send it by email, or share it with anyone (unless you +really want them to be able to impersonate you). The other file, +`id_rsa.pub`, contains the matching public key. Let's copy it onto the +remote machine we want to access: + +~~~ +$ scp .ssh/id_rsa.pub vlad@fastmachine:id_rsa.pub +Password: ******** +id_rsa.pub 100% 1 1.0 MB/s 00:00 +~~~ + +We still have to type our password because the public key isn't in the +right place on the remote machine when we run `scp`. Let's take care of +that by logging into the remote machine and creating a `.ssh` directory +there: + +~~~ +$ ssh vlad@fastmachine +Password: ******** + +$ mkdir .ssh +~~~ + +The next step is to copy the public key into a file in the `.ssh` +directory called `authorized_keys`: + +~~~ +$ cp id_rsa.pub .ssh/authorized_keys +~~~ + +The final step is to make sure that permissions are set properly on +`.ssh` and `authorized_keys`. This is an extra security measure: if +anyone but us can read or modify them, SSH will assume that they aren't +secure any longer. The right permissions are: + +* owner has read, write, and execute for the `.ssh` directory; +* owner has read and write for `.ssh/authorized_keys`; and +* nobody has anything else. + +The correct commands are: + +~~~ +$ chmod u=rwx,g=,o= .ssh +$ chmod u=rw,g=,o= .ssh/authorized_keys +~~~ + +We're all set. Let's exit from the remote shell and try running a +command to see if everything is working: + +~~~ +$ exit +$ ssh vlad@fastmachine pwd +/home/vlad +~~~ + +Here is what is on both machines after we created and installed the +keys: + +![Public/Private Keys](img/web/public-private-keys.png) + +We can now run `ssh` (and `scp`) from our local machine to the remote +machine without having to authenticate every time. This only works one +way, though: having the public key installed in the remote machine's +`authorized_keys` file does *not* give that machine permission to log in +to our local machine. If we wanted to do that, we would have to generate +a key pair on the remote machine and copy its `id_rsa.pub` to our local +`authorized_keys` file. + +What if we want to connect to a machine from several other machines? For +example, suppose we want to be able to copy files to and from the backup +server from our laptop, our desktop computer, and the machine in the +lab. To handle this, we add each machine's `id_rsa.pub` file to the +remote machine's `.ssh/authorized_keys` file. We can do this with an +editor, or more simply use `cat` and `>>` to append one file to another: + +~~~ +$ scp id_rsa.pub vlad@backupserver +Password: ******** + +$ ssh vlad@backupserver +Password: ******** + +$ cat id_rsa.pub >> .ssh/authorized_keys + +$ rm id_rsa.pub + +$ exit +~~~ + +
+## Key Points +* FIXME +
+ +
+## Challenges + +1. FIXME +
diff --git a/bash/intermediate/03-var.md b/bash/intermediate/03-var.md new file mode 100644 index 000000000..012320b94 --- /dev/null +++ b/bash/intermediate/03-var.md @@ -0,0 +1,259 @@ +--- +layout: lesson +root: ../.. +title: Variables +level: intermediate +--- +
+## Objectives +* FIXME +
+ +## Lesson + +The shell is just a program, and like other programs, it has variables. +Those variables control its execution, and by changing their values, you +can change how the shell and other programs behave. + +Let's start by running the command `set` and looking at some of the +variables in a typical shell session: + +~~~ +$ set +COMPUTERNAME=TURING +HOME=/home/vlad +HOMEDRIVE=C: +HOSTNAME=TURING +HOSTTYPE=i686 +NUMBER_OF_PROCESSORS=4 +OS=Windows_NT +PATH=/usr/local/bin:/usr/bin:/bin:/cygdrive/c/Windows/system32:/cygdrive/c/Windows:/cygdrive/c/bin:/cygdrive/c/Python27 +PWD=/home/vlad +UID=1000 +USERNAME=vlad +... +~~~ + +As you can see, there are quite a few—in fact, four or five times more +than what's shown on this slide. And yes, using `set` to *show* things +might seem a little strange, even for Unix, but if you don't give it any +arguments, it might as well show you things you *could* set. + +Every variable has a name. By convention, variables that are always +present are given upper-case names. All shell variables' values are +strings, even those (like `UID`) that look like numbers. it's up to +programs to convert these strings to other types when necessary. For +example, if a program wanted to find out how many processors the +computer had, it would convert the value of the `NUMBER_OF_PROCESSORS` +variable from a string to an integer. + +Similarly, some variables (like `PATH`) store lists of values. In this +case, the convention is to use a colon ':' as a separator. If a program +wants the individual elements of such a list, it's the program's +responsibility to split the variable's string value into pieces. + +Let's have a closer look at that `PATH` variable. Its value defines the +shell's [search path](glossary.html#search-path), i.e., the directories +that the shell looks in for runnable programs. If you recall, when we +type a command like `./analyze` that has a specific directory in the +path, the shell runs the program that path specifies. Similarly, if we +type `/bin/analyze`, the shell runs that specific program: we've +provided a specific path, so it knows what to do. But which one should +the shell do if we just type `analyze`? + +The rule is simple: the shell checks each directory in the `PATH` +variable in turn, looking for a program with the rqeuested name in that +directory. As soon as it finds a match, it stops searching and runs the +program. + +To show how this works, here are the components of `PATH` broken out one +per line: + +~~~ +/usr/local/bin +/usr/bin +/bin +/cygdrive/c/Windows/system32 +/cygdrive/c/Windows +/cygdrive/c/bin +/cygdrive/c/Python27 +~~~ + +On our computer, there are actually three programs called `analyze` in +three different directories: `/bin/analyze`, `/usr/local/bin/analyze`, +and `/users/vlad/analyze`. Since the shell searches the directories in +order, it finds the one in `/bin`, not either of the others. Notice that +it will *never* find the program `/users/vlad/analyze`, since the +directory `/users/vlad` isn't in our path. + +Before we explore variables any further, let's introduce one more +command: `echo`. All it does is print out its arguments. This doesn't +sound very exciting, but we can use it to show variables' values. First, +let's make sure it works: + +~~~ +$ echo hello transylvania! +hello transylvania! +~~~ + +Now let's try to show the value of the variable `HOME`: + +~~~ +$ echo HOME +HOME +~~~ + +That just prints "HOME", which isn't what we wanted. Let's try this +instead: `echo $HOME`: + +~~~ +$ echo $HOME +/home/vlad +~~~ + +The dollar sign tells the shell to replace the variable's name with its +value. This works just like wildcards: the shell does the replacement +*before* running the program we've asked for. Thanks to this expansion, +what we actually run is `echo /home/vlad`, which displays the right +thing. + +Creating a variable is easy: just assign a value to a name using "=": + +~~~ +$ SECRET_IDENTITY=Dracula + +$ echo $SECRET_IDENTITY +Dracula +~~~ + +To change the value, just assign a new one: + +~~~ +$ SECRET_IDENTITY=Camilla + +$ echo $SECRET_IDENTITY +Camilla +~~~ + +Now for the complicated bit. Assignment only changes a variable's value +in the current shell, not in any other shells that are currently +running, or in any shells that are started later. To see what this +means, let's go back and set our secret identity once again: + +~~~ +$ SECRET_IDENTITY=Dracula + +$ echo $SECRET_IDENTITY +Dracula +~~~ + +Once it's set, let's run a fresh copy of the shell by typing the command +`bash`. Remember, the shell is just another program: asking it to run a +fresh instance of itself in a new process is no different from asking it +to run `ls`, `ps`, or anything else. + +~~~ +$ bash +~~~ + +Nothing seems to have happened, but we now have two copies of the shell +running. We don't see anything signalling this on the screen because the +new shell prints the same prompt as the old one, but our keyboard input +and screen output are now tied to the child shell. + +![Running a Shell from the Shell](../img/shell/shell-on-shell.png) + +If we `echo $SECRET_IDENTITY` in the child shell, nothing is printed, +because the variable doesn't have a value in the child shell: it was +only set in the original shell. But if we exit the child shell and +return to the original, we can see that yes, the variable does exist. +Here's the whole sequence of commands with the ones executed in the +child shell highlighted: + +~~~ +$ SECRET_IDENTITY=Dracula + +$ echo $SECRET_IDENTITY +Dracula + +$ bash + +$ echo $SECRET_IDENTITY + +$ exit + +$ echo $SECRET_IDENTITY +Dracula +~~~ + +If we really want the shell to pass a variable to the processes it +creates, we must use the `export` command. Let's try the secret identity +example again. After giving `SECRET_IDENTITY` a value, we give the shell +the command `export SECRET_IDENTITY`: + +~~~ +$ SECRET_IDENTITY=Dracula + +$ export SECRET_IDENTITY +~~~ + +Note that it's *not* `export $SECRET_IDENTITY` with a dollar sign: if we +typed that, the shell would expand `SECRET_IDENTITY`, and our `export` +command would actually be `export Dracula`, which would do nothing, +because there's no variable called `Dracula`. + +Now let's run a new shell, and type `echo $SECRET_IDENTITY`. There's our +variable. And of course, exiting brings us back to our original shell. + +~~~ +$ bash + +$ echo $SECRET_IDENTITY +Dracula + +$ exit +~~~ + +If we want to set some variables' values automatically every time we run +a shell, we can put the command to do this in a file called `.bashrc` in +our home directory. (The '.' character at the front prevents `ls` from +listing this file unless we specifically ask it to using `-a`: we +normally don't want to worry about it. The "rc" at the end is an +abbreviation for "run control", which meant something really important +decades ago, and is now just a convention everyone follows without +understanding why.) For example, here are two lines in Vlad's `.bashrc` +file, which is in `/home/vlad/.bashrc`: + +~~~ +export SECRET_IDENTITY=Dracula +export TEMP_DIR=/tmp +export BACKUP_DIR=$TEMP_DIR/backup +~~~ + +These two lines create the variables `SECRET_IDENTITY` and `BACKUP_DIR`, +give them values, and export them so that any programs the shell runs +can see them as well. Notice that `BACKUP_DIR`'s definition relies on +the value of `TEMP_DIR`, so that if we change where we put temporary +files, our backups will be relocated automatically. + +While we're here, it's also common to use the `alias` command to create +shortcuts for things we frequently type. For example, we can define the +alias `backup` to run `/bin/zback` with a specific set of arguments: + +~~~ +alias backup=/bin/zback -v --nostir -R 20000 $HOME $BACKUP_DIR +~~~ + +As you can see, aliases can save us a lot of typing, and hence a lot of +typing mistakes. + +
+## Key Points +* FIXME +
+ +
+## Challenges + +1. FIXME +
diff --git a/bash/intermediate/04-job.md b/bash/intermediate/04-job.md new file mode 100644 index 000000000..129855627 --- /dev/null +++ b/bash/intermediate/04-job.md @@ -0,0 +1,168 @@ +--- +layout: lesson +root: ../.. +title: Job Control +level: intermediate +--- +
+## Objectives +* FIXME +
+ +## Lesson + +Our next topic is how to control programs *once they're running*. This +is called [job control](glossary.html#job-control), and while it's less +important today than it was back in the Dark Ages, it is coming back +into its own as more people begin to leverage the power of computer +networks. + +When we talk about controlling programs, what we really mean is +controlling *processes*. As we said earlier, a process is just a program +that's in memory and executing. Some of the processes on your computer +are yours: they're running programs you explicitly asked for, like your +web browser. Many others belong to the operating system that manages +your computer for you, or, if you're on a shared machine, to other +users. You can use the `ps` command to list them, just as you use `ls` +to list files and directories: + +~~~ +$ ps +PID PPID PGID TTY UID STIME COMMAND +2152 1 2152 con 1000 13:19:07 /usr/bin/bash +2276 2152 2276 con 1000 14:53:48 /usr/bin/ps +~~~ + +Every process has a unique process id (PID). Remember, this is a +property of the process, not of the program that process is executing: +if you are running three instances of your browser at once, each will +have its own process ID. + +The second column in this listing, PPID, shows the ID of each process's +parent. Every process on a computer is spawned by another, which is its +parent (except, of course, for the bootstrap process that runs +automatically when the computer starts up). + +The third column (labelled PGID) is the ID of the *process group* this +process belongs to. We won't discuss process groups in this lecture, but +they're often used to manage sets of related processes. Column 4 shows +the ID of the terminal this process is running in. Once upon a time, +this really would have been a terminal connected to a central timeshared +computer. It isn't as important these days, except that if a process is +a system service, such as a network monitor, `ps` will display a +question mark for its terminal, since it doesn't actually have one. + +Column 5 is more interesting: it's the user ID of the user this process +is being run by. This is the user ID the computer uses when checking +permissions: the process is allowed to access exactly the same things as +the user, no more, no less. + +Finally, Column 6 shows when the process started running, and Column 7 +shows what program the process is executing. Your version of `ps` may +show more or fewer columns, or may show them in a different order, but +the same information is generally available everywhere. + +The shell provides several commands for stopping, pausing, and resuming +processes. To see them in action, let's run our `analyze` program on our +latest data files. After a few minutes go by, we realize that this is +going to take a while to finish. Being impatient, we kill the process by +typing Control-C. This stops the currently-executing program right away. +Any results it had calculated, but not written to disk, are lost. + +~~~ +$ ./analyze results*.dat +...a few minutes pass... +^C +~~~ + +Let's run that same command again, with an ampersand `&` at the end of +the line to tell the shell we want it to run in the +[background](glossary.html#background): + +~~~ +$ ./analyze results*.dat & +~~~ + +When we do this, the shell launches the program as before. Instead of +leaving our keyboard and screen connected to the program's standard +input and output, though, the shell hangs onto them. This means the +shell can give us a fresh command prompt, and start running other +commands, right away. Here, for example, we're putting some parameters +for the next run of the program in a file: + +~~~ +$ cat > params.txt +density: 22.0 +viscosity: 0.75 +^D +~~~ + +(Remember, \^D is the shell's way of showing Control-D, which means "end +of input".) Now let's run the `jobs` command, which tells us what +processes are currently running in the background: + +~~~ +$ jobs +[1] ./analyze results01.dat results02.dat results03.dat +~~~ + +Since we're about to go and get coffee, we might as well use the +foreground command, `fg`, to bring our background job into the +foreground: + +~~~ +$ fg +...a few minutes pass... +~~~ + +When `analyze` finishes running, the shell gives us a fresh prompt as +usual. If we had several jobs running in the background, we could +control which one we brought to the foreground using `fg %1`, `fg %2`, +and so on. The IDs are *not* the process IDs. Instead, they are the job +IDs displayed by the `jobs` command. + +The shell gives us one more tool for job control: if a process is +already running in the foreground, Control-Z will pause it and return +control to the shell. We can then use `fg` to resume it in the +foreground, or `bg` to resume it as a background job. For example, let's +run `analyze` again, and then type Control-Z. The shell immediately +tells us that our program has been stopped, and gives us its job number: + +~~~ +$ ./analyze results01.dat +^Z +[1] Stopped ./analyze results01.dat +~~~ + +If we type `bg %1`, the shell starts the process running again, but in +the background. We can check that it's running using `jobs`, and kill it +while it's still in the background using `kill` and the job number. This +has the same effect as bringing it to the foreground and then typing +Control-C: + +~~~ +$ bg %1 + +$ jobs +[1] ./analyze results01.dat + +$ kill %1 +~~~ + +Job control was important when users only had one terminal window at a +time. It's less important now: if we want to run another program, it's +easy enough to open another window and run it there. However, these +ideas and tools are making a comeback, as they're often the easiest way +to run and control programs on remote computers elsewhere on the +network. We'll look at how to do this [later in this chapter](#s:ssh). + +
+## Key Points +* FIXME +
+ +
+## Challenges + +1. FIXME +
diff --git a/bash/intermediate/README.md b/bash/intermediate/guide.md similarity index 55% rename from bash/intermediate/README.md rename to bash/intermediate/guide.md index f9909f249..25d269597 100644 --- a/bash/intermediate/README.md +++ b/bash/intermediate/guide.md @@ -1,7 +1,7 @@ --- layout: lesson root: ../.. -title: The Unix Shell +title: Instructor's Guide level: intermediate --- -FIXME: to be written. +FIXME \ No newline at end of file diff --git a/bash/intermediate/index.md b/bash/intermediate/index.md new file mode 100644 index 000000000..2564b3bad --- /dev/null +++ b/bash/intermediate/index.md @@ -0,0 +1,23 @@ +--- +layout: lesson +root: ../.. +title: More Unix Shell +level: Intermediate +--- +FIXME: intro + +Topics +------ +1. [Permissions](01-perm.html) +2. [Working Remotely](02-ssh.html) +3. [Variables](03-var.html) +4. [Job Control](04-job.html) + +See Also +-------- +* [Instructor's Guide](guide.html) +* [Reference](reference.html) + +Resources +--------- +* FIXME diff --git a/bash/intermediate/reference.md b/bash/intermediate/reference.md new file mode 100644 index 000000000..ba530bfdd --- /dev/null +++ b/bash/intermediate/reference.md @@ -0,0 +1,7 @@ +--- +layout: lesson +root: ../.. +title: Shell Reference +level: intermediate +--- +FIXME