大约一个学期前,我用C语言写了这个壳,用于一项关于操作系统的大学作业。虽然我在这次任务中得了10分,但我怀疑这是值得的-
我们不允许将源代码分割成不同的文件。
这是它的源代码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
struct subcommand_t {
char **argument; // Array of arguments
size_t size; // Number of arguments
};
struct command_t {
struct subcommand_t *subcommand; // Array of subcommands.
size_t size; // Number of subcommands.
};
struct line_t {
struct command_t *command; // Array of commands.
size_t size; // Number of commands.
};
void prompt(char *prompt) {
#ifdef DEBUG
fprintf(stderr, "[PROMPT] Displaying prompt\n");
#endif
fprintf(stdout, "%s ", prompt);
}
char *reader(size_t size){
char *buffer;
#ifdef DEBUG
fprintf(stderr, "[READER] Allocating memory for buffer [size: %zu]\n", size);
#endif
buffer = malloc(sizeof(char) * size);
if (buffer == NULL) exit(EXIT_FAILURE);
#ifdef DEBUG
fprintf(stderr, "[READER] Reading to buffer\n");
#endif
int character;
size_t length = 0;
while (EOF != (character = fgetc(stdin)) && character != '\n') {
buffer[length++] = (char) character;
if (length == size) {
#ifdef DEBUG
fprintf(stderr, "[READER] Reallocating memory for buffer [size: %zu]\n", size);
#endif
buffer = realloc(buffer, sizeof(char) * (size += 32));
if (buffer == NULL) exit(EXIT_FAILURE);
}
}
#ifdef DEBUG
fprintf(stderr, "[READER] Setting the NULL terminator for buffer\n");
#endif
buffer[length++] = '\0';
return realloc(buffer, sizeof(char) * length);
}
void executor(struct line_t line) {
for (size_t i = 0; i < line.size; ++i) {
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Executing line.command[%zu]\n", i);
#endif
int previous;
for (size_t j = 0; j < line.command[i].size; ++j) {
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Executing line.command[%zu].subcommand[%zu]\n", i, j);
#endif
int p[2];
pipe(p);
if (line.command[i].subcommand[j].size != 0) {
if (strcmp(line.command[i].subcommand[j].argument[0], "exit") == 0) {
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Exiting shell\n");
#endif
exit(EXIT_SUCCESS);
} else if (strcmp(line.command[i].subcommand[j].argument[0], "cd") == 0) {
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Changing directory to %s\n", line.command[i].subcommand[j].argument[1]);
#endif
chdir(line.command[i].subcommand[j].argument[1]);
} else {
switch(fork()) {
case -1:
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Failed to fork a child process\n");
#endif
exit(EXIT_FAILURE);
case 0:
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Running on a child process [pid: %d]\n", getpid());
#endif
if (j == 0) {
dup2(0, STDIN_FILENO);
} else {
dup2(previous, STDIN_FILENO);
}
close(p[0]);
if (j+1 < line.command[i].size) {
dup2(p[1],STDOUT_FILENO);
}
close(p[0]);
execvp(line.command[i].subcommand[j].argument[0], line.command[i].subcommand[j].argument);
exit(EXIT_FAILURE);
default:
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Running on the parent process [%d]\n", getpid());
#endif
previous=p[0];
close(p[1]);
}
}
}
}
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Waiting for any terminated child processes\n");
#endif
while(wait(NULL) > 0) {
#ifdef DEBUG
fprintf(stderr, "[EXECUTOR] Found a terminated child process\n");
#endif
}
}
}
struct line_t parser(char *buffer, char *del1, char *del2, char *del3) {
size_t i, j, k;
char *str1, *str2, *str3;
char *token1, *token2, *token3;
char *saveptr1, *saveptr2, *saveptr3;
struct line_t line;
line.size=0;
line.command=NULL;
for (i = 0, str1 = buffer ;; i++, str1 = NULL) {
token1 = strtok_r(str1, del1, &saveptr1);
if (token1 == NULL) break;
line.size++;
if (i == 0) {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Allocating memory for line.command [size: %zu]\n", line.size);
#endif
line.command = malloc(sizeof(struct command_t));
} else {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Reallocating memory for line.command [size: %zu]\n", line.size);
#endif
line.command = realloc(line.command, line.size * sizeof(struct command_t));
}
line.command[i].size=0;
line.command[i].subcommand=NULL;
for (j = 0, str2 = token1 ;; j++, str2 = NULL) {
token2 = strtok_r(str2, del2, &saveptr2);
if (token2 == NULL) break;
line.command[i].size++;
if (j == 0) {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Allocating memory for line.command[%zu].subcommand [size: %zu]\n", i, line.command[i].size);
#endif
line.command[i].subcommand = malloc(sizeof(struct subcommand_t));
} else {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Reallocating memory for line.command[%zu].subcommand [size: %zu]\n", i, line.command[i].size);
#endif
line.command[i].subcommand = realloc(line.command[i].subcommand, line.command[i].size * sizeof(struct subcommand_t));
}
line.command[i].subcommand[j].size=0;
line.command[i].subcommand[j].argument=NULL;
for (k = 0, str3 = token2 ;; k++, str3 = NULL) {
token3 = strtok_r(str3, del3, &saveptr3);
if (token3 == NULL) break;
line.command[i].subcommand[j].size++;
if (k == 0) {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Allocating memory for line.command[%zu].subcommand[%zu].argument [size: %zu]\n", i, j, line.command[i].subcommand[j].size);
#endif
line.command[i].subcommand[j].argument = malloc(sizeof(char *));
} else {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Reallocating memory for line.command[%zu].subcommand[%zu].argument [size: %zu]\n", i, j, line.command[i].subcommand[j].size);
#endif
line.command[i].subcommand[j].argument = realloc(line.command[i].subcommand[j].argument, (line.command[i].subcommand[j].size + 1) * sizeof(char *));
}
line.command[i].subcommand[j].argument[k] = malloc((strlen(token3)+1) * sizeof(char));
memset(line.command[i].subcommand[j].argument[k], 0, strlen(token3)+1);
strcpy(line.command[i].subcommand[j].argument[k], token3);
}
if (line.command[i].subcommand[j].size != 0) {
#ifdef DEBUG
fprintf(stderr, "[PARSER] Setting the NULL terminator for line.command[%zu].subcommand[%zu]\n", i, j);
#endif
line.command[i].subcommand[j].argument[line.command[i].subcommand[j].size] = NULL;
}
}
}
return line;
}
int main() {
while (1) {
prompt("$");
char *buffer = reader(1024);
struct line_t line = parser(buffer, ";", "|", " \t");
executor(line);
}
}发布于 2018-04-13 05:00:09
你写了一个漂亮而简单的外壳。它适用于非常简单的命令,但对于更复杂的命令则失败(详见下文)。代码可以在几个地方清除。
要使您的代码被其他人很好地读懂,让自动格式化程序来处理缩进和间距。如果有可用的GNU缩进,请使用以下命令行:
indent --k-and-r-style --no-tabs --line-length 200 --case-indentation 4 --braces-on-func-def-line shell.c而不是每次都写这个:
#ifdef DEBUG
fprintf(stderr, "[READER] Allocating memory for buffer [size: %zu]\n", size);
#endif您应该定义一个宏DEBUG_PRINTF:
#ifdef DEBUG
#define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__)
#else
#define DEBUG_PRINTF(...) (void)0
#endif然后你就可以写:
DEBUG_PRINTF("[READER] Allocating memory for buffer [size: %zu]\n", size);if (buffer == NULL) exit(EXIT_FAILURE);在退出错误代码之前,应打印一条错误消息。按照惯例,空输出意味着成功。
启用所有编译器警告并正确修复它们。对于GCC来说,这是-Wall -Wextra -Werror -O2。
void prompt,而是编写static void prompt。这使得该函数成为当前文件的本地函数,并避免了冲突,以防另一个文件定义同名的另一个函数。(也适用于其他职能)int main(),编写int main(void)来修复“丢失的原型”警告。const char *而不是char *。这会影响prompt和parser的参数。重命名你的职能。函数名通常是动词。您当前的名称是executor、parser等。这些名称是可读的和可以理解的,但仍然应该是execute和parse。
对于reader来说,这更困难,因为有一个叫做read的系统提供的函数。因此,应该将其重命名为read_line。
在需要变量的地方直接声明变量。而不是:
char *buffer;
buffer = malloc(sizeof(char) * size);只需写:
char *buffer = malloc(sizeof(char) * size);由于sizeof(char)被定义为始终为1,所以不要使用它:
char *buffer = malloc(size);在executor函数中,不要到处重复line.command[i].subcommand[j],而是定义一个新变量:
for (size_t j = 0; j < line.command[i].size; ++j) {
subcommand_t subcommand = line.command[i].subcommand[j];
if (subcommand.size != 0) {
// ...parser函数当前意外地解析了以下命令:
echo ";"它输出",但没有给出为什么分号和第二个引号没有回显的任何提示。
memset(line.command[i].subcommand[j].argument[k], 0, strlen(token3)+1);
strcpy(line.command[i].subcommand[j].argument[k], token3);memset是多余的,应该删除。
与malloc或realloc一起分配的任何内存必须在与free一起使用后释放。
当我在您的shell中按下Ctrl+D时,我会陷入无穷无尽的循环中。所有其他shell都会在这一点上退出(或者指示我必须输入exit,而不是只输入Ctrl+D,这真的很烦人)。
要处理这种情况,请在reader函数中添加错误处理,如果返回值为NULL,则从main返回。
发布于 2018-04-13 13:35:43
_t您可能需要重新考虑在类型名称中使用_t结尾。简而言之,大多数标准类型名称都使用这种方法,一般的做法是不使用用户定义的类型。
引用POSIX:
为了允许实现者提供自己的类型,所有符合标准的应用程序都需要避免以"_t“结尾的符号,这允许实现者提供其他类型。
作为一个基本例子:
typedef struct Subcommand {
char **argument; // Array of arguments
size_t size; // Number of arguments
} Subcommand;https://codereview.stackexchange.com/questions/191914
复制相似问题