Apostolos Fanakis
7 years ago
commit
370841b91d
7 changed files with 338 additions and 0 deletions
@ -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 *~ |
Binary file not shown.
@ -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]: <https://en.wikipedia.org/wiki/Unix_shell> |
@ -0,0 +1,212 @@ |
|||||
|
#include <stdio.h> |
||||
|
#include <stdlib.h> |
||||
|
#include <string.h> |
||||
|
#include <ctype.h> |
||||
|
#include <unistd.h> |
||||
|
#include <sys/wait.h> |
||||
|
|
||||
|
#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<argc; ++i){ |
||||
|
free(argv[i]); |
||||
|
} |
||||
|
free(argv); |
||||
|
|
||||
|
//Checks if chunk isn't split by a double ampersand
|
||||
|
if (strlen(rest) != 0){ |
||||
|
if (!(rest[0] == '&' && isspace(rest[1]))){ |
||||
|
printf("Not supported\n"); |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
//Gets next token
|
||||
|
token = strtok_r(NULL, ampersandDelimeter, &rest); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
int execute(char *command, char **argv){ |
||||
|
pid_t pid; |
||||
|
int status = 0; |
||||
|
|
||||
|
if ((pid = fork()) < 0) { |
||||
|
//Failed to create child process
|
||||
|
perror("Can't fork"); |
||||
|
exit(EXIT_FAILURE); |
||||
|
} else if (pid == 0) { |
||||
|
//Child process
|
||||
|
if (execvp(command, argv)) { |
||||
|
perror("Error executing command"); |
||||
|
_exit(EXIT_FAILURE); |
||||
|
} |
||||
|
} else { |
||||
|
//Parent process
|
||||
|
while (wait(&status) != pid) |
||||
|
; |
||||
|
return status; |
||||
|
} |
||||
|
return status; |
||||
|
} |
||||
|
|
||||
|
void parseCommand(char *input, char **command, char ***argv, int *argc){ |
||||
|
char *token = strtok(input, " "); |
||||
|
*argc = 1; |
||||
|
|
||||
|
//Allocates memory and copies command
|
||||
|
*command = (char*)(malloc((strlen(token) + 1) * sizeof(char))); |
||||
|
strcpy(*command, trimWhitespaces(token)); |
||||
|
(*command)[strlen(*command)] = '\0'; |
||||
|
|
||||
|
//Allocates memory and copies first argument (command)
|
||||
|
*argv = (char**)(malloc((*argc) * sizeof(char*))); |
||||
|
(*argv)[(*argc) - 1] = (char*)(malloc((strlen(*command) + 1) * sizeof(char))); |
||||
|
strcpy((*argv)[(*argc) - 1], *command); |
||||
|
(*argv)[(*argc) - 1][strlen((*argv)[(*argc) - 1])] = '\0'; |
||||
|
|
||||
|
//Parses rest of the arguments
|
||||
|
token = strtok(NULL, " "); |
||||
|
while (token != NULL){ |
||||
|
++(*argc); |
||||
|
*argv = (char**)(realloc(*argv, (*argc) * sizeof(char*))); |
||||
|
(*argv)[(*argc) - 1] = (char*)(malloc((strlen(token) + 1) * sizeof(char))); |
||||
|
strcpy((*argv)[(*argc) - 1], trimWhitespaces(token)); |
||||
|
(*argv)[(*argc) - 1][strlen((*argv)[(*argc) - 1])] = '\0'; |
||||
|
token = strtok(NULL, " "); |
||||
|
} |
||||
|
|
||||
|
//NULL-terminates arguments array
|
||||
|
++(*argc); |
||||
|
*argv = (char**)(realloc(*argv, (*argc) * sizeof(char*))); |
||||
|
(*argv)[(*argc) - 1] = NULL; |
||||
|
} |
||||
|
|
||||
|
char *trimWhitespaces(char *string){ |
||||
|
char *end; |
||||
|
|
||||
|
//Trims leading spaces
|
||||
|
while (isspace( (unsigned char) *string )){ |
||||
|
string++; |
||||
|
} |
||||
|
|
||||
|
if(*string == 0){ //String was all spaces
|
||||
|
return string; |
||||
|
} |
||||
|
|
||||
|
//Trims trailing spaces
|
||||
|
end = string + strlen(string) - 1; |
||||
|
while (end > string && isspace( (unsigned char) *end )){ |
||||
|
end--; |
||||
|
} |
||||
|
|
||||
|
//Writes new null terminator
|
||||
|
*(end+1) = 0; |
||||
|
|
||||
|
return string; |
||||
|
} |
@ -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_
|
@ -0,0 +1,15 @@ |
|||||
|
#include <stdio.h> |
||||
|
#include <stdlib.h> |
||||
|
|
||||
|
#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); |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
mkdir someDir && touch someDir/file |
||||
|
mkdir someDir/dir2 ; touch someDir/file2 |
||||
|
|
||||
|
|
||||
|
ls someDir |
||||
|
touch someDir/dir2/file3 |
Loading…
Reference in new issue