Write a shell with history capabilities
This commit is contained in:
2
process_chp3/osh_shell/.gitignore
vendored
Normal file
2
process_chp3/osh_shell/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
osh_history
|
||||||
|
osh
|
||||||
18
process_chp3/osh_shell/Makefile
Normal file
18
process_chp3/osh_shell/Makefile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
CC=gcc
|
||||||
|
CFLAGS=-o osh -Wall
|
||||||
|
SRCS=history.c osh.c
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := build
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(CC) $(CFLAGS) -fsanitize=address -static-libasan $(SRCS)
|
||||||
|
|
||||||
|
release:
|
||||||
|
$(CC) $(CFLAGS) $(SRCS)
|
||||||
|
|
||||||
|
debug:
|
||||||
|
$(CC) $(CFLAGS) -fsanitize=address -static-libasan -ggdb $(SRCS)
|
||||||
|
gdb -q osh
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f osh
|
||||||
109
process_chp3/osh_shell/history.c
Normal file
109
process_chp3/osh_shell/history.c
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
#include "history.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define HISTORY_FILENAME "osh_history"
|
||||||
|
#define W_MODE "w"
|
||||||
|
#define R_MODE "r"
|
||||||
|
#define A_MODE "a+"
|
||||||
|
|
||||||
|
int allocate_history(void) {
|
||||||
|
FILE *file;
|
||||||
|
if (access(HISTORY_FILENAME, F_OK) == -1) {
|
||||||
|
if ((file = fopen(HISTORY_FILENAME, W_MODE)) == NULL) {
|
||||||
|
perror("Error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((file = fopen(HISTORY_FILENAME, R_MODE)) == NULL) {
|
||||||
|
perror("Error");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = -1;
|
||||||
|
char line[MAXLEN];
|
||||||
|
while (!feof(file)) {
|
||||||
|
fgets(line, sizeof(line), file);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
history_entry_t *get_history(int history_count) {
|
||||||
|
FILE *file = fopen(HISTORY_FILENAME, R_MODE);
|
||||||
|
if (!file) {
|
||||||
|
perror("Error");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int num = 0;
|
||||||
|
char line[MAXLEN];
|
||||||
|
const int offset = history_count - HISTORY_SIZE;
|
||||||
|
history_entry_t *history = calloc(HISTORY_SIZE, sizeof(history_entry_t));
|
||||||
|
while (fgets(line, sizeof(line), file)) {
|
||||||
|
if (num >= offset) {
|
||||||
|
history_entry_t entry;
|
||||||
|
entry.line_number = num + 1;
|
||||||
|
strcpy(entry.command, line);
|
||||||
|
history[num - offset] = entry;
|
||||||
|
}
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
return history;
|
||||||
|
}
|
||||||
|
|
||||||
|
int add_history(int count, char *cmd) {
|
||||||
|
FILE *file = fopen(HISTORY_FILENAME, A_MODE);
|
||||||
|
if (!file) {
|
||||||
|
perror("Error");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(file, "%s\n", cmd);
|
||||||
|
fclose(file);
|
||||||
|
return count + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int last_command(char *last) {
|
||||||
|
FILE *file = fopen(HISTORY_FILENAME, A_MODE);
|
||||||
|
if (!file) {
|
||||||
|
perror("Error");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[MAXLEN];
|
||||||
|
while (!feof(file)) {
|
||||||
|
fgets(line, sizeof(line), file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
strcpy(last, line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nth_command(char *s, int n) {
|
||||||
|
FILE *file = fopen(HISTORY_FILENAME, R_MODE);
|
||||||
|
if (!file) {
|
||||||
|
perror("Error");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int num = 0;
|
||||||
|
char line[MAXLEN];
|
||||||
|
while (fgets(line, sizeof(line), file)) {
|
||||||
|
if ((num + 1) == n) {
|
||||||
|
strcpy(s, line);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
fclose(file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
29
process_chp3/osh_shell/history.h
Normal file
29
process_chp3/osh_shell/history.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef __HISTORY_H__
|
||||||
|
#define __HISTORY_H__
|
||||||
|
|
||||||
|
#define MAXLEN 80
|
||||||
|
#define HISTORY_SIZE 10
|
||||||
|
|
||||||
|
typedef struct __HISTORY_ENTRY__ {
|
||||||
|
int line_number;
|
||||||
|
char command[MAXLEN];
|
||||||
|
} history_entry_t;
|
||||||
|
|
||||||
|
// Create new history file if not exists,
|
||||||
|
// otherwise read last command count and return it
|
||||||
|
int allocate_history(void);
|
||||||
|
|
||||||
|
// Return last ten command in the history file
|
||||||
|
history_entry_t *get_history(int);
|
||||||
|
|
||||||
|
// Write a record in the history file
|
||||||
|
int add_history(int, char *);
|
||||||
|
|
||||||
|
// Read last command in the history file
|
||||||
|
int last_command(char *);
|
||||||
|
|
||||||
|
// Search nth command in the history file,
|
||||||
|
// if not found nth command return -1.
|
||||||
|
int nth_command(char *, int);
|
||||||
|
|
||||||
|
#endif // __HISTORY_H__
|
||||||
147
process_chp3/osh_shell/osh.c
Normal file
147
process_chp3/osh_shell/osh.c
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#include "history.h"
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
struct command_t {
|
||||||
|
char **args;
|
||||||
|
bool bg_flag;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct command_t parse_command(char *);
|
||||||
|
int digits(char *);
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
|
||||||
|
// Create and/or read history file
|
||||||
|
int history_count = allocate_history();
|
||||||
|
if (history_count < 0) {
|
||||||
|
perror("Error");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int should_run = 1;
|
||||||
|
while (should_run) {
|
||||||
|
char raw[MAXLEN / 2 + 1];
|
||||||
|
printf("osh> ");
|
||||||
|
fflush(stdout);
|
||||||
|
fgets(raw, MAXLEN / 2 + 1, stdin);
|
||||||
|
|
||||||
|
// check if user press newline
|
||||||
|
if (strcmp(raw, "\n") == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: handle ^D shortcut
|
||||||
|
if (strcmp(raw, "\000") == 0) {
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
char *c;
|
||||||
|
if (strcmp(raw, "!!\n") == 0) {
|
||||||
|
if (nth_command(raw, history_count) != -1) {
|
||||||
|
printf("%s\n", raw);
|
||||||
|
} else {
|
||||||
|
printf("History is empty\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if ((c = strchr(raw, '!')) != NULL) {
|
||||||
|
const int n = digits(c);
|
||||||
|
if (nth_command(raw, n) != -1) {
|
||||||
|
printf("%s\n", raw);
|
||||||
|
} else {
|
||||||
|
printf("Command not found in position %d\n", n);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove carriage return
|
||||||
|
raw[strlen(raw) - 1] = '\0';
|
||||||
|
|
||||||
|
// save command in the history file
|
||||||
|
history_count = add_history(history_count, raw);
|
||||||
|
|
||||||
|
struct command_t cmd = parse_command(raw);
|
||||||
|
if (strcmp(cmd.args[0], "exit") == 0) {
|
||||||
|
should_run = 0;
|
||||||
|
} else if (strcmp(cmd.args[0], "history") == 0) {
|
||||||
|
// print on the stdout last 10 commands in reverse way
|
||||||
|
history_entry_t *history = get_history(history_count);
|
||||||
|
for (int i = HISTORY_SIZE - 1; i >= 0; i--) {
|
||||||
|
history_entry_t entry = history[i];
|
||||||
|
if (entry.line_number != 0) {
|
||||||
|
printf("%5d\t%s", entry.line_number, entry.command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(history);
|
||||||
|
history = NULL;
|
||||||
|
} else {
|
||||||
|
pid_t pid = fork();
|
||||||
|
switch (pid) {
|
||||||
|
case -1:
|
||||||
|
perror("Fork\n");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
case 0:
|
||||||
|
should_run = 0;
|
||||||
|
if (execvp(cmd.args[0], cmd.args)) {
|
||||||
|
perror("Error");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!cmd.bg_flag) {
|
||||||
|
wait(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cmd.args);
|
||||||
|
cmd.args = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct command_t parse_command(char *raw) {
|
||||||
|
int i = 0;
|
||||||
|
bool bg_flag = false;
|
||||||
|
char **args = (char **)calloc(MAXLEN, sizeof(char *));
|
||||||
|
char *tok = strtok(raw, " ");
|
||||||
|
while (tok != NULL) {
|
||||||
|
if (strcmp(tok, "&") == 0) {
|
||||||
|
bg_flag = true;
|
||||||
|
} else {
|
||||||
|
args[i] = tok;
|
||||||
|
}
|
||||||
|
tok = strtok(NULL, " ");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct command_t cmd = {.args = args, .bg_flag = bg_flag};
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
int digits(char *s) {
|
||||||
|
char *sub = malloc(strlen(s) * sizeof(s));
|
||||||
|
char c;
|
||||||
|
int i = 0, j = 0;
|
||||||
|
while ((c = s[i]) != '\n') {
|
||||||
|
if (isdigit(c)) {
|
||||||
|
sub[j++] = c;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int n = atoi(sub);
|
||||||
|
|
||||||
|
free(sub);
|
||||||
|
sub = NULL;
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user