Если вы напишите несколько вспомогательных функций и придумаете подходящую структуру данных, она станет тривиальной. Например:
main.c
:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stringlist.h"
#include "io.h"
int main(void)
{
StringList list = NULL;
get_input(&list);
while (strcmp(stringlist_string_at_index(list, 0), "exit")) {
pid_t p = fork();
if (p < 0) {
perror("fork() error");
exit(EXIT_FAILURE);
}
else if (p == 0) {
char ** args = stringlist_raw_list(list);
execvp(args[0], args);
switch (errno) {
case EACCES:
printf("Error: access denied.\n");
break;
case ENOENT:
printf("Error: file not found.\n");
break;
default:
printf("Error: couldn't fulfill request.\n");
break;
}
exit(EXIT_FAILURE);
}
else {
int status;
waitpid(p, &status, 0);
}
get_input(&list);
}
stringlist_destroy(list);
return EXIT_SUCCESS;
}
с вспомогательными файлами:
io.h
:
#ifndef IO_H
#define IO_H
#include "stringlist.h"
void get_input(StringList * list);
#endif /* IO_H */
io.c
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "io.h"
#include "stringlist.h"
#define MAX_INPUT_LENGTH 256
static void get_input_line(char * buffer, const size_t buffer_size);
static void output_prompt(void);
static StringList tokenize_input(void);
/* Prompts for and gets input from standard input.
*
* If the StringList pointed to by `list` is not NULL, it will
* be destroyed. The StringList pointed to by `list` will be
* modified to point to a new StringList created from the input.
* If no input is entered, function will prompt for it again.
*
* Note: the input is tokenized purely by space characters, so input
* resembling:
*
* cat "Filename with spaces"
*
* will return four tokens, not two. This simple method of tokenizing
* will be unsuitable for many applications.
*/
void get_input(StringList * list)
{
if (*list) {
stringlist_destroy(*list);
}
do {
output_prompt();
*list = tokenize_input();
} while (stringlist_length(*list) == 0);
}
/* Gets a line of input from standard input.
*
* Function strips the trailing newline, if present, and
* exits the program on error.
*/
static void get_input_line(char * buffer, const size_t buffer_size)
{
if (!fgets(buffer, buffer_size, stdin)) {
fprintf(stderr, "error getting input\n");
exit(EXIT_FAILURE);
}
const size_t len = strlen(buffer);
if (len > 0 && buffer[len - 1] == '\n') {
buffer[len - 1] = 0;
}
}
/* Outputs the shell prompt */
static void output_prompt(void)
{
printf("shell$ ");
fflush(stdout);
}
/* Gets a line of input from standard input and tokenizes it */
static StringList tokenize_input(void)
{
StringList list = stringlist_create();
char input[MAX_INPUT_LENGTH];
get_input_line(input, MAX_INPUT_LENGTH);
char * t = strtok(input, " ");
while (t) {
stringlist_add(list, t);
t = strtok(NULL, " ");
}
return list;
}
stringlist.h
:
#ifndef STRING_LIST_H
#define STRING_LIST_H
#include <stddef.h>
#include <stdbool.h>
typedef struct stringlist * StringList;
StringList stringlist_create(void);
bool stringlist_delete_last(StringList list);
void stringlist_destroy(StringList list);
size_t stringlist_length(StringList list);
char * stringlist_string_at_index(StringList list, const size_t index);
char ** stringlist_raw_list(StringList list);
void stringlist_add(StringList list, const char * str);
#endif /* STRING_LIST_H */
stringlist.c
:
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "stringlist.h"
#define DEFAULT_LIST_SIZE 8
struct stringlist {
char ** list; /* Pointer to list of strings */
size_t size; /* Current capacity of list */
size_t top; /* Lowest empty element of list */
};
/* Creates a new list of default capacity */
StringList stringlist_create(void)
{
struct stringlist * new_list = malloc(sizeof *new_list);
if (!new_list) {
perror("memory allocation failed");
exit(EXIT_FAILURE);
}
new_list->size = DEFAULT_LIST_SIZE;
new_list->top = 0;
char ** list = calloc(new_list->size, sizeof *list);
if (!list) {
perror("memory allocation failed");
exit(EXIT_FAILURE);
}
new_list->list = list;
return new_list;
}
/* Deletes the last string in the list.
*
* Returns false if the list was empty, otherwise true.
*/
bool stringlist_delete_last(StringList list)
{
if (list->top) {
list->top -= 1;
free(list->list[list->top]);
list->list[list->top] = NULL;
return true;
}
return false;
}
/* Destroys the list and frees all resources */
void stringlist_destroy(StringList list)
{
while (stringlist_delete_last(list)) {
;
}
free(list->list);
free(list);
}
/* Returns the number of strings currently in the list */
size_t stringlist_length(StringList list)
{
return list->top;
}
/* Returns the string at the specified index of the list */
char * stringlist_string_at_index(StringList list, const size_t index)
{
return list->list[index];
}
/* Returns a pointer to the raw list of strings.
*
* This raw list will be NULL-terminated, that is, if the raw
* list contains `length` strings, then raw_list[length] == NULL.
* This makes the raw list suitable for passing, for instance, to
* execv() and friends.
*/
char ** stringlist_raw_list(StringList list)
{
return list->list;
}
/* Adds a string to the list.
*
* The raw list will be dynamically resized, if necessary.
*/
void stringlist_add(StringList list, const char * str)
{
if (list->top + 1 >= list->size) {
char ** new_array = realloc(list->list,
list->size * 2 * sizeof *new_array);
if (!new_array) {
perror("memory allocation failed");
exit(EXIT_FAILURE);
}
list->list = new_array;
list->size *= 2;
}
char * duped = strdup(str);
if (!duped) {
perror("memory allocation failed");
exit(EXIT_FAILURE);
}
list->list[list->top] = duped;
list->top += 1;
list->list[list->top] = NULL;
}
Это добавляет некоторые проверки ошибок для пустого ввода, и отвечает на вызовы не удалось execvp()
в более значимым образом.
Пример сессии:
[email protected]:~/Documents/src/sandbox/simple_shell$ ./ss
shell$
shell$
shell$ ./Fakefile
Error: file not found.
shell$ ./Makefile
Error: access denied.
shell$ ls -alF
total 96
drwxr-xr-x 12 Paul staff 408 Nov 12 21:18 ./
drwxr-xr-x 6 Paul staff 204 Nov 12 20:42 ../
-rw-r--r-- 1 Paul staff 368 Nov 12 21:07 Makefile
-rw-r--r-- 1 Paul staff 2016 Nov 12 21:18 io.c
-rw-r--r-- 1 Paul staff 113 Nov 12 21:10 io.h
-rw-r--r-- 1 Paul staff 2240 Nov 12 21:18 io.o
-rw-r--r-- 1 Paul staff 1214 Nov 12 21:08 main.c
-rw-r--r-- 1 Paul staff 1608 Nov 12 21:11 main.o
-rwxr-xr-x 1 Paul staff 10032 Nov 12 21:18 ss*
-rw-r--r-- 1 Paul staff 2799 Nov 12 20:52 stringlist.c
-rw-r--r-- 1 Paul staff 504 Nov 12 20:53 stringlist.h
-rw-r--r-- 1 Paul staff 2492 Nov 12 21:11 stringlist.o
shell$ ps
PID TTY TIME CMD
75221 ttys002 0:00.19 -bash
75678 ttys002 0:00.00 ./ss
shell$ echo Hello, world!
Hello, world!
shell$ cat "Tokenizing input with spaces is generally bad.txt"
cat: "Tokenizing: No such file or directory
cat: input: No such file or directory
cat: with: No such file or directory
cat: spaces: No such file or directory
cat: is: No such file or directory
cat: generally: No such file or directory
cat: bad.txt": No such file or directory
shell$ exit
[email protected]:~/Documents/src/sandbox/simple_shell$
Великий. У вас возникли вопросы? –
вы начинаете с 'while (strcmp (input,« exit »)), но ничего не вводится в' input'. После этого вы читаете 'input' и fork. Даже если пользователь вводит «выход» (скажем). Глобальная логика не «логична». – hexasoft
Всякий раз, когда я запускаю программу, я не могу увидеть результат моей команды ... также, если поместить там ложную команду, ее не показывать никаких ошибок. – purkavlos