In this lab, we’ll learn how to use basic command-line tools. If there’s time, we’ll also learn a bit about a source control system called git
.
Note that the Documentation and Handouts page of the course website has a command-line tools introduction PDF that covers some of the same material. For today, we’ll be learning about the basic commands:
cd ls
mv cp
rm rmdir
mkdir man
ssh scp
We’ll also learn about how to redirect output and some useful commands like:
less
wc
grep
xargs
Finally, we’ll be learning the basics of text editing with vim
, and how to compile and run Java code using javac
and java
.
For this lab, we’ll be doing all of our work on little.cs.pomona.edu
, which is one of the department’s common servers. Because the server isn’t hooked up to a monitor, we’ll be forced to use the command line to do everything. Start by connecting to little.cs.pomona.edu
using the command:
ssh yourusername@little.cs.pomona.edu
If you are connecting from one of the lab machines, you can leave off the “yourusername” part, as the local username will be the same as the remote one. You will have to type in your password, and then you’ll have a terminal that’s running on the remote machine. Because your home directory (/home/yourusername
, which can be abbreviated as ~
) is actually mounted remotely from a common file server, the files in your home directory on little.cs.pomona.edu
are the same as those on the lab machines. You can see this if you run the command:
touch ~/Desktop/hello.txt
Which will create a file called hello.txt
on your desktop. The file should show up on the desktop of the lab computer you’re using within a few seconds. For this lab we’ll be working exclusively through the terminal though.
To start the lab, cd into your desktop (cd ~/Desktop
) and then copy over the starter files using the cp
command:
cp -r /common/cs/cs062/labs/lab08 lab08
Notice that like all shell commands, cp
takes arguments separated by spaces. Some arguments start with -
and are called “flags” or “switches”—they alter the behavior of the command in some way. Other arguments are just listed out in order, and they tell the program what to do or operate on. In the case of cp
, the first argument(s) are the thing(s) to copy, while the last argument is the directory to put them in (or the new name to copy a single source file as).
For cp
, the ‘-r’ flag forces it to copy entire directories ’r’ecursively, rather than just copying individual files. But what if you wanted to figure that out? The first and most useful program we’ll talk about today is called man
, which stands for manual
. man
can be used to get information about most other programs, so you can type man cp
to get the manual page for the cp
program. This will show how the command should be used (in the “SYNOPSIS” section) and list all of the different possible arguments along with what they do). If you want to figure out how to do something with a command, try to use the manual to find what you’re looking for.
Notice that at the bottom of the manual display it says “press h for help or q to quit”. The program man
looks up a manual page, but then uses another program, called less
to display it. less
is nice because it allows you to search for things: just type /
followed by the text you want to search for. So to find out what ‘-r’ does, we can do man cp
and then type /-r
to see the part that describes the -r
flag. You can view any file using less
by just typing less <filename>
.
To help you get familiar with some basic shell commands, the starter files include some simple tasks. The first is to separate out some image and text files. Go ahead and cd
into the images-and-text/
directory. Now is a good time to be reminded that you can use the TAB key to auto-complete the names of files when typing in a shell. So you can type cd im<TAB>
instead of cd images-and-text
. This both saves you keystrokes and ensures that you don’t accidentally misspell the filename. If you just hit TAB several times without typing anything, the shell should list out all of the possibilities. You can use this to navigate when using cd
to go deep into a directory structure.
Now that we’re in the images-and-text/
directory, use ls
to list the directory contents. Notice that there is a mix of .jpg and .txt files in this directory. Your first task is to sort these into an images/
directory and a text/
directory. Start by creating these directories using the mkdir
command. Now you can use the mv
command to move the .txt
files into the text/
directory and the .jpg
files into the images/
directory.
Moving each file individually is a bit of a chore. Instead, we can ask the shell to handle all files that match a pattern. The *
character in a shell argument will match any number of characters, so we can type mv *.txt text/
to move all of the .txt
files into the text
directory. Now we can use a second wildcard command to move all the .jpg
files into the images/
directory.
How many text files are there? We could count by hand, but instead we’ll use another shell trick to find out. There’s a handy program called wc
which counts words (and/or lines or characters). Notice that if you run just wc
nothing happens. In fact, it cuts off your terminal (try typing ls
)! Luckily, there’s a universal shortcut for interrupting the current process: hit control-C and the rogue wc
process will be killed, giving us back our normal prompt.
What happened? Well, if we look at man wc
we can see that if no “FILE” is given, it will “read standard input.” This means that when we ran wc
it was expecting us to type in its input. That’s all fine and good, but how do we tell it that we’re done? Well, the terminal has another shortcut for that: control-D will tell a program that you’re done giving it input (be careful: if you hit control-D in the outer shell, it’ll quit the ssh
session, and you’ll have to log in again). So we can type wc
and then a<enter>b<enter>c<enter><control-D>
and it should print out 3 3 6
which is the line-count, word-count, and character-count of the input, respectively. Notice from man wc
that we can give it the -l
flag to count just lines.
But how can we get wc
to count files? It can read what we type or something from a file, but we want it to read the output from the ls
command. In the shell, there are two ways to do this. First, we can redirect output into a file, using the >
operator. So we could say ls > list
and then wc -l list
to count the number of files in the current directory. However, this would leave around an extra list
file every time we did it. Instead, we can redirect output directly from one program into another using the |
operator. So to count how many text files there are, assuming we’ve moved them all into the text/
directory, we can do:
ls text | wc -l
This should print out “10.” Note in this case that we didn’t give wc
any arguments (besides the flag), so it works in “read from standard input mode.” However, because of the |
redirection (also called a “pipe”) the standard input came from the output of the ls
command, instead of from us typing. This pattern of redirection is quite useful, and can be repeated multiple times, with several programs feeding their output into each other.
Now that we’ve sorted our text and image files, let’s move over to the programs
directory. To do this, use cd ../programs
(assuming you’re still in the images-and-text/
directory). In this directory are a few random Java programs that were downloaded from Pastebin. However, the directory is messy: it also has some .class
files in it from compiling the .java
files.
We want to find out which files contain drawing code. To do this, we’ll use a program called grep
, which searches for patterns in its input. To search for the pattern “draw” we can use:
grep draw *.java
Recall that *.java
matches all files in the current directory that end in .java
. The entry for man grep
tells us that it expects a “PATTERN” followed by one or more “FILEs,” so this command will search all of the matching files for the pattern “draw.” By default, it prints out each line that matches. But in this case, we only want to know which files contain that pattern, not how many times or what their content is. Luckily, grep
has a -l
flag which does exactly that: ’l’ists files that match. So we’ll use:
grep -l draw *.java
Notice that grep
doesn’t care about the order of the -l
flag: you can put it first, second or third, and the other arguments will be interpreted the same way.
Now that we know which files contain some kind of drawing code, let’s move them all to a new directory. Create a new directory called draws/
using mkdir
. Now all we need to do is issue an mv
command targeting all of our grep -l
files and putting them into this directory. Unfortunately, our grep -l
command produces output (which we could move around with a ‘|’ or put in a file with ‘>’) but what we want those things to be are arguments to the mv
command. We could redirect the output to a file using ‘>’ and then edit that file to add “mv” before them and “draws/” after them and then run that file as a script, but there’s a better way: a program called xargs
exists just for this purpose.
As man xargs
says, xargs
is designed to take input and turn it into arguments. Normally, these arguments are passed to the target program after any default arguments, but in our case, we want the variable arguments to come before the destination argument of mv
, so we need to use xargs
‘’-i’ flag to tell it where to put the variable arguments. So we’ll construct a command line that does the following:
grep -l draw *.java
to generate output.xargs -iZZ
xargs
additional arguments to tell it to execute the mv
command with our variable arguments followed by ‘draws/’. Use the ‘ZZ’ pattern that we gave to the ‘-i’ command to tell xargs
where to put the additional arguments.If you execute the command properly, you should be able to move all of the .java
files that contain the text “draw” into the draws/
folder. Note that there’s another way to do this: in the shell, the ‘`’ character (a.k.a. the “backtick”) can be used to do command substitution, where the output of a command is used as part of another command. With this method, we write our mv
command, and just use `grep -l draw *.java
` where we want the extra arguments to appear.
javac
Since we’ve got these Java programs sitting here, let’s see if we can get any to run. First, let’s create a new directory has_main/
and put all of the java files which declare a “public void main” method somewhere in them into that directory. We can use the same technique as we did with “draw”, with one caveat: we want to search for the pattern “public void main”, but that pattern has spaces in it. Spaces are also used to separate arguments, but only one argument can be the pattern. If we just say:
grep public static void main *.java
the first three lines will look like:
grep: static: No such file or directory
grep: void: No such file or directory
grep: main: No such file or directory
To tell grep
that we really want one big argument including spaces, we’ll use double quotes, like this:
grep "public static void main" *.java
Use this command with the pattern above to isolate all of the .java files which have main
methods into a has_main/
directory.
Now let’s try to compile them. To compile Java files, you use the program javac
which produces .class
files as output. Let’s start by trying to compile all of the .java
files:
javac *.java
Unfortunately, one of our .java
files has an error in it (well, they were randomly downloaded from Pastebin, so what can you expect). Let’s skip that file for now by just changing its name (using mv
to have an extra ‘a’ in the .java
part, so that our *.java
won’t pick it up). Now the command above should work1.
java
To run a Java program, we use the program java
. However, java
is a bit quirky: we need to tell it the name of a class to run, not just the name of a .class
file. Luckily in Java, the name of a .class
file should always be the name of the class it defines. Unluckily, Java requires that classes which are in packages be in a folder with the name of that package. To see which classes need packages, use grep
to print out lines containing the pattern “package” from .java
files in the current directory.
Now pick a non-packaged file and run the class, using java <classname>
. Depending on which you ran, it should either print a few numbers or hang. If it hangs, use <control-C>
to kill it. Try running a few different classes.
When compiling or running Java code, you often need to tell it where to find external code that your code depends on. For example, if your code depends on the structure5
library, you need to tell it where to find that code (at least the .class files). Both javac
and java
require this information whenever you use import
to import something that isn’t part of the Java standard library. You can give Java a list of places to look by using the --classpath
flag (short version: -cp
) followed by a colon-separated list of directories and/or .jar
files to search. So if we want to import structure5
along with other .java
files in the src/
directory, we’d give it:
-cp /common/cs/cs062/bailey.jar:src/
If we also wanted to use JUnit, we’d get:
-cp /common/cs/cs062/bailey.jar:/common/cs/cs062/junit.jar:src/
As a final convenience, you can separate the .class
files that javac
produces from their .java
files by giving the ‘-d’ flag followed by a directory to put output in. Using -d bin
as an argument along with -cp
, we can exactly duplicate what Eclipse does when we tell it to run our project. First, we’ll run javac
with the ‘-cp’ and ‘-d’ arguments as above, and then we’ll run java
with just the ‘-cp’ argument. If all goes well, you should be able to run your assignment 3 this way (it’s the only assignment so far that doesn’t require some kind of graphics, which are unavailable while ssh
’d onto another machine).
Now that you can move files around and even compile and run Java code from the terminal, what if you want to edit a file? As hinted at above, you can use less
to view the contents of a file and even search within it, but it doesn’t allow you to make changes.
To edit files on the command line, there’s a simple editor called nano
that lets you type text and do basic operations. It even displays available commands at the bottom of the screen. However, for this lab, you’ll be learning a tiny bit of how to use vim
, a more powerful and efficient editor.
vim
basicsTo open a file with vim
, type vim <filename>
. In this case, cd
into the edit
directory from the starter materials and type vim ed<TAB>
. Now that you’re in vim
, the most important thing to remember is how to quit: type ‘:’ followed by ‘q’ and then the enter/return key to quit. This works because in vim
, the ‘:’ key initiates a command, and ‘q’ is short for the ‘quit’ command.
vim
is a modal editor, which means that most keys on the keyboard execute commands instead of just inserting characters. To insert characters, you use the ‘i’ key to get into insert mode where typing produces text in the file you’re editing. When you’re done typing, use the escape key to get back to normal mode. Although this is confusing at first, if you get used to it it’s considerably more efficient when you enter lots of commands (as opposed to just typing text). In particular, vim
has a powerful set of commands for moving the cursor, which we can use to our advantage.
In the edit-me.txt
file, you’ll see a line that says “Delete me.” Let’s move to this line by using the ‘/’ command, which starts a search. Type “/elete<enter>” and the cursor should move to the first ‘e’ of the word “Delete.”2 Now you can type ‘dd’ to delete the current line. At any point, if you want to undo a command, just type ‘u’ (you can also use control-R to redo something you undid).
Next, move down to the line that says “Add a line after this”, either with a ‘/’ command, or using the arrow keys or the h/j/k/l keys, which move the cursor around in vim
. Use the ‘o’ command to start inserting on a new line after the current line, and type something in. Then hit escape to get back to normal mode.
Next, go to the line that asks you to add to its end. Here, use the ‘A’ command (shift-a) to append to the end of the line (while ‘i’ and ‘a’ ‘i’nsert and ’a’ppend respectively, ’I’ and ‘A’ do so at the beginning or end of the current line instead of at the cursor). Again, type something and hit escape.
Finally, let’s replace all of those targets with some other text. To do this, start by using a ‘/’ command to find the first target. Then notice that you can use the ‘n’ and ‘N’ commands to move forwards and backwards among matches to the last pattern. Now, we’ll use the ‘c’ command (for ‘c’orrect). The ’c’ command is special, because it wants to be told what to correct. After ‘c’, you can give vim
a “movement command” to correct text from the cursor to the end of the movement. So “cl” would correct just the character under the cursor (‘l’ moves the cursor right one character), while “cj” would correct two entire lines (‘j’ moves down a line). We’ll use a special movement command “iw” that means “the current word.” So our whole command will be “ciw”, after which we’ll type some replacement text and then hit escape.
Conveniently, this “ciw” command, up to the point we hit escape, counts as a single command. And vim
happens to have a shortcut for “repeat last non-movement command:” the “.” command. So to replace all of the targets quickly, we can now just hit “n” followed by “.” several times. This “.” command makes vim
’s other commands much more useful, because it’s easy to repeat them (vim
also has a full-fledged macro-creation system).
Now that we’ve got the basics down, let’s try a more complicated task. Type “:tabe ge<TAB>” to edit the “geology.c” file in a new tab (:tabe is short for :tabedit; notice you can tab-complete filenames; to move between tabs, you can use “gt” and “gT” to go forwards/backwards). This is a complicated C file that’s part of a larger program, and we’ll be using it to try out some vim
commands.
Imagine that we’ve just learned that the construct_efd_int_node
function needs to take a global variable called WORLD
as its second argument instead of NULL
. We want to replace NULL
throughout the file with WORLD
, but only when it’s an argument to the construct_efd_int_node
function. First, however, we’re going to get a bird’s-eye view of our targets.
To see all of the instances of construct_efd_int_node
in this file at once, we’ll use grep
to filter the file. We could go back to the command line, run grep
, and then re-open the file, but there’s a simpler way. First, type “0gg” to warp to the 0th line of the file. Then type “V” to enter “visual-line” mode, where you can select lines to operate on. Next, type “G” to warp to the last line of the file. Now that you’ve selected the whole file, type “!grep construct_efd_int<enter>” to filter those lines through grep
. This gives us a picture of the lines we want to edit. Once we’re done looking at it, we can use “u” to undo our filter and get back to the original file.
To find those lines again, use a “/” command to look for “constru.” The first hit will be a construct_efd_obj_node
call, which isn’t what we want, so hit “n” to get to a construct_efd_int_node
call. Now that we’re on the exact word we want to find, hit “*" to search for the word under the cursor. You can hit “n” and/or “N” a few times to convince yourself we’re finding the right stuff.
Now that we can skip through the file to the right lines, we need to be able to jump from the ‘c’ of “construct_efd_int_node” to the ‘N’ of “NULL.” Luckily, vim
has a command ‘f’ that skips ‘f’orward on the current line until the next one of a certain character. Type “fN” to skip to the next capital N on the current line, which will be the ’N’ of “NULL.” This skip command can be repeated in the forward direction with ‘;’ and in the backward direction with ‘,’. Since we’re now on the word we want to replace, use “ciw” again to replace the “NULL” with “WORLD.”
With everything set up, we can go through the file and replace our NULL’s by repeating the sequence “n;.” which first repeats the last earch, then repeats the last skip, and then repeats the last edit command. This is kind of like a customized find/replace, but we’re just doing it using basic vim
commands. Note that just using find/replace on NULL
won’t work, because we only want to change NULL
s that are arguments to construct_efd_int_node
.
One other important command in vim
is the “:w” command, which ’w’rites out (saves) the current file. Once you’re done editing geology.c, you can save it and quit it: “:wq” in an abbreviation that does both save and quit at once. You can also save/quit the edit-me.txt file now.
First, copy your edited geology.c
file into the submit/
folder. Then fill out the lab08.json
and answers.txt
files with your username(s) and the answers to the questions in that file respectively. Make sure you put your answers exactly the example answers are. It may be useful to know that the “D” command deletes text from the cursor position to the end of the current line.
When you’re done, rename the submit
directory using mv
to be called “Lab08_YourName_PartnersName” and submit directly on litle.cs.pomona.edu
using the submit
script:
/common/cs/cs062/bin/submit cs062 lab08 Lab08_Names
Note that you can query the return value of a command by running echo $?
. A value of “0” indicates success, any other value indicates some type of failure. The details of shell if statements and variables are too complex to go into in depth for this lab however.↩
Using the ‘shift’ key requires valuable pinky energy and should be avoided whenever possible :P↩