Ancient CS 61 Content Warning!!!!!1!!!
This is not the current version of the class.
This site was automatically translated from a wiki. The translation may have introduced mistakes (and the content might have been wrong to begin with).

Shell Exercises 1

1. Print the contents of the files fork_1.c, fork_2.c, and fork_3.c in the cs61-sections/s10 directory, in order, using a single command line.

$ cat f1; cat f2; cat f3
I love CS61.
OR
 
$ cat f1 f2 f3
I love CS61.

2. Repeat #1, but store the result in a file called cs61.

$ cat f1 f2 f3 > cs61
$ cat cs61
I love CS61.

3. Do #2 again but produce a different command line.

$ cat f1 > cs61; cat f2 >> cs61; cat f3 >> cs61

4. What is the exit status of true?

$ true; echo $?
0

Or:

$ true || echo "true exits nonzero"
$ true && echo "true exits zero"
true exits zero

5. What is the exit status of false?

$ false; echo $?
1

Or:

$ false || echo "false exits nonzero"
false exits nonzero
$ false && echo "false exits zero"

6. What is the exit status of curl http://ipinfo.io/ip?

$ curl curlmyip.com; echo $?
###.###.###.###
0

7. What is the exit status of curl cs61://ipinfo.io/ip?

$ curl cs61://curlmyip.com; echo $?
curl: (1) Protocol cs61 not supported or disabled in libcurl
1

8. curl a URL and print Success if the download succeeds or Fail if the download fails.

$ curl curlmyip.com && echo Success || echo Fail
###.###.###.###
Success
$ curl cccurlmyip.com && echo Success || echo Fail
curl: (6) Could not resolve host: cccurlmyip.com
Fail

9. Repeat the above, but downloading the URL to a file called ip.

$ curl curlmyip.com > ip && echo Success || echo Fail
  % Total    % Received % Xferd  Average
                                 Dload  
100    14  100    14    0     0    158  
Success

Notice that curl now prints a progress meter; this is printing to stderr!

$ curl curlmyip.com > ip 2> /dev/null && echo Success || echo Fail

10. Count the number of lines in the file words. Hint: use the wc utility.

$ wc -l words

11. Print every unique line in the file words exactly once.

$ sort -u words

12. Count the number of unique lines in the file words.

$ sort -u words | wc -l

13. Write a command that could help you discover whether a shell really executes the two sides of a pipeline in parallel. Describe the result if (1) the shell executed the left side to completion first (and buffered the output for the right side to read), (2) the shell executed the sides in parallel.

$ sleep 10 | echo foo

Or:

$ (echo left; sleep 1; echo ldone) | (echo right; cat; echo rdone)

Representing a Parsed Command Line Solutions

Tree representation

1. Sketch a set of C structures corresponding to this design.

struct command {
    const char** argv; /* other stuff for redirections, etc. */
    struct command* next_in_pipeline;
};
struct pipeline {
    struct command* first_command;
    struct pipeline* next_in_conditional;
    int conditional_operator;
};
struct conditional {
    struct pipeline* first_pipeline;
    struct conditional* next_in_list;
    int is_background;
};

There are many other solutions. Logically, we need a struct for lists that indicates whether each list should be run in the foreground or background, a struct for conditionals that indicates the link type, a struct for pipelines, and a struct for commands. Each struct needs to hold the necessary pointers to form a linked list, as well as a pointer to the first item of its sublist (i.e., the overall command line struct needs a pointer to the head of the lists linked list, each list struct needs to hold a pointer to the head of its conditionals linked list, etc).

2. Given this command structure, how do we traverse it to decide which commands to run at which times?

We execute each list sequentially, either in the foreground or the background. Within each list, we execute conditionals sequentially. Within each conditional, we execute a pipeline, and depending on its return value, execute successive commands sequentially.

3. Given this command structure, how do we traverse it to determine which structures should be executed in the foreground or the background?

Since lists are executed sequentially, all we need is for the list element—that is, the conditional—to know whether it should be executed in the foreground or background.

int check_background(struct conditional* c) {
    return c->is_background;
}

4. Given a command structure in a pipeline, how can shell code determine whether the command is on the left-hand side of a pipe?

A given pipeline consists of a linked list of commands. When traversing this list of commands, the pipeline flows from left to right with traversal of the linked list. The left-hand side is the command that preceded the current one. The left-hand side of the first command is stdin.

int check_left_side_of_pipe(struct command* c) {
    return c->next_in_pipeline != NULL;
}

5. Given a command structure in a pipeline, how can shell code determine whether the command is on the right-hand side of a pipe?

Akin to before, the right-hand side of a pipe is the command that follows the current one. The right-hand side of the last command is stdout. Given the structures above, we need a bit more information to detect a pipe’s right-hand side. We could, for example, make the pipeline doubly-linked, so each struct command gets a prev_in_pipeline member. Then:

int check_right_side_of_pipeline(struct command* c) {
    return c->prev_in_pipeline != NULL;
}

Flat Linked List

1. Write a set of C structures corresponding to this design.

struct command {
    const char** argv;
    struct command* next;
    int next_operator; /* always TOKEN_SEQUENCE or TOKEN_BACKGROUND for last in list */
};

2. Given a command structure corresponding to the first command in a conditional, how can shell code determine whether the conditional should be executed in the foreground or the background?

int check_background(struct command* c) {
    while (c->next_operator != TOKEN_SEQUENCE
           && c->next_operator != TOKEN_BACKGROUND) {
        c = c->next;
    }
    return c->next_operator == TOKEN_BACKGROUND;
}

Things are slightly more complicated with a flat linked list. We are still executing commands sequentially, but some subsequences of commands may need to be executed in the foreground or the background. During parsing, we can skip ahead in the command line to find the end of our current list (i.e., our current chain of conditionals). If our current list must be executed in the background, we can fork a new copy of the shell to start executing the commands in the background while starting to process the next list of commands.

3. Given a command structure in a pipeline, how can shell code determine whether the command is on the left-hand side of a pipe?

int check_left_side_of_pipe(struct command* c) {
    return c->next_operator == TOKEN_PIPE;
}

4. Given a command structure in a pipeline, how can shell code determine whether the command is on the right-hand side of a pipe?

We now need to walk the structure backwards! There are several ways to do this, but we could also just add a prev pointer.

int check_right_side_of_pipe(struct command* c) {
    return c->prev && c->prev->next_operator == TOKEN_PIPE;
}

5. Sketch out code for parsing a command line into these C structures.

Many solutions, depending on the exact structure of the linked list.