diff --git a/Userspace/coreutils/src/sh.c b/Userspace/coreutils/src/sh.c
new file mode 100644
index 00000000..aba8e7f4
--- /dev/null
+++ b/Userspace/coreutils/src/sh.c
@@ -0,0 +1,333 @@
+/*
+ This file is part of Fennix C Library.
+
+ Fennix C Library is free software: you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of
+ the License, or (at your option) any later version.
+
+ Fennix C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Fennix C Library. If not, see .
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define MAX_LINE_LEN 1024
+#define MAX_HISTORY 128
+#define CTRL_KEY(k) ((k) & 0x1f)
+
+typedef struct
+{
+ char *items[MAX_HISTORY];
+ int count;
+ int index;
+} History;
+
+typedef enum
+{
+ MODE_INSERT,
+ MODE_COMMAND
+} InputMode;
+
+typedef struct
+{
+ char line[MAX_LINE_LEN];
+ int cursor;
+ int len;
+ InputMode mode;
+ History history;
+ struct termios orig_termios;
+} ShellState;
+
+void HandleSignalInterrupt(int sig)
+{
+ (void)sig;
+ write(STDOUT_FILENO, "\n", 1);
+}
+
+void EnableRawMode(ShellState *state)
+{
+ tcgetattr(STDIN_FILENO, &state->orig_termios);
+ struct termios raw = state->orig_termios;
+ raw.c_lflag &= ~(ECHO | ICANON);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
+}
+
+void DisableRawMode(ShellState *state)
+{
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->orig_termios);
+}
+
+void AddHistory(ShellState *state, const char *line)
+{
+ if (state->history.count < MAX_HISTORY)
+ state->history.items[state->history.count++] = strdup(line);
+ else
+ {
+ free(state->history.items[0]);
+ memmove(state->history.items, state->history.items + 1,
+ (MAX_HISTORY - 1) * sizeof(char *));
+ state->history.items[MAX_HISTORY - 1] = strdup(line);
+ }
+ state->history.index = state->history.count;
+}
+
+void LoadHistory(ShellState *state)
+{
+ char *home = getenv("HOME");
+ if (!home)
+ return;
+
+ char path[256];
+ snprintf(path, sizeof(path), "%s/.sh_history", home);
+
+ FILE *fp = fopen(path, "r");
+ if (!fp)
+ return;
+
+ char line[MAX_LINE_LEN];
+ while (fgets(line, sizeof(line), fp))
+ {
+ line[strcspn(line, "\n")] = '\0';
+ AddHistory(state, line);
+ }
+ fclose(fp);
+}
+
+void SaveHistory(ShellState *state)
+{
+ char *home = getenv("HOME");
+ if (!home)
+ return;
+
+ char path[256];
+ snprintf(path, sizeof(path), "%s/.sh_history", home);
+
+ FILE *fp = fopen(path, "w");
+ if (!fp)
+ return;
+
+ for (int i = 0; i < state->history.count; i++)
+ {
+ fprintf(fp, "%s\n", state->history.items[i]);
+ }
+ fclose(fp);
+}
+
+void ProcessViCommand(ShellState *state, char c)
+{
+ switch (c)
+ {
+ case 'h':
+ if (state->cursor > 0)
+ state->cursor--;
+ break;
+ case 'l':
+ if (state->cursor < state->len)
+ state->cursor++;
+ break;
+ case 'k':
+ {
+ if (state->history.index > 0)
+ {
+ state->history.index--;
+ strcpy(state->line, state->history.items[state->history.index]);
+ state->len = strlen(state->line);
+ state->cursor = state->len;
+ }
+ break;
+ }
+ case 'j':
+ {
+ if (state->history.index < state->history.count - 1)
+ {
+ state->history.index++;
+ strcpy(state->line, state->history.items[state->history.index]);
+ state->len = strlen(state->line);
+ state->cursor = state->len;
+ }
+ break;
+ }
+ case 'i':
+ state->mode = MODE_INSERT;
+ break;
+ case 27:
+ state->mode = MODE_COMMAND;
+ break;
+ }
+}
+
+void ReadLine(ShellState *state)
+{
+ state->len = 0;
+ state->cursor = 0;
+ state->line[0] = '\0';
+ state->mode = MODE_INSERT;
+
+ printf("$ ");
+ fflush(stdout);
+
+ EnableRawMode(state);
+
+ while (1)
+ {
+ char c = '\0';
+ if (read(STDIN_FILENO, &c, 1) != 1)
+ break;
+
+ if (state->mode == MODE_COMMAND)
+ ProcessViCommand(state, c);
+ else
+ {
+ if (c == CTRL_KEY('c'))
+ {
+ printf("\n");
+ return;
+ }
+ else if (c == '\n')
+ {
+ printf("\n");
+ break;
+ }
+ else if (c == 127)
+ {
+ if (state->cursor > 0)
+ {
+ memmove(&state->line[state->cursor - 1],
+ &state->line[state->cursor],
+ state->len - state->cursor + 1);
+ state->cursor--;
+ state->len--;
+ }
+ }
+ else if (state->len < MAX_LINE_LEN - 1)
+ {
+ memmove(&state->line[state->cursor + 1],
+ &state->line[state->cursor],
+ state->len - state->cursor + 1);
+ state->line[state->cursor] = c;
+ state->cursor++;
+ state->len++;
+ }
+ }
+
+ printf("\r\x1B[2K$ %s", state->line);
+ fflush(stdout);
+ printf("\r\x1B[%dC", state->cursor + 3);
+ }
+
+ DisableRawMode(state);
+ AddHistory(state, state->line);
+}
+
+void ExecuteCommand(char **args)
+{
+ if (args[0] == NULL)
+ return;
+
+ if (strcmp(args[0], "exit") == 0)
+ exit(0);
+ if (strcmp(args[0], "cd") == 0)
+ {
+ if (args[1] == NULL)
+ fprintf(stderr, "cd: missing argument\n");
+ else if (chdir(args[1]))
+ perror("cd");
+ return;
+ }
+
+ pid_t pid = fork();
+ if (pid == 0)
+ {
+ execvp(args[0], args);
+ perror("execvp");
+ exit(EXIT_FAILURE);
+ }
+ else if (pid > 0)
+ wait(NULL);
+ else
+ perror("fork");
+}
+
+void ShellLoop(ShellState *state)
+{
+ signal(SIGINT, HandleSignalInterrupt);
+ LoadHistory(state);
+
+ while (1)
+ {
+ ReadLine(state);
+ if (state->len == 0)
+ continue;
+
+ char *args[MAX_LINE_LEN / 2 + 1];
+ char *token = strtok(state->line, " ");
+ int i = 0;
+ while (token != NULL)
+ {
+ args[i++] = token;
+ token = strtok(NULL, " ");
+ }
+ args[i] = NULL;
+
+ ExecuteCommand(args);
+ }
+
+ SaveHistory(state);
+}
+
+void InitializeShell(ShellState *state)
+{
+ memset(state, 0, sizeof(ShellState));
+ LoadHistory(state);
+ signal(SIGINT, HandleSignalInterrupt);
+}
+
+int main(int argc, char *argv[])
+{
+ ShellState state = {0};
+ InitializeShell(&state);
+
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "-c") == 0)
+ ExecuteCommand(&argv[2]);
+ else
+ {
+ FILE *fp = fopen(argv[1], "r");
+ if (!fp)
+ {
+ perror("fopen");
+ exit(EXIT_FAILURE);
+ }
+ char line[MAX_LINE_LEN];
+ while (fgets(line, sizeof(line), fp))
+ {
+ line[strcspn(line, "\n")] = '\0';
+ char *args[] = {"/bin/sh", "-c", line, NULL};
+ ExecuteCommand(args);
+ }
+ fclose(fp);
+ }
+ }
+ else
+ ShellLoop(&state);
+
+ return EXIT_SUCCESS;
+}