Write a Shell in C
今天在网上看到这篇Write a Shell in C。
先看代码
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
void lsh_loop();
char* lsh_read_line();
char** lsh_split_line(char*);
int lsh_execute(char**);
int main(int argc, char **argv)
{
// Load config files, if any.
// Run command loop.
lsh_loop();
// Perform any shutdown/cleanup.
return EXIT_SUCCESS;
}
void lsh_loop()
{
char *line;
char **args;
int status;
do {
printf("> ");
line = lsh_read_line();
args = lsh_split_line(line);
status = lsh_execute(args);
free(line);
free(args);
} while (status);
}
char *lsh_read_line(void)
{
char *line = NULL;
size_t bufsize = 0; // have getline allocate a buffer for us
if (getline(&line, &bufsize, stdin) == -1){
if (feof(stdin)) {
exit(EXIT_SUCCESS); // We recieved an EOF
} else {
perror("readline");
exit(EXIT_FAILURE);
}
}
return line;
}
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line)
{
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
//分解字符串
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}
int lsh_launch(char **args)
{
pid_t pid, wpid;
int status;
pid = fork();
//一旦fork()返回,我们实际上有两个进程同时运行。子进程将采用第一个 if 条件 (where pid == 0)。
if (pid == 0) {
// Child process
//int execvp(const char *file, char *const argv[]); file文件路径,argv用于接收命令行参数,执行一个新程序当前程序不执行
//-1说明执行失败
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("lsh");
} else {
// Parent process
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
int lsh_cd(char **args);
int lsh_help(char **args);
int lsh_exit(char **args);
char *builtin_str[] = {"cd", "help", "exit"};
int (*builtin_func[]) (char **) = {&lsh_cd, &lsh_help,&lsh_exit};
int lsh_num_builtins() {
return sizeof(builtin_str) / sizeof(char *);
}
int lsh_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "lsh: expected argument to \"cd\"\n");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
int lsh_help(char **args)
{
int i;
printf("Stephen Brennan's LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
int lsh_exit(char **args)
{
return 0;
}
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}
cd
int lsh_launch(char **args)
{
pid_t pid, wpid;
int status;
pid = fork();
//一旦fork()返回,我们实际上有两个进程同时运行。子进程将采用第一个 if 条件 (where pid == 0)。
if (pid == 0) {
// Child process
//int execvp(const char *file, char *const argv[]); file文件路径,argv用于接收命令行参数,执行一个新程序当前程序不执行
//-1说明执行失败
if (execvp(args[0], args) == -1) {
perror("lsh");
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
// Error forking
perror("lsh");
} else {
// Parent process
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
pid_t pid, wpid; int status;: 声明变量以存储进程ID和状态信息。
pid = fork();: 通过复制现有进程创建一个新进程。父进程获得子进程的ID,而子进程获得0。
if (pid == 0) { … }: 这段代码块由子进程执行。它使用 execvp 用一个新程序替换子进程的内存空间。如果 execvp 失败,将打印错误并以失败状态退出子进程。
else if (pid < 0) { … }: 如果 fork 失败(返回负值),将打印错误。
else { … }: 这个块由父进程执行。它使用 waitpid 等待子进程完成,直到子进程退出或被信号终止。子进程的状态存储在 status 变量中。
总的来说,这个函数使用 fork 创建一个新进程,子进程使用 execvp 执行指定的命令。父进程等待子进程完成,然后返回1。
当我们在写好的shell中输入一段可执行程序的地址,它将调用这个函数,然后在执行程序。
int lsh_cd(char **args)
{
if (args[1] == NULL) {
fprintf(stderr, "lsh: expected argument to \"cd\"\n");
} else {
if (chdir(args[1]) != 0) {
perror("lsh");
}
}
return 1;
}
if (args[1] == NULL) { … }: 检查是否提供了cd命令的参数。如果没有提供参数,打印错误消息到标准错误输出(stderr),指示用户期望得到一个参数。
else { … }: 如果提供了参数,则执行这个块的代码。
chdir(args[1]): 使用chdir函数改变当前工作目录为参数args[1]指定的目录。如果成功,chdir返回0;如果失败,返回非零值。
if (chdir(args[1]) != 0) { … }: 检查chdir的返回值,如果不是0,表示发生错误。在这种情况下,使用perror函数打印一个描述错误的消息到标准错误输出。
help
char *builtin_str[] = {"cd", "help", "exit"};
int lsh_num_builtins() {
return sizeof(builtin_str) / sizeof(char *);
}
int lsh_help(char **args)
{
int i;
printf("Stephen Brennan's LSH\n");
printf("Type program names and arguments, and hit enter.\n");
printf("The following are built in:\n");
for (i = 0; i < lsh_num_builtins(); i++) {
printf(" %s\n", builtin_str[i]);
}
printf("Use the man command for information on other programs.\n");
return 1;
}
当输入help之后将提示以上输出的内容
exit
int lsh_exit(char **args)
{
return 0;
}
当输入exit后将返回0,终止之前的循环,结束程序
char *lsh_read_line(void)
{
char *line = NULL;
size_t bufsize = 0; // have getline allocate a buffer for us
if (getline(&line, &bufsize, stdin) == -1){
if (feof(stdin)) {
exit(EXIT_SUCCESS); // We recieved an EOF
} else {
perror("readline");
exit(EXIT_FAILURE);
}
}
return line;
}
#define LSH_TOK_BUFSIZE 64
#define LSH_TOK_DELIM " \t\r\n\a"
char **lsh_split_line(char *line)
{
int bufsize = LSH_TOK_BUFSIZE, position = 0;
char **tokens = malloc(bufsize * sizeof(char*));
char *token;
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
//分解字符串
token = strtok(line, LSH_TOK_DELIM);
while (token != NULL) {
tokens[position] = token;
position++;
if (position >= bufsize) {
bufsize += LSH_TOK_BUFSIZE;
tokens = realloc(tokens, bufsize * sizeof(char*));
if (!tokens) {
fprintf(stderr, "lsh: allocation error\n");
exit(EXIT_FAILURE);
}
}
token = strtok(NULL, LSH_TOK_DELIM);
}
tokens[position] = NULL;
return tokens;
}
这两个函数分别实现读取一整行和将字符串拆分,具体细节原作者提供了注释,我也加上了我自己的注释。
最后lsh_execute根据输入的内容判断调用哪个函数
int lsh_execute(char **args)
{
int i;
if (args[0] == NULL) {
// An empty command was entered.
return 1;
}
for (i = 0; i < lsh_num_builtins(); i++) {
if (strcmp(args[0], builtin_str[i]) == 0) {
return (*builtin_func[i])(args);
}
}
return lsh_launch(args);
}