Process Section 1: Shell commands

In today’s section, we'll investigate the behavior of a shell to kick off the process control unit and problem set 5.

College students are required to attend section live. TFs will record your attendance. For more on the attendance policy and what to do if you miss section refer to the syllabus here and here.

Extension students are welcome to attend section live (in-person or zoom) or watch the recording (Canvas > DCE Class Recordings). Both methods count towards your participation grade. If you attend a live section, TFs will record your attendance. If you watch the recording, fill out this reflection form.

Building intuition

The pset in this unit introduces a bunch of process control system calls through the medium of a shell implementation. A shell is a program responsible for running other programs; it’s the kind of program behind the command line terminal. You've been using a shell since you first started programming, probably mostly to compile and run your programs. But there are so many useful (and fun) things that you can accomplish using a command line that strings one or more shell commands together.

Common operators used to build command lines are:

Here are some commonly-installed, commonly-used programs that you can call directly from the shell. Documentation for these programs can be accessed via the man page, e.g. man cat, or often also through a help switch, e.g. cat --help.

Shell Program Description
cat Write named files (or standard input) to standard output.
wc Count lines, words, and characters in named files (or standard input).
head -n N Print first N lines of standard input.
head -c N Print first N characters of standard input.
tail -n N Print last N lines of standard input.
echo ARG1 ARG2... Print arguments to standard output.
printf FORMAT ARG... Print arguments with printf-style formatting.
true Silently succeed (exit with status 0).
false Silently fail (exit with status 1).
yes Print y (or any arguments) forever.
sort Sort lines in input.
uniq Drop duplicate lines in input (or print only duplicate lines).
tr Change characters; e.g., tr a-z A-Z makes all letters uppercase.
ps List processes.
curl URL Download URL and write result to standard output.
sleep N Pause for N seconds, then exit with status 0.
cut Cut selected portions of each line of a file.
grep PATTERN Print lines in named files (or standard input) that match a regular expression PATTERN.
tac Write lines in named files (or standard input) in reverse order to standard output.
exit [STATUS] Exit the shell with status STATUS (default 0).
sh -c "COMMANDLINE" Run another shell executing "COMMANDLINE".

Build some intuition for shell commands and operators with the following exercises. There is often more than one correct answer!

EXERCISE. Write a command line that compiles program P (using make) and runs it only if the compilation succeeded.

EXERCISE. Augment your last answer to redirect standard output and standard error to seperate files.

EXERCISE. Write a command line that lists all .cc files in the pset4 directory.

EXERCISE. Write a command line that finds all .cc files in the pset4 directory that contain the string io61_seek. Assume the shell is in the pset4 directory (meaning pwd would return /home/cs61-user/cs61-psets/pset4).

EXERCISE. Write a command line to download the contents of this webpage and then display the first ten lines in the terminal.

EXERCISE. Write a command line that tells you how many commits Eddie Kohler has made to a git repostory.

EXERCISE. Write a command line that tells you how many lines have been added to a git repository since the last commit.

EXERCISE. Write a command line that prints 'not ready for grading' if a student has not written their name in their AUTHORS.md, but otherwise doesn't print anything else. Hint: the handout AUTHORS.md file uses the placeholder (Your name.).

Command line breakdown

Now we'll formalize how complex command lines are built.

A command is a basic request to execute a program with some arguments. echo foo, for example, is a command. Pipelines, conditionals, and command lines combine commands to build more complex behaviors.

A precise way to explain command lines is with a BNF grammar. Here’s the shell grammar:

command_line ::= (empty)
          |  conditional
          |  conditional ";" command_line
          |  conditional "&" command_line

conditional ::=  pipeline
          |   conditional "&&" pipeline
          |   conditional "||" pipeline

pipeline ::=  command
          |   pipeline "|" command

command  ::=  word
          |   redirection
          |   command word
          |   command redirection

redirection  ::=  redirectionop filename
redirectionop  ::=  "<"  |  ">"  |  "2>"

The grammar says, for instance, that a command_line is zero or more conditionals, separated (and optionally terminated) by ; or &. It also defines conditionals in terms of pipelines, and pipelines in terms of commands.

EXERCISE. Test your understanding of the command line grammar. What are the conditionals in the following command line?

echo foo & echo bar | grep b && echo hello, there ; echo done < /dev/null

(It might be helpful to underline them.)

EXERCISE. What are the pipelines in that command line?

EXERCISE. What are the commands in that command line?

EXERCISE. What are the redirections in that command line, if any?

EXERCISE. Can any pipeline span across more than one conditional? Why or why not?

EXERCISE. Talk through the structure of the parser in phase 2 of the pset, and how it processes the command line from these exercises.

Command line scientists

In order to fully implement your shell you will need to completely understand the behavior of each shell operator. It’s possible to learn a lot by constructing “experiments”, namely command lines, and observing their behavior on a real shell!

For example, the behaviors of the && and || conditional operators can be explored with different combinations of programs like false, true, and echo.

# Hypothesis: `A && B` runs `B` iff `A` exits with status 0.
$ true && echo X
X
$ false && echo X
$ sh -c "exit 2" && echo X
# Hypothesis not falsified!


# Hypothesis: `A || B` runs `B` iff `A` does not exit with status 0.
$ true || echo X
$ false || echo X
X
$ sh -c "exit 2" || echo X
X
# Hypothesis not falsified!


# Hypothesis: The `sleep` command exits with status 1.
$ sleep 10 && echo Done
[10 seconds go by, then:] Done
# Hypothesis falsified 😢: it looks like `sleep` exits with status 0.

Indeed, it is true that A && B executes B only when A exits with status 0, and A || B executes B only when A does not exit with status 0!

In these exercises, you’ll attempt to explore more complex shell properties.

EXERCISE. Design command lines that test whether commands that are part of a single conditional are grouped left to right. That is, try and determine whether, given the chain A || B && C, commands are left associative:

  1. A || B runs first.
    1. So A runs first.
    2. Then, depending on A’s status, B runs or is skipped.
  2. Then, depending on A || B’s status, C runs or is skipped.

Or right associative:

  1. A runs first.
  2. Then, depending on A’s status, B && C runs or is skipped.

Or something entirely different.

EXERCISE. Design command lines that test whether two-process pipelines, such as A | B, execute A and B concurrently (at the same time) or serially (B does not start until A has completed).

EXERCISE. Design command lines that test whether the exit status of a conditional equals the exit status of the last-executed command in that conditional.

Hint. The $? shell variable equals the exit status of the most-recently-executed foreground conditional. For instance:

$ true
$ echo $?
0
$ false
$ echo $?
1
$ sh -c "exit 2"
$ echo $?
2

EXERCISE. Design command lines that test whether the exit status of a pipeline equals the exit status of its rightmost command. (Try to avoid using $?.)