Skip to content
Snippets Groups Projects
Select Git revision
  • main
1 result

test.sh

Blame
  • readme.md 11.74 KiB

    Assignment: Custom Shell Part 1 - Command Line Parser

    This week we will begin the first of a multi-part assignment to build a custom shell called dsh (Drexel shell).

    What is a Shell?

    A "shell" is a type of user interface for interacting with an operating system. You are already familiar with Linux command line shells in this course - the integrated terminal in vscode runs a shell (probably "bash" if you are using any of the Linux virtualization options suggested).

    When we say your terminal "runs the bash shell" it means this: a shell is a generalized term for a binary that provides a terminal-based interface; "bash" is the name of a specific shell that you can install and run as a binary. Linux distributions have default shell configurations, and most of them default to bash. You can install new shells and use them as your default shell upon login; zsh is a popular shell that many users prefer over bash.

    The purpose of the shell is to broker inputs and outputs to built-in shell commands, other binaries, and kernel operations like syscalls. When you open a terminal in vscode, the shell is interactive and you must provide inputs by typing in the terminal. Shells are also a part of "headless" processes like cron jobs (in Linux cron jobs run binaries on a schedule automatically, even if you are not logged in); these background jobs still execute in the context of a shell. The shell provides the job the same interface to the operating system, and the job can use the output that the shell brokers.

    Shell built-in vs. external commands

    Most "commands" you run are not implemented in the shell logic; they are usually other binaries on the filesystem that are invoked when you refer to them from your shell.

    However ... there several (50+) commands that are built in to the logic of the shell itself. Here are some examples:

    • cd: change the current working directory
    • exit: exit from the shell process
    • echo: print text to STDOUT
    • export: set environment variable in the current shell

    In this assignment, we will implement one "built in" command: exit. There is also an optional dragon command for extra credit.

    Future assignments will generally follow the pattern of other popular shells and implement some of the common builtin commands.

    What else does the shell do?

    Beyond implementing built-in commands, a shell acts as a robust broker of input and output. Shells must provide a lot of "glue" to handle streams; for example this is a common sequence you might see in a linux shell:

    run-some-command | grep "keyword"

    The | symbol is called a pipe, and it serves to stream the output of the first command (run-some-command) into the input of the second command (grep). We'll implement much of this "glue" logic in future assignments in this course.

    Assignment Details

    In this assignment you will implement the first part of dsh: a command line parser to interpret commands and their arguments.

    You will not implement any command logic, other than exiting when the exit command is provided. There is also one optional extra credit for implementing the dragon command.

    Step 1 - Review ./starter/dshlib.h

    The file ./starter/dshlib.h contains some useful definitions and types. Review the available resources in this file before you start coding - these are intended to make your work easier and more robust!

    Step 2 - Implement ./starter/dsh_cli.c

    This contains the entrypoint of your shell. Detailed comments are provided to implement main.

    Shells usually run in a forever loop until a command is issued to exit the shell (usually exit); an example is provided in comments to get you started:

        while (1)
        {
            printf("%s", SH_PROMPT);
            if (fgets(cmd_buff, ARG_MAX, stdin) == NULL)
            {
                printf("\n");
                break;
            }
            // remove the trailing \n from cmd_buff
            cmd_buff[strcspn(cmd_buff, "\n")] = '\0';
    
            // IMPLEMENT THE REST OF THE REQUIREMENTS
        }

    The libc fgets function is a good choice here due to being "lines of input" based. This excerpt from man 3 fgets explains why:

    fgets() reads in at most one less than size characters from stream and stores them into the buffer pointed to by s. Reading stops after an EOF or a newline. If a newline is read, it is stored into the buffer. A terminating null byte ('\0') is stored after the last character in the buffer.

    Key point, Reading stops after an EOF or a newline - which makes it essentially a "line by line" processor. This is an important detail for step 3!

    Step 2a - Implement the built-in function exit

    Inside the main loop in main() you can check for and implement the logic for the exit command. When this command is issued, your process should exit with a 0 exit code.