commit 370841b91d4669acdc14b01aafea8fb5847bebca Author: Apostolof Date: Thu Feb 8 00:14:00 2018 +0200 Init diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7a0203f --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +TARGET = apostolofShell +CC = gcc +CFLAGS = -Wall -O3 -I. +OBJ = apostolofShellMain.o apostolofShellFunctions.o +DEPS = apostolofShellFunctions.h + +.PHONY: default all clean + +default: $(TARGET) +all: default + +%.o: %.c $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +.PRECIOUS: $(TARGET) $(OBJ) + +$(TARGET): $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) + +clean: + $(RM) *.o *~ \ No newline at end of file diff --git a/Project.pdf b/Project.pdf new file mode 100644 index 0000000..b06fe4a Binary files /dev/null and b/Project.pdf differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..19a927f --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ + +# Shell + +From [Wikipedia]: + +>A Unix shell is a command-line interpreter or shell that provides a traditional Unix-like command line user interface. Users direct the operation of the computer by entering commands as text for a command line interpreter to execute, or by creating text scripts of one or more such commands. + +## Repository + +This repository provides an implementation of a simple Unix shell in C language. This shell was developed as part of the "Operating Systems" course at AUTH and has very limited functionality. All code in this repository is intended for academic and learning purposes only, not for everyday usage. + +## Dependencies + +Only a compiler is needed (e.g. gcc). + +## Compilation + +To compile, make sure all necessary packages and dependencies are installed. Then run: + +```sh +$ make +``` + +## Usage + +Run the shell in interactive mode with the command: +```sh +$ ./apostolofShell +``` +or in batch mode using the command: +```sh +$ ./apostolofShell script +``` +where **script** is the path and filename of the script you want to run. + +**Free Software, Hell Yeah!** + +[//]: # (Links) + + [Wikipedia]: diff --git a/apostolofShellFunctions.c b/apostolofShellFunctions.c new file mode 100644 index 0000000..0e5090d --- /dev/null +++ b/apostolofShellFunctions.c @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include + +#include "apostolofShellFunctions.h" + +const char semicolonDelimeter[2] = ";"; +const char ampersandDelimeter[2] = "&"; + +void interactiveMode(){ + char input[BUFFER_SIZE]; + int quit_called = 0; + + while (1){ + printf(GRN "\x1B[1mfanakis_8261> \x1B[0m" RESET); + //Gets user input and checks for errors + if (!fgets(input, BUFFER_SIZE, stdin) || feof(stdin) || ferror(stdin)){ + continue; + } + //Checks for input buffer overflow + if ((strlen(input) == BUFFER_SIZE - 1) && input[BUFFER_SIZE - 2] != '\n'){ + printf("Input too long! Please confine input to 512 characters.\n"); + //Clears garbage from stdin + char discarded; + while ((discarded = fgetc(stdin)) != '\n' && discarded != EOF); + continue; + } + //Trims possible leading and trailing whitespace + strcpy(input, trimWhitespaces(input)); + if (strcmp(input, "\n") == 0 || input == 0){ + continue; + } + //Parses semicolon-separated chunks first + unconditionalRun(input, &quit_called); + if (quit_called == QUIT_CALLED){ + exit(EXIT_SUCCESS); + } + } +} + +void batchFileMode(char *filename){ + char line[BUFFER_SIZE]; + int quit_called = 0; + + //Opens file + FILE* file = fopen(filename, "r"); + if (!file){ + printf("Couldn't open file.\n"); + exit(EXIT_FAILURE); + } + + //Reads file one line at a time + while (fgets(line, sizeof(line), file)) { + strcpy(line, trimWhitespaces(line)); + if (strcmp(line, "\n") == 0 || line == 0){ + continue; + } + unconditionalRun(line, &quit_called); + if (quit_called == QUIT_CALLED){ + fclose(file); + exit(EXIT_SUCCESS); + } + } + + fclose(file); + return; +} + +void unconditionalRun(char *input, int *quit_called){ + char *rest, *token; + + //Parses chunks of input split by semicolons (";") + //Gets first token + token = strtok_r(input, semicolonDelimeter, &rest); + while (token != NULL){ + //Further parses and conditionally runs chunks + conditionalRun(token, quit_called); + //Parses ampersand-separated chunks and runs them successively + if (*quit_called == QUIT_CALLED){ + return; + } /*else if ((*rest == 0)){ + return 1; + }*/ + token = strtok_r(NULL, semicolonDelimeter, &rest); + } +} + +void conditionalRun(char *input, int *quit_called){ + char *rest, *token; + + //Parses ampersand-separated chunks + //Gets first token + token = strtok_r(input, ampersandDelimeter, &rest); + while (token != NULL){ + char *command, **argv; + int argc = 0; + + //Parses the command + parseCommand(trimWhitespaces(token), &command, &argv, &argc); + + if (strcmp(command, "quit") == 0){ + *quit_called = QUIT_CALLED; + return; + } + + //Executes the command + if (execute(command, argv)){ + //Exits on failure because this is a conditional run + return; + } + + //Cleans up space allocated + free(command); + for (int i=0; i string && isspace( (unsigned char) *end )){ + end--; + } + + //Writes new null terminator + *(end+1) = 0; + + return string; +} \ No newline at end of file diff --git a/apostolofShellFunctions.h b/apostolofShellFunctions.h new file mode 100644 index 0000000..489db09 --- /dev/null +++ b/apostolofShellFunctions.h @@ -0,0 +1,44 @@ +#ifndef APOSTOLOFSHELLFUNCTIONS_H_ /* Include guard */ +#define APOSTOLOFSHELLFUNCTIONS_H_ + +//Read buffer size is the maximum number of characters (512) plus the newline character and the null +//terminator +#define BUFFER_SIZE 512 + 1 + 1 +#define QUIT_CALLED 11880 +//Cool colors +#define GRN "\x1B[32m" +#define RESET "\x1B[0m" + +const char semicolonDelimeter[2]; +const char ampersandDelimeter[2]; + +//Function interactiveMode implements an interactive shell +void interactiveMode(); + +//Function batchFileMode runs a script file +void batchFileMode(char *filename); //Filename and path of the script + +//Function unconditionalRun splits a string on semicolon character (";") and runs each chunk +//unconditionally +void unconditionalRun(char *input, //String to be split + int *quit_called); //A flag indicating if quit command was called + +//Function conditionalRun splits a string on the and character ("&"), runs each chunk conditionally +//if the first character of the next chunk is also the and character and unconditionally otherwise +void conditionalRun(char *input, //String to be split + int *quit_called); //A flag indicating if quit command was called + +//Function execute creates a fork and runs a command using execvp +int execute(char *command, //String containing the command + char **argv); //NULL terminated string array containing command's arguments + +//Function parseCommand parses a command and its arguments out of a string +void parseCommand(char *input, //String to be parsed + char **command, //String containing the command + char ***argv, //NULL terminated string array containing command's arguments + int *argc); //Number of arguments + +//Function trimWhitespaces trims leading and trailing whitespaces of a string +char *trimWhitespaces(char *string); //String to be trimmed + +#endif // APOSTOLOFSHELLFUNCTIONS_H_ \ No newline at end of file diff --git a/apostolofShellMain.c b/apostolofShellMain.c new file mode 100644 index 0000000..6999389 --- /dev/null +++ b/apostolofShellMain.c @@ -0,0 +1,15 @@ +#include +#include + +#include "apostolofShellFunctions.h" + +int main(int argc, char *argv[]){ + if (argc == 1){ + interactiveMode(); + } else if (argc == 2) { + batchFileMode(argv[1]); + } else { + printf("Not supported\n"); + } + exit(EXIT_SUCCESS); +} \ No newline at end of file diff --git a/testScript b/testScript new file mode 100644 index 0000000..3b8f608 --- /dev/null +++ b/testScript @@ -0,0 +1,6 @@ +mkdir someDir && touch someDir/file +mkdir someDir/dir2 ; touch someDir/file2 + + +ls someDir +touch someDir/dir2/file3