/* noshmore * an extended version of nosh, * the minimal POSIX C shell. * * # notable changes * - less memory leaks (almost none) * - more stable * - cleaner code (ish) * - GNU readline * - command history * and more fixes and tweaks. * * von czjstmax, */ #include #include #include #include #include #include #include #include #include #include #define NOSHMORE_VERSION 2.1 #define MAX_ALIASES 256 typedef struct { char *dst; char *src; } Alias; Alias aliases[MAX_ALIASES]; int alias_count = 0; char *line = NULL; char *history_path; wordexp_t p; const char *RED = "\x1b[31m"; const char *WHITE = "\x1b[37m"; const char *RESET = "\x1b[0m"; const char *ITALIC = "\x1b[3m"; const char *UNDERLINE = "\x1b[4m"; void HandleCC (int sig); Alias CreateAlias (char *dst, char *src); void FreeAll (); void FreeAliases (); void Cleanup (); void Greet() { printf( "welcome to %s%snoshmore%s | spinning version %s%s%.1f%s\n" "type %s%shelp%s for a list of %sbuilt-in%s commands!\n" , RED, ITALIC, RESET, RED, ITALIC, NOSHMORE_VERSION, RESET , RED, ITALIC, RESET, ITALIC, RESET ); } int main(void) { atexit(Cleanup); signal(SIGINT, HandleCC); using_history(); char *USER = getenv("USER"); if (!USER) USER = "anon"; char *HOME = getenv("HOME"); if (!HOME) HOME = "."; size_t history_path_len = strlen(HOME) + strlen("/.nshm_hist") + 1; history_path = malloc(history_path_len); snprintf(history_path, history_path_len, "%s/.nshm_hist", HOME); if (access(history_path, F_OK) == 0) read_history(history_path); else { perror("nsh+! ERROR: couldn't load command history"); fprintf(stderr, "(%sfirst run? try the %swritehist%s%s command%s)\n", ITALIC, RED, RESET, ITALIC, RESET); } // initialization phase completed, now Greet() the USER Greet(); while (true) { char prompt[strlen(USER) + strlen("$ ") + 1]; snprintf(prompt, sizeof(prompt), "%s$ ", USER); line = readline(prompt); if (!line) break; if (*line) { add_history(line); write_history(history_path); } if (wordexp(line, &p, 0) != 0) { perror("nsh+! ERROR while parsing input\n"); FreeAll(); continue; } // built-ins if (p.we_wordc == 0) { FreeAll(); continue; } if (strcmp(p.we_wordv[0], ":q") == 0 || strcmp(p.we_wordv[0], "exit") == 0) { //Cleanup(); return 0; } else if (strcmp(p.we_wordv[0], "echo") == 0) { for (size_t i = 1; i < p.we_wordc; i++) printf("%s ", p.we_wordv[i]); if (p.we_wordc >= 2) { if (strcmp(p.we_wordv[1], "-c") != 0) printf("\n"); else continue; } } else if (strcmp(p.we_wordv[0], "alias") == 0) { if (p.we_wordc <= 2) { fprintf(stderr, "nsh+! ERROR: expected 2 args\n"); } else { // overwrite if exists bool found = false; for (int i = 0; i < alias_count; i++) { if (strcmp(aliases[i].dst, p.we_wordv[1]) == 0) { free(aliases[i].src); aliases[i].src = strdup(p.we_wordv[2]); found = true; break; } } if (!found && alias_count < MAX_ALIASES) { aliases[alias_count++] = CreateAlias(p.we_wordv[1], p.we_wordv[2]); } } } else if (strcmp(p.we_wordv[0], "cd") == 0) { if (p.we_wordc <= 1) { if (chdir(HOME)) perror("nsh+! ERROR: cannot retrieve HOME, failed to change directory"); FreeAll(); continue; } if (chdir(p.we_wordv[1])) { perror("nsh+! ERROR: failed to change directory"); FreeAll(); } } else if (strcmp(p.we_wordv[0], "clear") == 0) { printf("\033[2J\033[H\033[3J"); fflush(stdout); FreeAll(); } else if (strcmp(p.we_wordv[0], "svhist") == 0 || strcmp(p.we_wordv[0], "writehist") == 0) { write_history(history_path); FreeAll(); } else if (strcmp(p.we_wordv[0], "readhist") == 0 || strcmp(p.we_wordv[0], "loadhist") == 0) { if (!read_history(history_path)) perror("nsh+! ERROR: couldn't load command history"); FreeAll(); } else if (strcmp(p.we_wordv[0], "rmhist") == 0 || strcmp(p.we_wordv[0], "cleanhist") == 0) { clear_history(); FreeAll(); } else if (strcmp(p.we_wordv[0], "help") == 0) { printf( "%snoshmore%s\tVERSION(%.1f)\tALIASES(%d)\n" "an extended version of %snosh%s, the minimal POSIX C shell.\n" "history file path -> '%s%s%s%s'\n" "\n" "## built-ins:\n" "echo -> outputs a string of text, doesn't need \" enclosure.\n" " supports '-c' for output without '\\n'\n" "clear -> clears screen (and scrollback)\n" "alias -> creates non-persistent command aliases\n" " (i.e. '%salias v vim%s')\n" "cd -> change working directory\n" "writehist -> writes current sesh command history to disk\n" " aliases: svhist\n" "readhist -> reads last written sesh command history from disk\n" " aliases: loadhist\n" "cleanhist -> clears sesh command history\n" " aliases: rmhist\n" "help -> prints this screen\n" "@ github.com/jstmaxlol/noshmore\n" , RED, RESET, NOSHMORE_VERSION, MAX_ALIASES , ITALIC, RESET , RED, ITALIC, history_path, RESET , ITALIC, RESET /* make no mistakes: i know i can just #define RED_FMT(s) RED s RESET and make this printf() 10x more readable and lovely, but i wont. */ ); FreeAll(); } else { for (int i = 0; i < alias_count; i++) { if (strcmp(p.we_wordv[0], aliases[i].dst) == 0) { char new_line[81920] = {0}; // goofy ass size ngl strcat(new_line, aliases[i].src); for (size_t j = 1; j < p.we_wordc; j++) { strcat(new_line, " "); strcat(new_line, p.we_wordv[j]); } wordfree(&p); if (wordexp(new_line, &p, 0) != 0) { fprintf(stderr, "nsh+! ERROR: alias expansion failed\n"); break; } break; } } pid_t pid = fork(); char **argv = p.we_wordv; // pray with me now if (pid == 0) { execvp(p.we_wordv[0], argv); perror("nsh+! ERROR: failed to execvp()"); FreeAll(); free(history_path); _exit(1); } else if (pid > 0) { int status; waitpid(pid, &status, 0); } else { fprintf(stderr, "nsh+! ERROR: %s not found\n", p.we_wordv[0]); } } } } void HandleCC(int sig) { (void)sig; rl_replace_line("", 0); printf("\n"); rl_on_new_line(); rl_redisplay(); } Alias CreateAlias(char *dst, char *src) { Alias alias; alias.dst = strdup(dst); alias.src = strdup(src); return alias; } void FreeAll() { wordfree(&p); free(line); line = NULL; } void FreeAliases() { for (int i = 0; i < alias_count; i++) { free(aliases[i].dst); free(aliases[i].src); } } void Cleanup() { FreeAll(); FreeAliases(); free(history_path); }