feat: implemented pipes and redirects + background operator
This commit is contained in:
parent
6b9bedac3c
commit
7e6c1147db
2
Makefile
2
Makefile
|
|
@ -4,7 +4,7 @@ shell:
|
||||||
gcc -std=c99 -Wall -pedantic main.c scanner.c shell.c cmd.c -o shell
|
gcc -std=c99 -Wall -pedantic main.c scanner.c shell.c cmd.c -o shell
|
||||||
|
|
||||||
bonus:
|
bonus:
|
||||||
gcc -std=c99 -Wall -DEXT_PROMPT -pedantic main.c scanner.c shell.c cmd.c -o shell
|
gcc -std=c99 -Wall -DEXT_PROMPT -pedantic main.c scanner.c shell.c cmd.c -o shell -lreadline
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *~
|
rm -f *~
|
||||||
|
|
|
||||||
50
README.md
50
README.md
|
|
@ -1,3 +1,5 @@
|
||||||
|
Bonuses added for lab 3 are at the bottom of this document, starting [here](#navigable-arrow-keys).
|
||||||
|
|
||||||
# general code structure
|
# general code structure
|
||||||
The main objective of this assignment was to tokenize given inputs and process the resulting tokens according to the given grammar. We decided to seperate the two steps of tokenizing and processing almost entirely, which decouples the input parser from the execution very neatly. The one drawback of this method is that each input is, more or less, processed twice - but we considered this a fair trade-off, as the input for a shell can generally be expected to not be incredulously large.
|
The main objective of this assignment was to tokenize given inputs and process the resulting tokens according to the given grammar. We decided to seperate the two steps of tokenizing and processing almost entirely, which decouples the input parser from the execution very neatly. The one drawback of this method is that each input is, more or less, processed twice - but we considered this a fair trade-off, as the input for a shell can generally be expected to not be incredulously large.
|
||||||
|
|
||||||
|
|
@ -23,8 +25,6 @@ Builtin | Result
|
||||||
--- | ---
|
--- | ---
|
||||||
status | prints the return status of the last run command.
|
status | prints the return status of the last run command.
|
||||||
exit | exits the shell.
|
exit | exits the shell.
|
||||||
true | sets status to `0`.
|
|
||||||
false | sets status to `-1`.
|
|
||||||
cd | changes the current working directory.
|
cd | changes the current working directory.
|
||||||
debug | prints debugging information to `stdout`.
|
debug | prints debugging information to `stdout`.
|
||||||
|
|
||||||
|
|
@ -65,11 +65,13 @@ typedef struct CommandList {
|
||||||
} CommandList;
|
} CommandList;
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Chain` struct models one complete chain to be executed. It is currently a bit barebones, but will be expanded when we implement piping, redirection, and running in the background.
|
The `Chain` struct models one complete chain to be executed. It conains a list of commands to execute, and extra information related to I/O - any redirects. These are set to `NULL` if no redirect was given.
|
||||||
```c
|
```c
|
||||||
typedef struct Chain {
|
typedef struct Chain {
|
||||||
CommandList commands;
|
CommandList commands;
|
||||||
bool runInBackground;
|
bool runInBackground;
|
||||||
|
char *in;
|
||||||
|
char *out;
|
||||||
} Chain;
|
} Chain;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -90,8 +92,6 @@ The execution is done within a while loop that runs until the `cpy` list is enti
|
||||||
- COMPOSITION
|
- COMPOSITION
|
||||||
- BUILTIN
|
- BUILTIN
|
||||||
|
|
||||||
// TODO: discuss what the process is for each enum type.
|
|
||||||
|
|
||||||
If the code encounters any other types at this level, it means that the syntax of the input is incorrect. If this happens, an error message is printed out and `cpy` is set to NULL in order to exit the loop.
|
If the code encounters any other types at this level, it means that the syntax of the input is incorrect. If this happens, an error message is printed out and `cpy` is set to NULL in order to exit the loop.
|
||||||
|
|
||||||
In the end, we use `free()` and `freeTokenList()` to free the memory.
|
In the end, we use `free()` and `freeTokenList()` to free the memory.
|
||||||
|
|
@ -163,10 +163,40 @@ The end result looks like this:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## true / false
|
|
||||||
The smallest of our extra implementations, we have added two `builtin`s which directly modify `status`.
|
|
||||||
|
|
||||||
* The builtin `true` will set `status` to `0`.
|
## navigable arrow keys
|
||||||
* The builtin `false` will set `status` to `-1`.
|
Another standard shell feature, available in everything from the modern fish and zsh, to shells as far back as sh, is that of the arrow key. Because our shell takes input using `getchar()` the user is unable to navigate back in an unfinished prompt. By switching to the GNU readline library, we can make use of the `readline()` function - allowing us to navigate an unfinished prompt in the expected manner.
|
||||||
|
|
||||||
Really this was more of an implementation to allow us to do proper debugging, but it's still something we added to the shell that was not specifically part of the assignment, so we decided we may as well document it here.
|
This is a bit hard to illustrate using text/images, so we recommend you build the shell yourself if you wish to play around with this a little bit.
|
||||||
|
|
||||||
|
|
||||||
|
## tab completion
|
||||||
|
Another perk of the GNU readline library is it quite neatly allows for tab completion, bash-style. Use tab while entering a command and watch as the shell tries its best to complete it for you.
|
||||||
|
|
||||||
|
Note that this only works on filenames - the bash-style tab-completion of variables and executable commands is not implemented. Included below is an example; after entering 'cat' we then pressed tab to showcase the completion, which prints all (matching) files in CWD to the shell.
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## background operation
|
||||||
|
Everyone who has ever used a shell before will be familiar with the background operator, but here's a quick recap anyay: Whenever a command is suffixed with a single ampersand (`&`), it is run in the background and control of the shell is immediately returned to the user. The output streams of the command are still connected to the shell, (ergo, `echo 7 &` will still print '7' to stdout, which might mess with the prompt a little), but the command is run as non-blocking.
|
||||||
|
|
||||||
|
Because of the structured approach we took towards chain/command design, it was trivially easy for us to implement this bonus: a simple if statement (checking for the background operator) and we are good to go!
|
||||||
|
|
||||||
|
The relevant snippet of code lives in `executeChain` and reads as follows:
|
||||||
|
```c
|
||||||
|
// wait on children
|
||||||
|
int stat;
|
||||||
|
#if EXT_PROMPT
|
||||||
|
if (chain.runInBackground) return;
|
||||||
|
#endif
|
||||||
|
for (int i=0; i < numCommands; i++) {
|
||||||
|
waitpid(pids[i], &stat, 0);
|
||||||
|
if (WIFEXITED(stat)) {
|
||||||
|
*status = WEXITSTATUS(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- ## command history -->
|
||||||
|
<!-- Similarly, by using the GNU history header, we are able to implement a simple command history using a linked list structure. Try pressing the 'up' and 'down' arrow keys to navigate your command history. -->
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
162
cmd.c
162
cmd.c
|
|
@ -3,6 +3,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "scanner.h"
|
#include "scanner.h"
|
||||||
|
|
||||||
|
|
@ -35,6 +37,8 @@ Chain _newChain() {
|
||||||
Chain chain;
|
Chain chain;
|
||||||
chain.commands = _newCommandList();
|
chain.commands = _newCommandList();
|
||||||
chain.runInBackground = false;
|
chain.runInBackground = false;
|
||||||
|
chain.in = NULL;
|
||||||
|
chain.out = NULL;
|
||||||
return chain;
|
return chain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +126,21 @@ void _insertCommand(Command cmd, CommandList *list) {
|
||||||
list->commands[list->numCommands++] = cmd;
|
list->commands[list->numCommands++] = cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addRedirect(List *lp, Chain *chain) {
|
||||||
|
// exit on invalid syntax; should be unreachable.
|
||||||
|
if ((*lp)->type != REDIRECT || (*lp)->next->type != FILENAME) exit(1);
|
||||||
|
|
||||||
|
if ((*lp)->t[0] == '>' ) {
|
||||||
|
// out redirect
|
||||||
|
chain->out = (*lp)->next->t;
|
||||||
|
} else {
|
||||||
|
// in redirect
|
||||||
|
chain->in = (*lp)->next->t;
|
||||||
|
}
|
||||||
|
*lp = (*lp)->next;
|
||||||
|
*lp = (*lp)->next;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function build up one chain of commands from a given listPointer.
|
* This function build up one chain of commands from a given listPointer.
|
||||||
* It parses one 'unit' of executables, meaning any number of commands+options chained together by pipes and redirects.
|
* It parses one 'unit' of executables, meaning any number of commands+options chained together by pipes and redirects.
|
||||||
|
|
@ -152,9 +171,10 @@ Chain buildChain(List *lp) {
|
||||||
*lp = (*lp)->next;
|
*lp = (*lp)->next;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// finding a redirect means a bunch of things we haven't thought out yet.
|
// finding a redirect means we need to take this into account during chain execution;
|
||||||
|
// add the redirect to the chain struct, and we can deal with it down the line.
|
||||||
case REDIRECT:
|
case REDIRECT:
|
||||||
//TODO: implement redirect
|
addRedirect(lp, &chain);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// finding a background operator means two things:
|
// finding a background operator means two things:
|
||||||
|
|
@ -180,50 +200,115 @@ Chain buildChain(List *lp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function executes a given command in a child process.
|
* This function executes all commands in a given chain.
|
||||||
* To make this a blocking operation, the parent process will wait until the child exits - simultaneously updating the status variable.
|
* It updates the status as it does so.
|
||||||
* @param cmd the Command to execute.
|
* The function uses the variables chain.in and chain.out for redirection, if necessary.
|
||||||
* @param status variable which will contain the exit status of the command.
|
* Additionally it supports non-blocking execution through the chain,runInBackground attribute.
|
||||||
|
* @param chain the Chain to execute.
|
||||||
|
* @param status variable which will contain the exit status after execution.
|
||||||
*/
|
*/
|
||||||
void _executeCommand(Command cmd, int* status) {
|
void executeChain(Chain chain, int* status) {
|
||||||
pid_t pid = fork();
|
// variables for clarity
|
||||||
|
int numCommands = chain.commands.numCommands;
|
||||||
|
int numPipes = chain.commands.numCommands-1;
|
||||||
|
pid_t pids[numCommands];
|
||||||
|
Command cmd;
|
||||||
|
|
||||||
switch (pid) {
|
// error if input and output are identical files.
|
||||||
// fork failed.
|
if (chain.in != NULL && chain.out != NULL && !strcmp(chain.in, chain.out)) {
|
||||||
|
printf("Error: input and output files cannot be equal!\n");
|
||||||
|
*status = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipe creation
|
||||||
|
int *pipes = calloc(numPipes*2, sizeof(int));
|
||||||
|
for (int i=0; i < numPipes; i++) {
|
||||||
|
if (pipe(pipes + i*2) < 0) {
|
||||||
|
perror("ERROR: failed to create pipes!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// command execution
|
||||||
|
for (int i=0; i < numCommands; i++) {
|
||||||
|
cmd = chain.commands.commands[i];
|
||||||
|
|
||||||
|
pids[i] = fork();
|
||||||
|
switch (pids[i]) {
|
||||||
case -1:
|
case -1:
|
||||||
printf("ERROR: failed to create child!\n");
|
printf("ERROR: failed to create child!\n");
|
||||||
*status =-1;
|
*status =-1;
|
||||||
break;
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
// child process
|
// child process
|
||||||
|
|
||||||
|
// if not first command, connect input to previous output
|
||||||
|
if (i > 0) {
|
||||||
|
if (dup2(pipes[(i-1)*2], 0) < 0) {
|
||||||
|
perror("ERROR: failed to connect pipes!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (chain.in != NULL) {
|
||||||
|
// in redirection
|
||||||
|
int fd0 = open(chain.in, O_RDONLY, 0);
|
||||||
|
if (dup2(fd0, 0) < 0) {
|
||||||
|
perror("ERROR: failed to redirect input!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close(fd0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not last command, connect output to next input
|
||||||
|
if (i+1 < numCommands) {
|
||||||
|
if (dup2(pipes[i*2+1], 1) < 0) {
|
||||||
|
perror("ERROR: failed to connect pipes!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (chain.out != NULL) {
|
||||||
|
// output redirection
|
||||||
|
int fd1 = open(chain.out, O_WRONLY|O_CREAT, 0755);
|
||||||
|
if (dup2(fd1, 1) < 0) {
|
||||||
|
perror("ERROR: failed to redirect output!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
close(fd1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// close all pipes
|
||||||
|
for (int i = 0; i < numPipes; i++) {
|
||||||
|
close(pipes[i*2]);
|
||||||
|
close(pipes[i*2+1]);
|
||||||
|
}
|
||||||
|
|
||||||
execvp(cmd.command, cmd.arguments);
|
execvp(cmd.command, cmd.arguments);
|
||||||
// this line is only ever reached if execvp fails (for example, when an executable can't be found).
|
// this line is only ever reached if execvp fails (for example, when an executable can't be found).
|
||||||
exit(127);
|
exit(127);
|
||||||
break;
|
break;
|
||||||
default:;
|
default:;
|
||||||
// parent process; wait for child and update status.
|
// parent process
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// close pipes
|
||||||
|
for (int i = 0; i < numPipes; i++) {
|
||||||
|
close(pipes[i*2]);
|
||||||
|
close(pipes[i*2+1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait on children
|
||||||
int stat;
|
int stat;
|
||||||
wait(&stat);
|
#if EXT_PROMPT
|
||||||
|
if (chain.runInBackground) return;
|
||||||
|
#endif
|
||||||
|
for (int i=0; i < numCommands; i++) {
|
||||||
|
waitpid(pids[i], &stat, 0);
|
||||||
if (WIFEXITED(stat)) {
|
if (WIFEXITED(stat)) {
|
||||||
*status = WEXITSTATUS(stat);
|
*status = WEXITSTATUS(stat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: extend to include I/O redirection and background operation
|
|
||||||
/**
|
|
||||||
* This function executes all commands in a given chain.
|
|
||||||
* It updates the status as it does so.
|
|
||||||
* This function is barebones at the moment, but will be expanded to include support for redirection and non-blocking execution.
|
|
||||||
* @param chain the Chain to execute.
|
|
||||||
* @param status variable which will contain the exit status after execution.
|
|
||||||
*/
|
|
||||||
void executeChain(Chain chain, int* status) {
|
|
||||||
for (int i=0; i < chain.commands.numCommands; i++) {
|
|
||||||
_executeCommand(chain.commands.commands[i], status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function executes a BUILTIN.
|
* This function executes a BUILTIN.
|
||||||
* BUILTINs are pre-defined commands ran within the shell itself, as opposed to executables found in $PATH or $CWD.
|
* BUILTINs are pre-defined commands ran within the shell itself, as opposed to executables found in $PATH or $CWD.
|
||||||
|
|
@ -237,26 +322,27 @@ bool executeBuiltin(Command cmd, int *status, bool *debug) {
|
||||||
bool executeBuiltin(Command cmd, int *status) {
|
bool executeBuiltin(Command cmd, int *status) {
|
||||||
#endif
|
#endif
|
||||||
if (!strcmp(cmd.command, "status")) {
|
if (!strcmp(cmd.command, "status")) {
|
||||||
printf("The most recent exit code is: %d.\n", *status);
|
printf("The most recent exit code is: %d\n", *status);
|
||||||
} else if (!strcmp(cmd.command, "exit")) {
|
} else if (!strcmp(cmd.command, "exit")) {
|
||||||
return false;
|
return false;
|
||||||
} else if (!strcmp(cmd.command, "true")) {
|
|
||||||
*status = 0;
|
|
||||||
} else if (!strcmp(cmd.command, "false")) {
|
|
||||||
*status = 1;
|
|
||||||
#if EXT_PROMPT
|
|
||||||
} else if (!strcmp(cmd.command, "debug")) {
|
|
||||||
*debug = ! *debug;
|
|
||||||
printf("Toggled debug to %d.\n", *debug);
|
|
||||||
} else if (!strcmp(cmd.command, "cd")) {
|
} else if (!strcmp(cmd.command, "cd")) {
|
||||||
if (cmd.numArguments == 2) {
|
if (cmd.numArguments == 2) {
|
||||||
char *PWD = getenv("HOME");
|
printf("Error: cd requires folder to navigate to!\n");
|
||||||
*status = chdir(PWD);
|
*status = 2;
|
||||||
} else {
|
} else {
|
||||||
*status = chdir(cmd.arguments[1]);
|
*status = chdir(cmd.arguments[1]);
|
||||||
|
if (*status) {
|
||||||
|
printf("Error: cd directory not found!\n");
|
||||||
|
*status = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if EXT_PROMPT
|
||||||
|
else if (!strcmp(cmd.command, "debug")) {
|
||||||
|
*debug = ! *debug;
|
||||||
|
printf("Toggled debug to %d.\n", *debug);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}// can be expanded by growing the if/else chain.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
3
cmd.h
3
cmd.h
|
|
@ -29,6 +29,8 @@ typedef struct CommandList {
|
||||||
typedef struct Chain {
|
typedef struct Chain {
|
||||||
CommandList commands;
|
CommandList commands;
|
||||||
bool runInBackground;
|
bool runInBackground;
|
||||||
|
char *in;
|
||||||
|
char *out;
|
||||||
} Chain;
|
} Chain;
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,6 +38,7 @@ void freeCommand(Command cmd);
|
||||||
void freeChain(Chain chain);
|
void freeChain(Chain chain);
|
||||||
|
|
||||||
Command buildCommand(List *lp);
|
Command buildCommand(List *lp);
|
||||||
|
void addRedirect(List *lp, Chain *chain);
|
||||||
Chain buildChain(List *lp);
|
Chain buildChain(List *lp);
|
||||||
|
|
||||||
void executeChain(Chain chain, int* status);
|
void executeChain(Chain chain, int* status);
|
||||||
|
|
|
||||||
55
main.c
55
main.c
|
|
@ -1,4 +1,3 @@
|
||||||
#include <stdio.h>
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
@ -12,11 +11,15 @@
|
||||||
#include "cmd.h"
|
#include "cmd.h"
|
||||||
|
|
||||||
// general TODO:
|
// general TODO:
|
||||||
// * properly bind stdin/stdout for piped commands
|
|
||||||
// * run chains in bg if given '&'
|
|
||||||
// * manage < and > redirects
|
|
||||||
// * signal handling
|
// * signal handling
|
||||||
// * builtins - 'kill'
|
// * builtins - 'kill'
|
||||||
|
// * simple command history
|
||||||
|
// * run scripts with argv[]
|
||||||
|
// * redirect stderr with the n> operator
|
||||||
|
// * allow multiple redirects (turn chain.in and chain.out into char**)
|
||||||
|
//
|
||||||
|
// Big rewrite: create a 'shell' struct which is the actual program to run
|
||||||
|
// this struct would take care of things like command history, signal handling, etc
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function skips one COMMAND, including redirects and pipes (if any).
|
* This function skips one COMMAND, including redirects and pipes (if any).
|
||||||
|
|
@ -44,7 +47,7 @@ void skipCommand(List *lp) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function checks whether the current COMPOSITION operator passes (and thus should execute the next function).
|
* This function checks whether the current COMPOSITION operator passes (and thus should execute the next command).
|
||||||
* @param s the COMPOSITION operator.
|
* @param s the COMPOSITION operator.
|
||||||
* @param status the exit status of the last ran command.
|
* @param status the exit status of the last ran command.
|
||||||
* @return whether the COMPOSITION succeeds/whether to execute the next command.
|
* @return whether the COMPOSITION succeeds/whether to execute the next command.
|
||||||
|
|
@ -57,17 +60,48 @@ bool compositionPasses(char *s, int status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#if EXT_PROMPT
|
||||||
|
int execute_script(char* script) {
|
||||||
|
FILE* file = fopen(script, "r");
|
||||||
|
if (file == NULL) {
|
||||||
|
printf("Error: provided Scriptfile, '%s', does not exist or is inaccessible!\n", script);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
char line[512];
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), file)) {
|
||||||
|
printf("%s", line);
|
||||||
|
//TODO: continue working here on the script functionality later, after the Refactor
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Driver function. Contains the main loop which collects and parses input, and decides what and how to execute + output.
|
* Driver function. Contains the main loop which collects and parses input, and decides what and how to execute + output.
|
||||||
*/
|
*/
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
|
#if EXT_PROMPT
|
||||||
|
if (argc >= 2) {
|
||||||
|
if (argc > 2) {
|
||||||
|
printf("Error: unexpected number of arguments!\n");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if (execute_script(argv[1])) return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
char *inputLine;
|
char *inputLine;
|
||||||
List tokenList;
|
List tokenList;
|
||||||
int status = 0;
|
int status = 0;
|
||||||
bool do_loop = true;
|
bool do_loop = true;
|
||||||
#if EXT_PROMPT
|
#if EXT_PROMPT
|
||||||
bool debug = false;
|
bool debug = false;
|
||||||
char cwd[101], *usr;
|
char cwd[128], *usr, prompt[256];
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Disable buffering so we don't have to deal with out-of-order prints.
|
// Disable buffering so we don't have to deal with out-of-order prints.
|
||||||
|
|
@ -80,10 +114,13 @@ int main(int argc, char *argv[]) {
|
||||||
#if EXT_PROMPT
|
#if EXT_PROMPT
|
||||||
getcwd(cwd, sizeof(cwd));
|
getcwd(cwd, sizeof(cwd));
|
||||||
usr = getlogin();
|
usr = getlogin();
|
||||||
if (!status) printf("\x1b[33m%s \x1b[36m%s \x1b[32m>\x1b[0m ", usr, cwd);
|
if (!status) snprintf(prompt, 200*sizeof(char), "\x1b[33m%s \x1b[36m%s \x1b[32m>\x1b[0m ", usr, cwd);
|
||||||
if ( status) printf("\x1b[33m%s \x1b[36m%s \x1b[31m>\x1b[0m ", usr, cwd);
|
if ( status) snprintf(prompt, 200*sizeof(char), "\x1b[33m%s \x1b[36m%s \x1b[31m>\x1b[0m ", usr, cwd);
|
||||||
#endif
|
inputLine = readInputLine(prompt);
|
||||||
|
#else
|
||||||
inputLine = readInputLine();
|
inputLine = readInputLine();
|
||||||
|
#endif
|
||||||
|
|
||||||
// We have modified the readInputLine function to return NULL on EOF.
|
// We have modified the readInputLine function to return NULL on EOF.
|
||||||
if (inputLine == NULL) {
|
if (inputLine == NULL) {
|
||||||
do_loop = false;
|
do_loop = false;
|
||||||
|
|
|
||||||
13
scanner.c
13
scanner.c
|
|
@ -7,7 +7,17 @@
|
||||||
|
|
||||||
#include "scanner.h"
|
#include "scanner.h"
|
||||||
|
|
||||||
//TODO: handle EOF more completely (currently only EOF at the start of the line is handled)
|
#if EXT_PROMPT
|
||||||
|
#include <readline/readline.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an inputline from stdin.
|
||||||
|
* @return a string containing the inputline.
|
||||||
|
*/
|
||||||
|
char *readInputLine(char const *prompt) {
|
||||||
|
return readline(prompt);
|
||||||
|
}
|
||||||
|
#else
|
||||||
/**
|
/**
|
||||||
* Reads an inputline from stdin.
|
* Reads an inputline from stdin.
|
||||||
* @return a string containing the inputline.
|
* @return a string containing the inputline.
|
||||||
|
|
@ -43,6 +53,7 @@ char *readInputLine() {
|
||||||
s[i] = '\0';
|
s[i] = '\0';
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function isOperatorCharacter checks whether the input paramater \param c is an operator.
|
* The function isOperatorCharacter checks whether the input paramater \param c is an operator.
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,11 @@ typedef struct ListNode {
|
||||||
} ListNode;
|
} ListNode;
|
||||||
|
|
||||||
|
|
||||||
|
#if EXT_PROMPT
|
||||||
|
char *readInputLine(char const *prompt);
|
||||||
|
#else
|
||||||
char *readInputLine();
|
char *readInputLine();
|
||||||
|
#endif
|
||||||
|
|
||||||
List getTokenList(char *s);
|
List getTokenList(char *s);
|
||||||
|
|
||||||
|
|
|
||||||
7
shell.c
7
shell.c
|
|
@ -123,6 +123,9 @@ bool _parsePipeline(List *lp) {
|
||||||
* @return a bool denoting whether the filename was parsed successfully.
|
* @return a bool denoting whether the filename was parsed successfully.
|
||||||
*/
|
*/
|
||||||
bool _parseFilename(List *lp) {
|
bool _parseFilename(List *lp) {
|
||||||
|
if (*lp == NULL) return false;
|
||||||
|
|
||||||
|
|
||||||
//we run a POSIX compliant system, meaning all characters save '/' and NULL are allowed in filenames.
|
//we run a POSIX compliant system, meaning all characters save '/' and NULL are allowed in filenames.
|
||||||
//NULL is already taken care of by the List library, and / just means the file is located in a directory.
|
//NULL is already taken care of by the List library, and / just means the file is located in a directory.
|
||||||
//as a result the only limit we place on filenames are the reserved (operator) characters.
|
//as a result the only limit we place on filenames are the reserved (operator) characters.
|
||||||
|
|
@ -178,11 +181,9 @@ bool _parseBuiltIn(List *lp) {
|
||||||
char *builtIns[] = {
|
char *builtIns[] = {
|
||||||
"exit",
|
"exit",
|
||||||
"status",
|
"status",
|
||||||
"true",
|
"cd",
|
||||||
"false",
|
|
||||||
#if EXT_PROMPT
|
#if EXT_PROMPT
|
||||||
"debug",
|
"debug",
|
||||||
"cd",
|
|
||||||
#endif
|
#endif
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue