diff --git a/Userspace/coreutils/CMakeLists.txt b/Userspace/coreutils/CMakeLists.txt
index 074c3606..f9440128 100644
--- a/Userspace/coreutils/CMakeLists.txt
+++ b/Userspace/coreutils/CMakeLists.txt
@@ -57,3 +57,11 @@ if(UNIX AND TARGET test)
\"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/[\"
)")
endif()
+
+if(UNIX AND TARGET sh)
+ install(CODE "execute_process(
+ COMMAND ${CMAKE_COMMAND} -E create_symlink
+ sh
+ \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/bin/fsh\"
+ )")
+endif()
diff --git a/Userspace/coreutils/src/sh.c b/Userspace/coreutils/src/sh.c
index aba8e7f4..7214379b 100644
--- a/Userspace/coreutils/src/sh.c
+++ b/Userspace/coreutils/src/sh.c
@@ -15,6 +15,7 @@
along with Fennix C Library. If not, see .
*/
+#define _GNU_SOURCE
#include
#include
#include
@@ -27,16 +28,22 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
#define MAX_LINE_LEN 1024
#define MAX_HISTORY 128
-#define CTRL_KEY(k) ((k) & 0x1f)
+#define CTRL_KEY(k) ((k) & 0x1F)
+#define MAX_COMPLETIONS 128
typedef struct
{
char *items[MAX_HISTORY];
int count;
- int index;
+ int Index;
} History;
typedef enum
@@ -49,43 +56,85 @@ typedef struct
{
char line[MAX_LINE_LEN];
int cursor;
- int len;
+ int length;
InputMode mode;
History history;
- struct termios orig_termios;
+ struct termios origTermios;
+ char prompt[PATH_MAX + 128];
+ int promptLength;
} ShellState;
+typedef struct
+{
+ char *items[MAX_COMPLETIONS];
+ int count;
+} CompletionList;
+
+static ShellState GlobalShellState;
+static int progIsFsh = 0;
+
+void DisableRawMode(ShellState *state);
+void SaveHistory(ShellState *state);
+void InitializeShell(ShellState *state);
+void FreeCompletionList(CompletionList *list);
+CompletionList GetCompletions(const char *partial);
+void DisplayCompletions(ShellState *state, CompletionList *list);
+void UpdatePrompt(ShellState *state);
+int GetVisibleLength(const char *str);
+
+void CleanupAndExit(int code)
+{
+ SaveHistory(&GlobalShellState);
+ DisableRawMode(&GlobalShellState);
+ exit(code);
+}
+
void HandleSignalInterrupt(int sig)
{
(void)sig;
- write(STDOUT_FILENO, "\n", 1);
+ write(STDOUT_FILENO, "\n\r", 2);
+ CleanupAndExit(130);
}
void EnableRawMode(ShellState *state)
{
- tcgetattr(STDIN_FILENO, &state->orig_termios);
- struct termios raw = state->orig_termios;
- raw.c_lflag &= ~(ECHO | ICANON);
+ tcgetattr(STDIN_FILENO, &state->origTermios);
+ struct termios raw = state->origTermios;
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ raw.c_oflag &= ~(OPOST);
+ raw.c_cflag |= (CS8);
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ raw.c_cc[VMIN] = 1;
+ raw.c_cc[VTIME] = 0;
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
}
void DisableRawMode(ShellState *state)
{
- tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->orig_termios);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &state->origTermios);
}
void AddHistory(ShellState *state, const char *line)
{
+ if (!line || strlen(line) == 0)
+ return;
+
+ char *newEntry = strdup(line);
+ if (!newEntry)
+ return;
+
if (state->history.count < MAX_HISTORY)
- state->history.items[state->history.count++] = strdup(line);
+ state->history.items[state->history.count++] = newEntry;
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.items[MAX_HISTORY - 1] = newEntry;
}
- state->history.index = state->history.count;
+ state->history.Index = state->history.count;
+
+ SaveHistory(state);
}
void LoadHistory(ShellState *state)
@@ -94,20 +143,42 @@ void LoadHistory(ShellState *state)
if (!home)
return;
- char path[256];
- snprintf(path, sizeof(path), "%s/.sh_history", home);
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/.fsh_history", home);
FILE *fp = fopen(path, "r");
if (!fp)
return;
+ char **tmpHistory = malloc(MAX_HISTORY * sizeof(char *));
+ int tmpCount = 0;
+
char line[MAX_LINE_LEN];
while (fgets(line, sizeof(line), fp))
{
- line[strcspn(line, "\n")] = '\0';
- AddHistory(state, line);
+ size_t len = strlen(line);
+ if (len > 0 && line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ if (strlen(line) > 0)
+ {
+ if (tmpCount < MAX_HISTORY)
+ tmpHistory[tmpCount++] = strdup(line);
+ else
+ {
+ free(tmpHistory[0]);
+ memmove(tmpHistory, tmpHistory + 1, (MAX_HISTORY - 1) * sizeof(char *));
+ tmpHistory[MAX_HISTORY - 1] = strdup(line);
+ }
+ }
}
fclose(fp);
+
+ for (int i = 0; i < tmpCount; i++)
+ state->history.items[i] = tmpHistory[i];
+ state->history.count = tmpCount;
+ state->history.Index = tmpCount;
+ free(tmpHistory);
}
void SaveHistory(ShellState *state)
@@ -116,18 +187,39 @@ void SaveHistory(ShellState *state)
if (!home)
return;
- char path[256];
- snprintf(path, sizeof(path), "%s/.sh_history", home);
+ char path[PATH_MAX];
+ snprintf(path, sizeof(path), "%s/.fsh_history", home);
- FILE *fp = fopen(path, "w");
- if (!fp)
+ int fd = open(path, O_WRONLY | O_APPEND | O_CREAT, 0600);
+ if (fd < 0)
return;
- for (int i = 0; i < state->history.count; i++)
+ struct flock fl = {
+ .l_type = F_WRLCK,
+ .l_whence = SEEK_SET,
+ .l_start = 0,
+ .l_len = 0};
+
+ if (fcntl(fd, F_SETLKW, &fl) == -1)
{
- fprintf(fp, "%s\n", state->history.items[i]);
+ close(fd);
+ return;
}
- fclose(fp);
+
+ if (state->history.count > 0)
+ {
+ int last = state->history.count - 1;
+ if (state->history.items[last])
+ {
+ char *cmd = state->history.items[last];
+ write(fd, cmd, strlen(cmd));
+ write(fd, "\n", 1);
+ }
+ }
+
+ fl.l_type = F_UNLCK;
+ fcntl(fd, F_SETLK, &fl);
+ close(fd);
}
void ProcessViCommand(ShellState *state, char c)
@@ -139,31 +231,27 @@ void ProcessViCommand(ShellState *state, char c)
state->cursor--;
break;
case 'l':
- if (state->cursor < state->len)
+ if (state->cursor < state->length)
state->cursor++;
break;
case 'k':
- {
- if (state->history.index > 0)
+ 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;
+ state->history.Index--;
+ strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
+ state->length = strlen(state->line);
+ state->cursor = state->length;
}
break;
- }
case 'j':
- {
- if (state->history.index < state->history.count - 1)
+ 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;
+ state->history.Index++;
+ strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN);
+ state->length = strlen(state->line);
+ state->cursor = state->length;
}
break;
- }
case 'i':
state->mode = MODE_INSERT;
break;
@@ -173,94 +261,471 @@ void ProcessViCommand(ShellState *state, char c)
}
}
+void FreeCompletionList(CompletionList *list)
+{
+ for (int i = 0; i < list->count; i++)
+ free(list->items[i]);
+ list->count = 0;
+}
+
+CompletionList GetCompletions(const char *partial)
+{
+ CompletionList list = {0};
+ char *path = getenv("PATH");
+ if (!path)
+ return list;
+
+ char *pathCopy = strdup(path);
+ char *dir = strtok(pathCopy, ":");
+
+ while (dir && list.count < MAX_COMPLETIONS)
+ {
+ DIR *d = opendir(dir);
+ if (!d)
+ {
+ dir = strtok(NULL, ":");
+ continue;
+ }
+
+ struct dirent *entry;
+ while ((entry = readdir(d)) && list.count < MAX_COMPLETIONS)
+ {
+ if (strncmp(entry->d_name, partial, strlen(partial)) == 0)
+ {
+ char fullPath[PATH_MAX];
+ snprintf(fullPath, sizeof(fullPath), "%s/%s", dir, entry->d_name);
+
+ struct stat st;
+ if (stat(fullPath, &st) == 0 && (st.st_mode & S_IXUSR))
+ list.items[list.count++] = strdup(entry->d_name);
+ }
+ }
+ closedir(d);
+ dir = strtok(NULL, ":");
+ }
+
+ free(pathCopy);
+ return list;
+}
+
+void DisplayCompletions(ShellState *state, CompletionList *list)
+{
+ if (list->count == 0)
+ return;
+
+ write(STDOUT_FILENO, "\n\r", 2);
+
+ for (int i = 0; i < list->count; i++)
+ {
+ write(STDOUT_FILENO, list->items[i], strlen(list->items[i]));
+ write(STDOUT_FILENO, " ", 2);
+ }
+ write(STDOUT_FILENO, "\n\r", 2);
+
+ write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
+ write(STDOUT_FILENO, state->line, state->length);
+}
+
void ReadLine(ShellState *state)
{
- state->len = 0;
+ state->length = 0;
state->cursor = 0;
state->line[0] = '\0';
state->mode = MODE_INSERT;
- printf("$ ");
- fflush(stdout);
+ UpdatePrompt(state);
+
+ write(STDOUT_FILENO, "\r", 1);
+ write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
EnableRawMode(state);
while (1)
{
- char c = '\0';
- if (read(STDIN_FILENO, &c, 1) != 1)
- break;
+ char c;
+ ssize_t nread = read(STDIN_FILENO, &c, 1);
+ if (nread <= 0)
+ continue;
if (state->mode == MODE_COMMAND)
ProcessViCommand(state, c);
else
{
- if (c == CTRL_KEY('c'))
+ if (c == '\t')
{
- printf("\n");
+
+ char *wordStart = state->line;
+ for (int i = state->cursor - 1; i >= 0; i--)
+ {
+ if (state->line[i] == ' ')
+ {
+ wordStart = &state->line[i + 1];
+ break;
+ }
+ }
+
+ char partial[MAX_LINE_LEN];
+ int len = state->cursor - (wordStart - state->line);
+ strncpy(partial, wordStart, len);
+ partial[len] = '\0';
+
+ CompletionList completions = GetCompletions(partial);
+
+ if (completions.count == 1)
+ {
+
+ int restLength = strlen(completions.items[0]) - len;
+ if (restLength > 0)
+ {
+ memmove(&state->line[state->cursor + restLength],
+ &state->line[state->cursor],
+ state->length - state->cursor + 1);
+ memcpy(&state->line[state->cursor],
+ &completions.items[0][len],
+ restLength);
+ state->cursor += restLength;
+ state->length += restLength;
+ }
+ }
+ else if (completions.count > 1)
+ {
+
+ int prefixLength = len;
+ int canExtend = 1;
+
+ while (canExtend)
+ {
+ char nextChar = completions.items[0][prefixLength];
+ if (nextChar == '\0')
+ break;
+
+ for (int i = 1; i < completions.count; i++)
+ {
+ if (completions.items[i][prefixLength] != nextChar)
+ {
+ canExtend = 0;
+ break;
+ }
+ }
+ if (canExtend)
+ prefixLength++;
+ }
+
+ if (prefixLength > len)
+ {
+
+ int restLength = prefixLength - len;
+ memmove(&state->line[state->cursor + restLength],
+ &state->line[state->cursor],
+ state->length - state->cursor + 1);
+ memcpy(&state->line[state->cursor],
+ &completions.items[0][len],
+ restLength);
+ state->cursor += restLength;
+ state->length += restLength;
+ }
+
+ DisplayCompletions(state, &completions);
+ }
+
+ FreeCompletionList(&completions);
+ continue;
+ }
+ else if (c == 0x1B)
+ {
+ char seq[3];
+ if (read(STDIN_FILENO, &seq[0], 1) != 1)
+ continue;
+
+ if (read(STDIN_FILENO, &seq[1], 1) != 1)
+ continue;
+
+ if (seq[0] == '[')
+ {
+ switch (seq[1])
+ {
+ case 'A':
+ if (state->history.Index > 0)
+ {
+ state->history.Index--;
+ strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
+ state->length = strlen(state->line);
+ state->cursor = state->length;
+ }
+ break;
+ case 'B':
+ if (state->history.Index < state->history.count)
+ {
+ state->history.Index++;
+ if (state->history.Index == state->history.count)
+ {
+ state->line[0] = '\0';
+ state->length = 0;
+ state->cursor = 0;
+ }
+ else
+ {
+ strncpy(state->line, state->history.items[state->history.Index], MAX_LINE_LEN - 1);
+ state->length = strlen(state->line);
+ state->cursor = state->length;
+ }
+ }
+ break;
+ case 'C':
+ if (state->cursor < state->length)
+ state->cursor++;
+ break;
+ case 'D':
+ if (state->cursor > 0)
+ state->cursor--;
+ break;
+ case 'H':
+ state->cursor = 0;
+ break;
+ case 'F':
+ state->cursor = state->length;
+ break;
+ case '3':
+ {
+ if (read(STDIN_FILENO, &seq[2], 1) != 1)
+ continue;
+
+ if (seq[2] == '~' && state->cursor < state->length)
+ {
+ memmove(&state->line[state->cursor], &state->line[state->cursor + 1],
+ state->length - state->cursor);
+ state->length--;
+ }
+ break;
+ }
+ }
+ }
+ else if (seq[0] == 'O')
+ {
+ switch (seq[1])
+ {
+ case 'H':
+ state->cursor = 0;
+ break;
+ case 'F':
+ state->cursor = state->length;
+ break;
+ }
+ }
+ }
+ else if (c == CTRL_KEY('c'))
+ {
+ write(STDOUT_FILENO, "\n\r", 2);
+ write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
+ state->length = 0;
+ state->cursor = 0;
+ state->line[0] = '\0';
+ }
+ else if (c == '\r' || c == '\n')
+ {
+ write(STDOUT_FILENO, "\n", 1);
+ state->line[state->length] = '\0';
+ DisableRawMode(state);
+ if (state->length > 0)
+ {
+ AddHistory(state, state->line);
+ state->history.Index = state->history.count;
+ }
return;
}
- else if (c == '\n')
- {
- printf("\n");
- break;
- }
- else if (c == 127)
+ else if (c == 0x7F)
{
if (state->cursor > 0)
{
- memmove(&state->line[state->cursor - 1],
- &state->line[state->cursor],
- state->len - state->cursor + 1);
+ memmove(&state->line[state->cursor - 1], &state->line[state->cursor],
+ state->length - state->cursor + 1);
state->cursor--;
- state->len--;
+ state->length--;
}
}
- else if (state->len < MAX_LINE_LEN - 1)
+ else if (c == CTRL_KEY('d') && state->length == 0)
{
- memmove(&state->line[state->cursor + 1],
- &state->line[state->cursor],
- state->len - state->cursor + 1);
+ write(STDOUT_FILENO, "\n", 1);
+ DisableRawMode(state);
+ exit(EXIT_SUCCESS);
+ }
+ else if (isprint(c) && state->length < MAX_LINE_LEN - 1)
+ {
+ memmove(&state->line[state->cursor + 1], &state->line[state->cursor],
+ state->length - state->cursor + 1);
state->line[state->cursor] = c;
state->cursor++;
- state->len++;
+ state->length++;
}
}
- printf("\r\x1B[2K$ %s", state->line);
- fflush(stdout);
- printf("\r\x1B[%dC", state->cursor + 3);
+ write(STDOUT_FILENO, "\r", 1);
+ write(STDOUT_FILENO, "\x1b[K", 3);
+ write(STDOUT_FILENO, state->prompt, strlen(state->prompt));
+ write(STDOUT_FILENO, state->line, state->length);
+
+ int promptVisibleLength = GetVisibleLength(state->prompt);
+ char buf[32];
+ snprintf(buf, sizeof(buf), "\r\x1b[%dC", promptVisibleLength + state->cursor);
+ write(STDOUT_FILENO, buf, strlen(buf));
+ }
+}
+
+int GetVisibleLength(const char *str)
+{
+ int len = 0;
+ int inEscape = 0;
+
+ while (*str)
+ {
+ if (*str == '\x1b')
+ inEscape = 1;
+ else if (inEscape)
+ {
+ if ((*str >= 'A' && *str <= 'Z') || (*str >= 'a' && *str <= 'z'))
+ inEscape = 0;
+ }
+ else
+ len++;
+ str++;
+ }
+ return len;
+}
+
+void UpdatePrompt(ShellState *state)
+{
+ char hostname[256] = {0};
+ gethostname(hostname, sizeof(hostname));
+
+ struct passwd *pw = getpwuid(getuid());
+ char *username = pw ? pw->pw_name : "user";
+
+ char cwd[PATH_MAX];
+ if (!getcwd(cwd, sizeof(cwd)))
+ strcpy(cwd, "~");
+
+ if (pw && pw->pw_dir && strncmp(cwd, pw->pw_dir, strlen(pw->pw_dir)) == 0)
+ {
+ size_t home_len = strlen(pw->pw_dir);
+ if (strlen(cwd) == home_len)
+ strcpy(cwd, "~");
+ else if (cwd[home_len] == '/')
+ {
+ memmove(cwd + 1, cwd + home_len, strlen(cwd) - home_len + 1);
+ cwd[0] = '~';
+ }
}
- DisableRawMode(state);
- AddHistory(state, state->line);
+ char *customPrompt = getenv("SHELL_PROMPT");
+ if (customPrompt)
+ {
+ snprintf(state->prompt, sizeof(state->prompt), "%s", customPrompt);
+ }
+ else
+ {
+ // snprintf(state->prompt, sizeof(state->prompt),
+ // "\x1b[;32m┌──(%s@%s)-[\x1b[0;1m%s\x1b[;32m]\n\r└─\x1b[;32m$\x1b[00m ",
+ // username, hostname, cwd);
+
+ // snprintf(state->prompt, sizeof(state->prompt),
+ // "\x1b[;32m%s@%s\x1b[0;1m:\x1b[01;34m%s\x1b[0;1m$\x1b[00m ",
+ // username, hostname, cwd);
+
+ if (progIsFsh)
+ {
+ snprintf(state->prompt, sizeof(state->prompt),
+ "\x1b[1;34m%s\x1b[0;1m:\x1b[01;35m%s\x1b[0;1m$\x1b[00m ",
+ username, cwd);
+ }
+ else
+ {
+ snprintf(state->prompt, sizeof(state->prompt),
+ "$ ");
+ }
+ }
+
+ state->promptLength = GetVisibleLength(state->prompt);
}
void ExecuteCommand(char **args)
{
- if (args[0] == NULL)
+ if (!args[0])
return;
+ for (int i = 0; args[i] != NULL; i++)
+ {
+ char *arg = args[i];
+ size_t len = strlen(arg);
+ if (len >= 2 && arg[0] == '"' && arg[len - 1] == '"')
+ {
+ arg[len - 1] = '\0';
+ memmove(arg, arg + 1, len - 1);
+ }
+ }
+
if (strcmp(args[0], "exit") == 0)
exit(0);
- if (strcmp(args[0], "cd") == 0)
+ else if (strcmp(args[0], "cd") == 0)
{
- if (args[1] == NULL)
- fprintf(stderr, "cd: missing argument\n");
- else if (chdir(args[1]))
+ char *targetDirectory = args[1];
+ if (!targetDirectory || strcmp(targetDirectory, "~") == 0)
+ {
+ struct passwd *pw = getpwuid(getuid());
+ if (pw && pw->pw_dir)
+ targetDirectory = pw->pw_dir;
+ else
+ {
+ fprintf(stderr, "cd: HOME not set and no password directory available\n");
+ return;
+ }
+ }
+ else if (targetDirectory[0] == '~')
+ {
+ struct passwd *pw = getpwuid(getuid());
+ if (pw && pw->pw_dir)
+ {
+ char newPath[PATH_MAX];
+ snprintf(newPath, sizeof(newPath), "%s%s", pw->pw_dir, targetDirectory + 1);
+ targetDirectory = newPath;
+ }
+ }
+
+ if (chdir(targetDirectory))
perror("cd");
+ else
+ UpdatePrompt(&GlobalShellState);
return;
}
pid_t pid = fork();
if (pid == 0)
{
+ struct termios term;
+ tcgetattr(STDIN_FILENO, &term);
+ term.c_lflag |= (ECHO | ICANON | IEXTEN | ISIG);
+ term.c_oflag |= (OPOST | ONLCR);
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &term);
+ write(STDOUT_FILENO, "\r", 1);
+
execvp(args[0], args);
- perror("execvp");
+ if (errno == ENOENT)
+ {
+ write(STDERR_FILENO, "\r", 1);
+ fprintf(stderr, "%s: command not found\n", args[0]);
+ }
+ else
+ {
+ write(STDERR_FILENO, "\r", 1);
+ perror(args[0]);
+ }
exit(EXIT_FAILURE);
}
else if (pid > 0)
- wait(NULL);
+ {
+ int status;
+ waitpid(pid, &status, 0);
+ write(STDOUT_FILENO, "\r\n", 2);
+ }
else
perror("fork");
}
@@ -273,36 +738,113 @@ void ShellLoop(ShellState *state)
while (1)
{
ReadLine(state);
- if (state->len == 0)
+ if (state->length == 0)
continue;
+ char *trimmed = state->line;
+ while (*trimmed && isspace(*trimmed))
+ trimmed++;
+
+ char *end = trimmed + strlen(trimmed) - 1;
+ while (end > trimmed && isspace(*end))
+ *end-- = '\0';
+
+ if (*trimmed == '\0')
+ continue;
+
+ memmove(state->line, trimmed, strlen(trimmed) + 1);
+ state->length = strlen(state->line);
+
char *args[MAX_LINE_LEN / 2 + 1];
- char *token = strtok(state->line, " ");
int i = 0;
- while (token != NULL)
+ char *p = state->line;
+ int inQuotes = 0;
+ char *start = p;
+
+ while (*p)
{
- args[i++] = token;
- token = strtok(NULL, " ");
+ if (*p == '"')
+ inQuotes = !inQuotes;
+ else if ((*p == ' ' || *p == '\t') && !inQuotes)
+ {
+ if (p > start)
+ {
+ *p = '\0';
+ args[i++] = start;
+ }
+ start = p + 1;
+ }
+ p++;
}
+
+ if (p > start)
+ args[i++] = start;
args[i] = NULL;
- ExecuteCommand(args);
+ if (i > 0)
+ ExecuteCommand(args);
}
-
- SaveHistory(state);
}
void InitializeShell(ShellState *state)
{
memset(state, 0, sizeof(ShellState));
+ UpdatePrompt(state);
LoadHistory(state);
signal(SIGINT, HandleSignalInterrupt);
+
+ printf("\x1b[01;35mFennix Shell v%s\n\r", PROGRAM_VERSION);
+ printf("\x1b[;31mEarly development version!\x1b[0m\n\r");
+}
+
+void DisableRawModeAtExit(void)
+{
+ DisableRawMode(&GlobalShellState);
+}
+
+void PrintHelp()
+{
+ printf("Usage: sh [OPTION]... [SCRIPT]\n");
+ printf("A simple shell implementation.\n\n");
+ printf("Options:\n");
+ printf(" -c COMMAND execute COMMAND and exit\n");
+ printf(" --help display this help and exit\n");
+ printf(" --version output version information and exit\n\n");
+ printf("If SCRIPT is provided, execute commands from the script file.\n");
+ printf("Otherwise, run in interactive mode.\n\n");
+ printf("Environment variables:\n");
+ printf(" SHELL_PROMPT custom prompt format (default: user@host:path$ )\n");
}
int main(int argc, char *argv[])
{
- ShellState state = {0};
- InitializeShell(&state);
+ if (argc > 1)
+ {
+ if (strcmp(argv[1], "--help") == 0)
+ {
+ PrintHelp();
+ exit(EXIT_SUCCESS);
+ }
+ else if (strcmp(argv[1], "--version") == 0)
+ {
+ PRINTF_VERSION;
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ char *basename = strrchr(argv[0], '/');
+ if (basename == NULL)
+ basename = argv[0];
+ else
+ basename++;
+ if (strcmp(basename, "fsh") == 0)
+ progIsFsh = 1;
+
+ memset(&GlobalShellState, 0, sizeof(ShellState));
+ InitializeShell(&GlobalShellState);
+
+ signal(SIGINT, HandleSignalInterrupt);
+ atexit(DisableRawModeAtExit);
if (argc > 1)
{
@@ -314,8 +856,9 @@ int main(int argc, char *argv[])
if (!fp)
{
perror("fopen");
- exit(EXIT_FAILURE);
+ CleanupAndExit(EXIT_FAILURE);
}
+
char line[MAX_LINE_LEN];
while (fgets(line, sizeof(line), fp))
{
@@ -327,7 +870,8 @@ int main(int argc, char *argv[])
}
}
else
- ShellLoop(&state);
+ ShellLoop(&GlobalShellState);
+ CleanupAndExit(EXIT_SUCCESS);
return EXIT_SUCCESS;
}