/* * # noshmore * an extended version of nosh, * the minimal POSIX C99 shell. * * # notable changes * - less memory leaks * - more stable * - cleaner code * - uses GNU readline * - command history * and other fixes and tweaks. * * von czjstmax, */ #define _POSIX_C_SOURCE 200809L #define NOSHMORE_VERSION 2.21 #define MAX_ALIASES 256 #include #include #include #include #include #include #include #include #include #include typedef struct { char *dst; char *src; } Alias; Alias aliases[MAX_ALIASES]; int alias_count = 0; char *line = NULL; char *history_path; wordexp_t p; static const char *RED = "\x1b[31m"; static const char *WHITE = "\x1b[37m"; static const char *ITALIC = "\x1b[3m"; static const char *UNDERLINE = "\x1b[4m"; static const char *RESET = "\x1b[0m"; 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%s%.2f%s\n" "type %s%shelp%s for a list of %sbuilt-in%s commands!\n" , RED, ITALIC, RESET, UNDERLINE, RED, ITALIC, NOSHMORE_VERSION, RESET , RED, ITALIC, RESET, ITALIC, RESET ); } int main(void) { // phase 0 - setup atexit(Cleanup); signal(SIGINT, HandleCC); using_history(); // phase 1 - runtime initialization 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 { fprintf(stderr, "nsh+! ERROR: couldn't load command history\n"); fprintf(stderr, "(%sfirst run? try the %swritehist%s%s command%s)\n", ITALIC, RED, RESET, ITALIC, RESET); } char prompt[strlen(USER) + strlen("$ ") + 1]; snprintf(prompt, sizeof(prompt), "%s$ ", USER); // phase 2 - shell aliases creation //aliases[alias_count++] = CreateAlias("NOSH_HIST", history_path); /* * note: this is broken because the aliasing expansion system sucks. * once ill have reworked the aliasing expansion system and probably * made a tokenizer for it this will come back, for now, get //'d */ // initialization phase completed, now Greet() the USER Greet(); while (true) { line = readline(prompt); if (!line) break; if (*line) { add_history(line); write_history(history_path); } if (wordexp(line, &p, 0) != 0) { fprintf(stderr, "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(); -- taken care of by atexit() return 0; } else if (strcmp(p.we_wordv[0], "echo") == 0 || strcmp(p.we_wordv[0], "@") == 0) { size_t start = 1; bool no_newline = false; if (p.we_wordc >= 2 && (strcmp(p.we_wordv[1], "-c") == 0 || strcmp(p.we_wordv[1], "--clean") == 0)) { no_newline = true; start = 2; } for (size_t i = start; i < p.we_wordc; ++i) { printf("%s", p.we_wordv[i]); if (i < p.we_wordc - 1) printf(" "); } if (!no_newline) printf("\n"); FreeAll(); } 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(%.2f)\tALIASES(%d)\n" "an extended version of %snosh%s, the minimal POSIX C shell.\n" "history file path -> '%s%s%s%s'\n" //"note: you can access this file by the %sNOSH_HIST%s alias\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 //, RED, 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], p.we_wordv); perror("nsh+! ERROR"); // let errno do the work 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]); // never reached (?) } } } } 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); } alias_count = 0; } void Cleanup() { FreeAll(); FreeAliases(); free(history_path); }