├── images └── imageshell.png ├── own_exit.c ├── main.c ├── own_env.c ├── own_cd.c ├── shell_no_interactive.c ├── shell_interactive.c ├── read_line.c ├── own_help.c ├── new_process.c ├── shell.h ├── execute_args.c ├── read_stream.c ├── split_line.c └── README.md /images/imageshell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/santiagobedoa/shell/HEAD/images/imageshell.png -------------------------------------------------------------------------------- /own_exit.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * own_exit - couses normal process termination 5 | * @args: empty args 6 | * 7 | * Return: 0 to terminate the process 8 | */ 9 | int own_exit(char **args) 10 | { 11 | /* exit with status */ 12 | if (args[1]) 13 | { 14 | return (atoi(args[1])); 15 | } 16 | /* exit success */ 17 | else 18 | { 19 | return (0); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * main - function that checks if our shell is called 5 | * 6 | * Return: 0 on success 7 | */ 8 | int main(void) 9 | { 10 | /* determines if file descriptor is associated with a terminal */ 11 | if (isatty(STDIN_FILENO) == 1) 12 | { 13 | shell_interactive(); 14 | } 15 | else 16 | { 17 | shell_no_interactive(); 18 | } 19 | return (0); 20 | } 21 | -------------------------------------------------------------------------------- /own_env.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * own_env - function that prints enviroment variables 5 | * @args: arguments 6 | * 7 | * Return: 1 on success, 0 otherwise 8 | */ 9 | int own_env(char **args) 10 | { 11 | int i = 0; 12 | (void)(**args); 13 | 14 | while (environ[i]) 15 | { 16 | write(STDOUT_FILENO, environ[i], strlen(environ[i])); 17 | write(STDOUT_FILENO, "\n", 1); 18 | i++; 19 | } 20 | return (-1); 21 | } 22 | -------------------------------------------------------------------------------- /own_cd.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * own_cd - changes the working dir of the current shell executon env 5 | * @args: target directory 6 | * 7 | * Return: 1 one success, 0 otherwise. 8 | */ 9 | int own_cd(char **args) 10 | { 11 | if (args[1] == NULL) 12 | { 13 | fprintf(stderr, "expected argument to \"cd\"\n"); 14 | } 15 | else 16 | { 17 | if (chdir(args[1]) != 0) 18 | { 19 | perror("error in own_cd.c: changing dir\n"); 20 | } 21 | } 22 | return (-1); 23 | } 24 | -------------------------------------------------------------------------------- /shell_no_interactive.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * shell_no_interactive - unix command line interpreter 5 | * 6 | * Return: void 7 | */ 8 | void shell_no_interactive(void) 9 | { 10 | char *line; 11 | char **args; 12 | int status = -1; 13 | 14 | do { 15 | line = read_stream(); 16 | args = split_line(line); /* tokenize line */ 17 | status = execute_args(args); 18 | /* avoid memory leaks */ 19 | free(line); 20 | free(args); 21 | /* exit with status */ 22 | if (status >= 0) 23 | { 24 | exit(status); 25 | } 26 | } while (status == -1); 27 | } 28 | -------------------------------------------------------------------------------- /shell_interactive.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * shell_interactive - UNIX command line interpreter 5 | * 6 | * Return: void 7 | */ 8 | void shell_interactive(void) 9 | { 10 | char *line; 11 | char **args; 12 | int status = -1; 13 | 14 | do { 15 | printf("simple_prompt$ "); /* print prompt symbol */ 16 | line = read_line(); /* read line from stdin */ 17 | args = split_line(line); /* tokenize line */ 18 | status = execute_args(args); 19 | /* avoid memory leaks */ 20 | free(line); 21 | free(args); 22 | /* exit with status */ 23 | if (status >= 0) 24 | { 25 | exit(status); 26 | } 27 | } while (status == -1); 28 | } 29 | -------------------------------------------------------------------------------- /read_line.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * read_line - read a line from stdin 5 | * 6 | * Return: pointer that points to a str with the line content 7 | */ 8 | char *read_line(void) 9 | { 10 | char *line = NULL; 11 | size_t bufsize = 0; 12 | 13 | if (getline(&line, &bufsize, stdin) == -1) /* if getline fails */ 14 | { 15 | if (feof(stdin)) /* test for the eof */ 16 | { 17 | free(line); /* avoid memory leaks when ctrl + d */ 18 | exit(EXIT_SUCCESS); /* we recieved an eof */ 19 | } 20 | else 21 | { 22 | free(line); /* avoid memory leaks when getline fails */ 23 | perror("error while reading the line from stdin"); 24 | exit(EXIT_FAILURE); 25 | } 26 | } 27 | return (line); 28 | } -------------------------------------------------------------------------------- /own_help.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * own_help - print help 5 | * @args: arguments 6 | * 7 | * Return: 1 on success, 0 otherwise 8 | */ 9 | int own_help(char **args) 10 | { 11 | char *builtin_func_list[] = { 12 | "cd", 13 | "env", 14 | "help", 15 | "exit" 16 | }; 17 | long unsigned int i = 0; 18 | (void)(**args); 19 | 20 | printf("\n---help simple_shell---\n"); 21 | printf("Type a command and its arguments, then hit enter\n"); 22 | printf("Built-in commands:\n"); 23 | for (; i < sizeof(builtin_func_list) / sizeof(char *); i++) 24 | { 25 | printf(" -> %s\n", builtin_func_list[i]); 26 | } 27 | printf("Use the man command for information on other programs.\n\n"); 28 | return (-1); 29 | } 30 | -------------------------------------------------------------------------------- /new_process.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * new_process - create a new process 5 | * @args: array of strings that contains the command and its flags 6 | * 7 | * Return: 1 if success, 0 otherwise. 8 | */ 9 | int new_process(char **args) 10 | { 11 | pid_t pid; 12 | int status; 13 | 14 | pid = fork(); 15 | if (pid == 0) 16 | { 17 | /* child process */ 18 | if (execvp(args[0], args) == -1) 19 | { 20 | perror("error in new_process: child process"); 21 | } 22 | exit(EXIT_FAILURE); 23 | } 24 | else if (pid < 0) 25 | { 26 | /* error forking */ 27 | perror("error in new_process: forking"); 28 | } 29 | else 30 | { 31 | /* parent process */ 32 | do { 33 | waitpid(pid, &status, WUNTRACED); 34 | } while (!WIFEXITED(status) && !WIFSIGNALED(status)); 35 | } 36 | return (-1); 37 | } 38 | -------------------------------------------------------------------------------- /shell.h: -------------------------------------------------------------------------------- 1 | #ifndef SHELL_H 2 | #define SHELL_H 3 | 4 | /*---LIBRARIES---*/ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /*---Macros---*/ 12 | #define TOK_DELIM " \t\r\n\a\"" 13 | extern char **environ; 14 | 15 | /*---PROTOTYPES---*/ 16 | /* main.c */ 17 | void shell_interactive(void); 18 | void shell_no_interactive(void); 19 | 20 | /* shell_interactive.c */ 21 | char *read_line(void); 22 | char **split_line(char *line); 23 | int execute_args(char **args); 24 | 25 | /* execute_args */ 26 | int new_process(char **args); 27 | 28 | /* shell_no_interactive */ 29 | char *read_stream(void); 30 | 31 | /*---Builtin func---*/ 32 | int own_cd(char **args); 33 | int own_exit(char **args); 34 | int own_env(char **args); 35 | int own_help(char **args); 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /execute_args.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * execute_args - map if command is a builtin or a process 5 | * @args: command and its flags 6 | * 7 | * Return: 1 on sucess, 0 otherwise 8 | */ 9 | int execute_args(char **args) 10 | { 11 | char *builtin_func_list[] = { 12 | "cd", 13 | "env", 14 | "help", 15 | "exit" 16 | }; 17 | int (*builtin_func[])(char **) = { 18 | &own_cd, 19 | &own_env, 20 | &own_help, 21 | &own_exit 22 | }; 23 | long unsigned int i = 0; 24 | 25 | if (args[0] == NULL) 26 | { 27 | /* empty command was entered */ 28 | return (-1); 29 | } 30 | /* find if the command is a builtin */ 31 | for (; i < sizeof(builtin_func_list) / sizeof(char *); i++) 32 | { 33 | /* if there is a match execute the builtin command */ 34 | if (strcmp(args[0], builtin_func_list[i]) == 0) 35 | { 36 | return ((*builtin_func[i])(args)); 37 | } 38 | } 39 | /* create a new process */ 40 | return (new_process(args)); 41 | } 42 | -------------------------------------------------------------------------------- /read_stream.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * read_stream - read a line from the stream 5 | * 6 | * Return: pointer that points the the read line 7 | */ 8 | char *read_stream(void) 9 | { 10 | int bufsize = 1024; 11 | int i = 0; 12 | char *line = malloc(sizeof(char) * bufsize); 13 | int character; 14 | 15 | if (line == NULL) 16 | { 17 | fprintf(stderr, "allocation error in read_stream"); 18 | exit(EXIT_FAILURE); 19 | } 20 | while (1) 21 | { 22 | character = getchar(); /* read first char from stream */ 23 | if (character == EOF) 24 | { 25 | free(line); 26 | exit(EXIT_SUCCESS); 27 | } 28 | else if (character == '\n') 29 | { 30 | line[i] = '\0'; 31 | return (line); 32 | } 33 | else 34 | { 35 | line[i] = character; 36 | } 37 | i++; 38 | if (i >= bufsize) 39 | { 40 | bufsize += bufsize; 41 | line = realloc(line, bufsize); 42 | if (line == NULL) 43 | { 44 | fprintf(stderr, "reallocation error in read_stream"); 45 | exit(EXIT_FAILURE); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /split_line.c: -------------------------------------------------------------------------------- 1 | #include "shell.h" 2 | 3 | /** 4 | * split_line - split a string into multiple strings 5 | * @line: string to be splited 6 | * 7 | * Return: pointer that points to the new array 8 | */ 9 | char **split_line(char *line) 10 | { 11 | int bufsize = 64; 12 | int i = 0; 13 | char **tokens = malloc(bufsize * sizeof(char *)); 14 | char *token; 15 | 16 | if (!tokens) 17 | { 18 | fprintf(stderr, "allocation error in split_line: tokens\n"); 19 | exit(EXIT_FAILURE); 20 | } 21 | token = strtok(line, TOK_DELIM); 22 | while (token != NULL) 23 | { 24 | /* handle comments */ 25 | if (token[0] == '#') 26 | { 27 | break; 28 | } 29 | tokens[i] = token; 30 | i++; 31 | if (i >= bufsize) 32 | { 33 | bufsize += bufsize; 34 | tokens = realloc(tokens, bufsize * sizeof(char *)); 35 | if (!tokens) 36 | { 37 | fprintf(stderr, "reallocation error in split_line: tokens"); 38 | exit(EXIT_FAILURE); 39 | } 40 | } 41 | token = strtok(NULL, TOK_DELIM); 42 | } 43 | tokens[i] = NULL; 44 | return (tokens); 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple Shell 🐚 2 | ![plot](images/imageshell.png) 3 | 4 | ## Description 5 | 6 | Here is the base code for the project/tutorial that I made on how to create a shell using the C programming language. I invite you to follow the link to carry out the project step by step and improve your knowledge of C. 7 | 8 | https://medium.com/@santiagobedoa/coding-a-shell-using-c-1ea939f10e7e 9 | 10 | ## Files 11 | 12 | | Name | Description | 13 | | ------------------------------ | -------------------------------------------- | 14 | | shell.h | Header file program. | 15 | | main.c | Main function, interactive and non-interactive. | 16 | | new_procees.c | Function that creates a new process. | 17 | | own_cd.c | Change the working directory. | 18 | | own_env.c | Function that prints environment variables. | 19 | | own_exit.c | Exit shell with a given state. | 20 | | own_help.c | Function that prints help (get information about a command) | 21 | | read_line.c | Read a line from stdin. | 22 | | read_stream.c | Read a line from the stream. | 23 | | shell_interactive.c | Run shell interactive mode. | 24 | | shell_no_interactive.c | Run shell non-interactive mode. | 25 | | split_line.c | Split a string into tokens. | 26 | | execute_args.c | Number of builtin functions. | 27 | 28 | ## List of functions and system calls. 29 | 30 | * ```chdir``` (man 2 chdir) 31 | * ```exit``` (man 3 exit) 32 | * ```fork``` (man 2 fork) 33 | * ```free``` (man 3 free) 34 | * ```getline``` (man 3 getline) 35 | * ```isatty``` (man 3 isatty) 36 | * ```malloc``` (man 3 malloc) 37 | * ```perror``` (man 3 perror) 38 | * ```strtok``` (man 3 strtok) 39 | * ```waitpid``` (man 2 waitpid) 40 | 41 | ## Install 42 | 43 | Clone this repo and compile as follow: 44 | 45 | > gcc -Wall -Werror -Wextra -pedantic -std=gnu89 *.c -o hsh 46 | 47 | ## Usage 48 | 49 | Interactive mode: ```./hsh``` 50 | 51 | Non-interactive mode: ```echo "/bin/ls" | ./hsh``` 52 | 53 | ### Built-ins 54 | 55 | * [x] ```cd``` 56 | * [x] ```env``` 57 | * [x] ```help``` 58 | * [x] ```exit``` 59 | * [ ] ```setenv``` 60 | * [ ] ```unsetenv``` 61 | 62 | ### Examples 63 | 64 | * **Run shell in interactive mode:** 65 | 66 | ``` 67 | $ ./hsh 68 | simple_prompt$ ls -l 69 | total 72 70 | -rw-r--r-- 1 root root 771 Nov 16 12:01 execute_args.c 71 | -rwxr-xr-x 1 root root 20192 Nov 16 12:31 hsh 72 | -rw-r--r-- 1 root root 307 Nov 16 08:39 main.c 73 | -rw-r--r-- 1 root root 646 Nov 16 11:59 new_process.c 74 | -rw-r--r-- 1 root root 384 Nov 16 12:28 own_cd.c 75 | -rw-r--r-- 1 root root 338 Nov 16 12:28 own_env.c 76 | -rw-r--r-- 1 root root 284 Nov 16 12:29 own_exit.c 77 | -rw-r--r-- 1 root root 591 Nov 16 12:31 own_help.c 78 | -rw-r--r-- 1 root root 590 Nov 16 09:13 read_line.c 79 | -rw-r--r-- 1 root root 815 Nov 16 12:26 read_stream.c 80 | -rw-r--r-- 1 root root 677 Nov 16 12:27 shell.h 81 | -rw-r--r-- 1 root root 516 Nov 16 08:38 shell_interactive.c 82 | -rw-r--r-- 1 root root 442 Nov 16 12:07 shell_no_interactive.c 83 | -rw-r--r-- 1 root root 848 Nov 16 09:35 split_line.c 84 | ``` 85 | ``` 86 | $ /hsh 87 | simple_prompt$ echo “Hello, World!” 88 | “Hello, World!” 89 | ``` 90 | * **Run shell in non-interactive mode:** 91 | 92 | ``` 93 | $ echo "/bin/ls" | ./hsh 94 | execute_args.c new_process.c own_exit.c read_stream.c shell_no_interactive.c 95 | hsh own_cd.c own_help.c shell.h split_line.c 96 | main.c own_env.c read_line.c shell_interactive.c 97 | ``` 98 | 99 |

 

100 | 101 |

Connect with me:

102 |

103 | https://www.linkedin.com/in/santiago-bedoya-arias-852a15233/ 104 | https://twitter.com/santiagobedoa 105 | https://medium.com/@santiagobedoa 106 |

107 |

108 | --------------------------------------------------------------------------------