codecrafters-shell-c

personal solution to the "Build Your Own Shell" challenge from codecrafters
git clone https://github.com/5hif7y/codecrafters-shell-c.git
Log | Files | Refs | README

main.c (5192B)


      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <stdbool.h>
      4 #include <stdlib.h>
      5 
      6 #ifdef _WIN32
      7   #define WIN32_LEAN_AND_MEAN
      8   #include <windows.h>
      9   #define F_OK 0
     10 #else
     11   #include <sys/wait.h>
     12   #include <unistd.h>
     13 #endif
     14 
     15 void parse_input(const char *input, char *cmd, char **args, int *argc);
     16 bool find_executable(const char *cmd, char *result_path);
     17 void run_executable(const char *cmd, char **args);
     18 
     19 int main() {
     20 
     21   // Process user input
     22   bool running = true;
     23   char input[100];
     24   char cmd[100];
     25   char *args[10];
     26   int argc;
     27 
     28   while(running){
     29     // Flush after every printf
     30     setbuf(stdout, NULL);
     31     printf("$ ");
     32 
     33     // Exit on error
     34     if(fgets(input, sizeof(input), stdin) == NULL){
     35       running = false;
     36     }
     37 
     38     // remove \n
     39     int n = strlen(input);
     40     if (n > 0 && input[n - 1] == '\n'){
     41       input[n - 1] = '\0';
     42     }
     43 
     44     // Parse input
     45     parse_input(input, cmd, args, &argc);
     46 
     47     // Evaluate commands:
     48     if(!strcmp(cmd, "exit")){
     49       //printf("Exiting...\n");
     50       running = false;
     51     } else if (!strcmp(cmd, "echo")){
     52       for (int i = 0; i < argc; i++){
     53         printf("%s ", args[i]);
     54       }
     55       printf("\n");
     56     } else if (!strcmp(cmd, "type")){
     57 	if (argc > 0){
     58 	  char path[512];
     59           if (!strcmp(args[0], "echo")
     60 	      || !strcmp(args[0], "exit")
     61   	      || !strcmp(args[0], "type")){
     62             printf("%s is a shell builtin\n", args[0]);
     63 	  } else if (find_executable(args[0], path)) {
     64             printf("%s is %s\n", args[0], path);
     65   	  } else {
     66             printf("%s not found\n", args[0]);
     67   	  }
     68 	} else {
     69 	  printf("Usage: type [command]\n");
     70 	}
     71       } else {
     72 	char path[512];
     73 	if (find_executable(cmd, path)){
     74 	  run_executable(path, args);
     75 	} else {
     76           printf("%s: command not found\n", cmd);
     77 	}
     78       }
     79 
     80     //free mem
     81     for (int i = 0; i < argc; i++){
     82       free(args[i]);
     83     }
     84   }
     85   return 0;
     86 }
     87 
     88 
     89 void parse_input(const char *input, char *cmd, char **args, int *argc){
     90 
     91   char input_cpy[100];
     92   strncpy(input_cpy, input, sizeof(input_cpy) - 1);
     93   input_cpy[sizeof(input_cpy) - 1] = '\0';
     94 
     95   char *token = strtok(input_cpy, " ");
     96   if(token != NULL){
     97     strcpy(cmd, token);
     98   }
     99 
    100   *argc = 0;
    101   while((token = strtok(NULL, " ")) != NULL){
    102     args[*argc] = malloc(strlen(token) + 1);
    103     if (args[*argc] != NULL) {
    104       strcpy(args[*argc], token);
    105       (*argc)++;
    106     }
    107   }
    108 
    109   args[*argc] = NULL;
    110 }
    111 
    112 bool find_executable(const char *cmd, char *result_path){
    113 
    114   // First, search in current folder
    115   #ifdef _WIN32
    116     const char *extensions[] = {".exe", NULL}; // Extension support list
    117     // ------------
    118     if (strchr(cmd, '.')){
    119       snprintf(result_path, 512, ".\\%s", cmd);
    120       if (access(result_path, F_OK) == 0){
    121         return true;
    122       }
    123     } else {
    124       for (const char **ext = extensions; *ext != NULL; ext++){
    125         snprintf(result_path, 512, ".\\%s%s", cmd, *ext);
    126         if (access(result_path, F_OK) == 0){
    127           return true;
    128         }
    129       }
    130 
    131   #else
    132     // In POSIX systems, simply search in current folder
    133     snprintf(result_path, 512, "./%s", cmd);
    134     if (access(result_path, F_OK) == 0){
    135       return true;
    136     }
    137   #endif
    138 
    139   // Second, search in PATH
    140   char *path_env = getenv("PATH");
    141   if(!path_env){
    142     return false;
    143   }
    144 
    145   char path_copy[1024];
    146   strncpy(path_copy, path_env, sizeof(path_copy) - 1);
    147   path_copy[sizeof(path_copy) - 1] = '\0';
    148 
    149   char separator = ':';
    150   #ifdef _WIN32
    151     separator = ';';
    152   #endif
    153 
    154   char *dir = strtok(path_copy, &separator);
    155   while (dir != NULL){
    156     if (strlen(dir) > 0) {
    157   #ifdef _WIN32
    158     if (strchr(cmd, '.')){
    159       snprintf(result_path, 512, "%s\\%s", dir, cmd);
    160       if (access(result_path, F_OK) == 0){
    161         return true;
    162       }
    163     } else {
    164       for (const char **ext = extensions; *ext != NULL; ext++){
    165         snprintf(result_path, 512, "%s\\%s%s", dir, cmd, *ext);
    166         if (access(result_path, F_OK) == 0){
    167           return true;
    168         }
    169       }
    170     }
    171   #else
    172     snprintf(result_path, 512, "%s/%s", dir, cmd);
    173     if(access(result_path, X_OK) == 0){
    174       return true;
    175     }
    176   #endif
    177     }
    178     dir = strtok(NULL, &separator);
    179   }
    180 
    181   return false;
    182 }
    183 
    184 void run_executable(const char *cmd, char **args){
    185   #ifdef _WIN32
    186   // 1. Build command line
    187   char command_line[1024] = {0};
    188   snprintf(command_line, sizeof(command_line), "\"%s\"", cmd);
    189 
    190   for (int i = 0; args[i] != NULL; i++){
    191     strncat(command_line, " ", sizeof(command_line) - strlen(command_line) - 1);
    192     strncat(command_line, args[i], sizeof(command_line) - strlen(command_line) - 1);
    193   }
    194 
    195   // 2. Setup structures for 'CreateProcess'
    196   STARTUPINFO si = {0};
    197   PROCESS_INFORMATION pi = {0};
    198   si.cb = sizeof(STARTUPINFO);
    199 
    200   // 3. Try to create process
    201   if (!CreateProcess(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)){
    202     fprintf(stderr, "Error: error executing command %s\n", cmd);
    203   } else {
    204     WaitForSingleObject(pi.hProcess, INFINITE);
    205     CloseHandle(pi.hProcess);
    206     CloseHandle(pi.hThread);
    207   }
    208 
    209   #else
    210     // Use 'execvp' in POSIX systems
    211     if (fork() == 0){
    212       execvp(cmd, args);
    213       perror("Error executing command");
    214       exit(EXIT_FAILURE);
    215     } else {
    216       wait(NULL);
    217     }
    218   #endif
    219 }
    220 
    221