{"id":24737439,"url":"https://github.com/engineersbox/unix-shell","last_synced_at":"2025-03-22T17:44:57.127Z","repository":{"id":192504197,"uuid":"683897939","full_name":"EngineersBox/UNIX-Shell","owner":"EngineersBox","description":"Implementation of a simple UNIX shell","archived":false,"fork":false,"pushed_at":"2023-09-04T05:18:40.000Z","size":1437,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-03T22:46:21.302Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EngineersBox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-08-28T02:39:41.000Z","updated_at":"2023-09-04T05:16:16.000Z","dependencies_parsed_at":null,"dependency_job_id":"863e3b2a-6754-418e-b43a-4c47a397888a","html_url":"https://github.com/EngineersBox/UNIX-Shell","commit_stats":null,"previous_names":["engineersbox/unix-shell"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EngineersBox%2FUNIX-Shell","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EngineersBox%2FUNIX-Shell/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EngineersBox%2FUNIX-Shell/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EngineersBox%2FUNIX-Shell/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EngineersBox","download_url":"https://codeload.github.com/EngineersBox/UNIX-Shell/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244999228,"owners_count":20544866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-27T22:08:20.906Z","updated_at":"2025-03-22T17:44:57.098Z","avatar_url":"https://github.com/EngineersBox.png","language":"C","readme":"# Unix Shell\n\nIn this project, you'll build a simple Unix shell. The shell is the heart of\nthe command-line interface, and thus is central to the Unix/C programming\nenvironment. Mastering use of the shell is necessary to become proficient in\nthis world; knowing how the shell itself is built is the focus of this\nproject.\n\nThere are three specific objectives to this assignment:\n\n* To further familiarize yourself with the Linux programming environment.\n* To learn how processes are created, destroyed, and managed.\n* To gain exposure to the necessary functionality in shells.\n\n## Overview\n\nIn this assignment, you will implement a *command line interpreter (CLI)* or,\nas it is more commonly known, a *shell*. The shell should operate in this\nbasic way: when you type in a command (in response to its prompt), the shell\ncreates a child process that executes the command you entered and then prompts\nfor more user input when it has finished.\n\nThe shells you implement will be similar to, but simpler than, the one you run\nevery day in Unix. If you don't know what shell you are running, it's probably\n`bash`. One thing you should do on your own time is learn more about your\nshell, by reading the man pages or other online materials.\n\n## Program Specifications\n\nYour basic shell, called `anubis` (short for ANU basic interactive shell), is\nbasically an interactive loop: it repeatedly prints a prompt `anubis\u003e ` (note\nthe space after the greater-than sign), parses the input, executes the command\nspecified on that line of input, and waits for the command to finish. This is\nrepeated until the user types `exit`.  The name of your final executable\nshould be `anubis`.\n\nThe shell can be invoked with either no arguments or a single argument;\nanything else is an error. Here is the no-argument way:\n\n```\nprompt\u003e ./anubis\nanubis\u003e \n```\n\nAt this point, `anubis` is running, and ready to accept commands. Type away!\n\nThe mode above is called *interactive* mode, and allows the user to type\ncommands directly. The shell also supports a *batch mode*, which instead reads\ninput from a batch file and executes commands from therein. Here is how you\nrun the shell with a batch file named `batch.txt`:\n\n```\nprompt\u003e ./anubis batch.txt\n```\n\nOne difference between batch and interactive modes: in interactive mode, a\nprompt is printed (`anubis\u003e `). In batch mode, no prompt should be printed.\n\nYou should structure your shell such that it creates a process for each new\ncommand (the exception are *built-in commands*, discussed below).  Your basic\nshell should be able to parse a command and run the program corresponding to\nthe command.  For example, if the user types `ls -la /tmp`, your shell should\nrun the program `/bin/ls` with the given arguments `-la` and `/tmp` (how does\nthe shell know to run `/bin/ls`? It's something called the shell **path**;\nmore on this below).\n\n## Required functionalities\n\n### (R1) Basic Shell\n\nThe shell is very simple (conceptually): it runs in a while loop, repeatedly\nasking for input to tell it what command to execute. It then executes that\ncommand. The loop continues indefinitely, until the user types the built-in\ncommand `exit`, at which point it exits. That's it!\n\nFor reading lines of input, you should use `getline()`. This allows you to\nobtain arbitrarily long input lines with ease. Generally, the shell will be\nrun in *interactive mode*, where the user types a command (one at a time) and\nthe shell acts on it. However, your shell will also support *batch mode*, in\nwhich the shell is given an input file of commands; in this case, the shell\nshould not read user input (from `stdin`) but rather from this file to get the\ncommands to execute.\n\nIn either mode, if you hit the end-of-file marker (EOF), you should call\n`exit(0)` and exit gracefully. \n\nTo parse the input line into constituent pieces, you might want to use\n`strsep()`. Read the man page (carefully) for more details.\n\nTo execute commands, look into `fork()`, `exec()`, and `wait()/waitpid()`.\nSee the man pages for these functions, and also read the relevant [book\nchapter](http://www.ostep.org/cpu-api.pdf) for a brief overview.\n\nYou will note that there are a variety of commands in the `exec` family; for\nthis project, you must use `execv`. You should **not** use the `system()`\nlibrary function call to run a command.  Remember that if `execv()` is\nsuccessful, it will not return; if it does return, there was an error (e.g.,\nthe command does not exist). The most challenging part is getting the\narguments correctly specified. \n\n### (R2) Paths\n\nIn our example above, the user typed `ls` but the shell knew to execute the\nprogram `/bin/ls`. How does your shell know this?\n\nIt turns out that the user must specify a **path** variable to describe the\nset of directories to search for executables; the set of directories that\ncomprise the path are sometimes called the *search path* of the shell. The\npath variable contains the list of all directories to search, in order, when\nthe user types a command. \n\n**Important:** Note that the shell itself does not *implement* `ls` or other\ncommands (except built-ins). All it does is find those executables in one of\nthe directories specified by `path` and create a new process to run them.\n\nTo check if a particular file exists in a directory and is executable,\nconsider the `access()` system call. For example, when the user types `ls`,\nand path is set to include both `/bin` and `/usr/bin`, try `access(\"/bin/ls\",\nX_OK)`. If that fails, try \"/usr/bin/ls\". If that fails too, it is an error.\n\nYour initial shell path should contain one directory: `/bin`\n\nNote: Most shells allow you to specify a binary specifically without using a\nsearch path, using either **absolute paths** or **relative paths**. For\nexample, a user could type the **absolute path** `/bin/ls` and execute the\n`ls` binary without a search path being needed. A user could also specify a\n**relative path** which starts with the current working directory and\nspecifies the executable directly, e.g., `./main`. In this project, you **do\nnot** have to worry about these features.\n\n### (R3) Built-in Commands\n\nWhenever your shell accepts a command, it should check whether the command is\na **built-in command** or not. If it is, it should not be executed like other\nprograms. Instead, your shell will invoke your implementation of the built-in\ncommand. For example, to implement the `exit` built-in command, you simply\ncall `exit(0);` in your anubis source code, which then will exit the shell.\n\nIn this project, you should implement `exit`, `cd`, and `path` as built-in\ncommands.\n\n* `exit`: When the user types `exit`, your shell should simply call the `exit`\n  system call with 0 as a parameter. It is an error to pass any arguments to\n  `exit`. \n\n* `cd`: `cd` always take one argument (0 or \u003e1 args should be signaled as an\nerror). To change directories, use the `chdir()` system call with the argument\nsupplied by the user; if `chdir` fails, that is also an error.\n\n* `path`: The `path` command takes 0 or more arguments, with each argument\n  separated by whitespace from the others. A typical usage would be like this:\n  `anubis\u003e path /bin /usr/bin`, which would add `/bin` and `/usr/bin` to the\n  search path of the shell. If the user sets path to be empty, then the shell\n  should not be able to run any programs (except built-in commands). The\n  `path` command always overwrites the old path with the newly specified\n  path. \n\n### (R4) Output Redirection\n\nMany times, a shell user prefers to send the output of a program to a file\nrather than to the screen. Usually, a shell provides this nice feature with\nthe `\u003e` character. Formally this is named as redirection of standard\noutput. To make your shell users happy, your shell should also include this\nfeature, but with a slight twist (explained below).\n\nFor example, if a user types `ls -la /tmp \u003e output`, nothing should be printed\non the screen. Instead, the standard output of the `ls` program should be\nrerouted to the file `output`. In addition, the standard error output of\nthe program should be rerouted to the file `output` (the twist is that this\nis a little different than standard redirection).\n\nIf the `output` file exists before you run your program, you should simple\noverwrite it (after truncating it).  \n\nThe exact format of redirection is a command (and possibly some arguments)\nfollowed by the redirection symbol followed by a filename. Multiple\nredirection operators or multiple files to the right of the redirection sign\nare errors.\n\nNote: don't worry about redirection for built-in commands (e.g., we will\nnot test what happens when you type `path /bin \u003e file`).\n\n### (R5) Pipes\n\nOne of the main design principles surrounding the design of a shell is\nthe ability to chain together a collection of simple utilities to perform \na more complex task. A particularly powerful chaining method is the ability\nto redirect the output of a program to the input of another program. This is\nalso known as a `pipe`, and is usually denoted with the symbol `|`.  \n\nFor example, if a user types in a command \n`ls -la /tmp | head -n 2`, it should print the first two lines of the output\nof the `ls -la /tmp` command. An arbitrary number of commands can be chained this way. \nMore generally, the piping operator should be able to support a chain of commands such as\n\n`command_1 args_1 | command_2 args_2 | ... | command_n args_n`.\n\nThese commands should be executed in parallel (though one or more may block while waiting for\nthe output of another command). \n\nTo implement this piping operator, you can use the `pipe()` system call to create a pipe, \nand the `dup()` and `dup2()` system calls to redirect standard input/output of processes\nas appropriate. \n\n\n### (R6) Parallel Commands\n\nYour shell will also allow the user to launch parallel commands. This is\naccomplished with the ampersand operator as follows:\n\n```\nanubis\u003e cmd1 \u0026 cmd2 args1 args2 \u0026 cmd3 args1\n```\n\nIn this case, instead of running `cmd1` and then waiting for it to finish,\nyour shell should run `cmd1`, `cmd2`, and `cmd3` (each with whatever arguments\nthe user has passed to it) in parallel, *before* waiting for any of them to\ncomplete. \n\nThen, after starting all such processes, you must make sure to use `wait()`\n(or `waitpid`) to wait for them to complete. After all processes are done,\nreturn control to the user as usual (or, if in batch mode, move on to the next\nline).\n\n## Operator precedence\n\nThe procedence of the three shell operators above (`\u003e`, `|` and `\u0026`) is ordered as follows: `|` has the highest,\nfollowed by `\u003e` and finally `\u0026`. That means that in a command like\n\n```\nanubis\u003e cmd1 | cmd2 \u003e output1 \u0026 cmd3 \u003e ouput2 \u0026 cmd4\n```\n\nshould be interpreted as the parallel execution of the following three commands: \n\n- the piped commands `cmd1 | cmd2`, with the output of the pipe redirected to the file `output1`,\n\n- the command `cmd3`, with its output redirected to the file `output2`, and\n\n- the command `cmd4`, which outputs to the standard output. \n\n\n## Program Errors\n\n**The one and only error message.** You should print this one and only error\nmessage whenever you encounter an error of any type: \n\n```\nAn error has occurred\n```\n\nHowever, while developing and debugging your code, you may find it useful to print informative error messages, rather than the same error message for all errors. The template file `anubis.c` provides you with a function (`ERROR`) to print this error message. \nThe behaviour of this function depends on a global variable (`_DEBUG`). If `_DEBUG` is 0, then only the simple message `\"An error has occured\"` is printed. Use this for your final submitted version.\nIf `_DEBUG` is set to 1, the `ERROR` function will print custom messages you set it to. In most cases, if you only want to output a simple error message, simply use\n\n```C\nERROR(0,\"this is a custom error message\"); \n```\n\nNotice that `ERROR` is what is called a \"variadic\" function, and can take arguments of arbitrary length, much like `printf`. For example, the following\n\n```C\nERROR(0,\"one (%d), two (%d), three (%d)\", 1, 2, 3);\n```\n\nwill output `\"one (1), two (2), three (3)\"` (followed by a new line). \n\n\nAfter most errors, your shell simply *continues processing* after\nprinting the one and only error message. However, if the shell is\ninvoked with more than one file, or if the shell is passed a bad batch\nfile, it should exit by calling `exit(1)`.\n\nThere is a difference between errors that your shell catches and those that\nthe program catches. Your shell should catch all the syntax errors specified\nin this project page. If the syntax of the command looks perfect, you simply\nrun the specified program. If there are any program-related errors (e.g.,\ninvalid arguments to `ls` when you run it, for example), the shell does not\nhave to worry about that (rather, the program will print its own error\nmessages and exit).\n\n\n## Miscellaneous Hints\n\nRemember to get the **basic functionality** of your shell working before\nworrying about all of the error conditions and end cases. For example, first\nget a single command running (probably first a command with no arguments, such\nas `ls`). \n\nNext, add built-in commands. Then, try working on redirection. Finally, think\nabout parallel commands. Each of these requires a little more effort on\nparsing, but each should not be too hard to implement.\n\nAt some point, you should make sure your code is robust to white space of\nvarious kinds, including spaces (` `) and tabs (`\\t`). In general, the user\nshould be able to put variable amounts of white space before and after\ncommands, arguments, and various operators; however, the operators\n(redirection, pipe and parallel commands) do not require whitespace.\n\nCheck the return codes of all system calls from the very beginning of your\nwork. This will often catch errors in how you are invoking these new system\ncalls. It's also just good programming sense.\n\nBeat up your own code! You are the best (and in this case, the only) tester of\nthis code. Throw lots of different inputs at it and make sure the shell\nbehaves well. Good code comes through testing; you must run many different\ntests to make sure things work as desired. Don't be gentle -- other users\ncertainly won't be. \n\nFinally, keep versions of your code. More advanced programmers will use a\nsource control system such as git. Minimally, when you get a piece of\nfunctionality working, make a copy of your .c file (perhaps a subdirectory\nwith a version number, such as v1, v2, etc.). By keeping older, working\nversions around, you can comfortably work on adding new functionality, safe in\nthe knowledge you can always go back to an older, working version if need be.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fengineersbox%2Funix-shell","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fengineersbox%2Funix-shell","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fengineersbox%2Funix-shell/lists"}