写一个自己的Unix Shell(3)实现cd和exit命令

        在《写一个自己的Unix Shell(2)将读入的字符变成命令》这篇文章中,我们实现了将输入的字符转成命令的功能。例如当我们输入ls时,我们的Shell程序会开辟一个子进程,将ls字符串转成字符串数组,然后调用execvp()函数去环境变量PATH中的目录中查找ls对应的可执行文件并执行它。对于其他常见的命令也是这个过程,但有两个命令除外,那就是cd命令和exit命令。我们知道:cd命令的作用是切换目录,exit命令是退出Shell程序。

        对于cd命令来说,它切换的是Shell程序的工作目录,什么是工作目录?工作目录就是你现在在操作系统的哪个目录下,比如你在/root/目录下,那么/root/就是Shell进程的工作目录,你在/home/hello/目录下,/home/hello/就是Shell进程的工作目录,切换Shell进程的工作目录需要Shell程序的父进程来完成,因为如果是子进程切换工作目录切换的是子进程的,它不会影响到父进程的工作目录。当然,子进程可以继承父进程的工作目录,就是说假设父进程的工作目录是/root/,那么当父进程fork()出子进程后,子进程这时的工作目录也是/root/,但子进程之后再切换工作目录,这就与父进程无关了。

        对于exit命令来说,它是退出Shell进程,那么它必须由Shell程序的父进程执行,因为是父进程在不断的等待输入,解析,开辟子进程执行命令,所以我们要实现在父进程中检测用户输入的是否是exit命令,如果是,那么就退出Shell程序。

        综上,我们要在Shell程序的父进程中实现切换工作目录和退出Shell程序的功能。

        我们用chdir()系统调用来实现切换进程当前工作目录的功能。下面看看chdir()函数的声明。

1
int     chdir(const char *pathname);

        chdir()系统调用的函数声明非常简单,它接受一个绝对路径或相对路径来设置工作目录,成功返回0,失败返回-1。

        我们用handleChangeDirCommand()函数来实现解析cd命令并切换工作目录的功能,下面是handleChangeDirCommand()函数的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
des:
处理切换目录命令,如果cmdString是切换目录命令,就切换进程的工作目录
param:
cmdString[IN]: 命令字符串
return:
是切换目录命令,进行切换: 1
不是切换目录命令: 0
*/
int handleChangeDirCommand(char *cmdString) {
char **argv = parseCmdString(cmdString);
int i = 0;
int execChangDir = 0;
if(argv != NULL && argv[0] != NULL && strcmp(argv[0], "cd") == 0) {
// 处理cd命令
if(argv[1] == NULL) {
// 切换工作目录到家目录
char *homeDir = getenv("HOME");
if(homeDir == NULL) {
fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
} else if(chdir(homeDir) < 0) {
fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
}
} else if(argv[1] != NULL) {
if(chdir(argv[1]) < 0) {
fprintf(stderr, "%s: %s\n", argv[0], strerror(errno));
}
}
execChangDir = 1;
}
// 释放内存
if(argv) {
while(argv[i] != NULL) {
free(argv[i]);
i++;
}
free(argv);
}
return execChangDir;
}

        在handleChangeDirCommand()函数的实现中,我们复用了parseCmdString()函数将命令字符串转成字符串数组方便我们后面判断。然后我们检查是否输入了cd命令,如果输入了cd命令并且cd命令的后面没有目标路径的话,我们切换到当前用户的家目录(用getenv("HOME")来获取当前用户的家目录,getenv()用来获取指定的环境变量的值,HOME是一个环境变量,就像PATH一样,只不过HOME环境变量存放的是当前用户家目录的字符串)。如果输入了cd命令,并且cd命令的后面存在一个目标路径的话,那我们就会使用chdir()函数将当前工作目录切换到目标目录。如果没有输入cd命令,就返回0。

        我们用handleExitCommand()函数来实现对退出程序命令的处理,它的实现如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
des:
处理退出命令,如果是退出命令,则退出程序
param:
cmdString[IN]: 命令字符串
return:
是退出命令: 直接退出
不是退出命令: 0
*/
int handleExitCommand(char *cmdString) {
char **argv = parseCmdString(cmdString);
int i = 0;
int execExit = 0;
if(argv != NULL && argv[0] != NULL && strcmp(argv[0], "exit") == 0) {
execExit = 1;
}
// 释放内存
if(argv) {
while(argv[i] != NULL) {
free(argv[i]);
i++;
}
free(argv);
}
if(execExit) {
printf("Bey~\n");
exit(EXIT_SUCCESS);
}
return execExit;
}

        首先我们将cmdString转成字符串数组,然后判断它是不是exit命令,如果是直接退出,否则返回0。

        现在我们有了对切换工作目录功能和退出程序功能的处理,接下来我们将这两个功能集成到我们上一篇文章《写一个自己的Unix Shell(2)将读入的字符变成命令》的程序中。我们只需在execCommand()函数中调用handleChangeDirCommand()函数和handleExitCommand()函数即可。代码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
des:
执行命令
param:
cmdString[IN]: 命令字符串
*/
void execCommand(char *cmdString) {
pid_t pid = -1;
// 处理切换工作目录命令和退出命令
if(handleChangeDirCommand(cmdString) || handleExitCommand(cmdString)) {
// 如果切换工作目录命令或退出命令,不往下执行,直接返回
return;
}
pid = fork();
if(pid < 0) {
fprintf(stderr, "execCommand() fork() error\n");
} else if(pid == 0) {
// 子进程
// 获取argv数组
char *const *argv = NULL;
argv = parseCmdString(cmdString);
if(argv == NULL) {
fprintf(stderr, "execCommand() parseCmdString() error\n");
exit(EXIT_FAILURE);
}
// 执行execvp函数调用其他程序
if(execvp(argv[0], argv) < 0) {
perror("myshell");
}

} else {
// parent process
if(wait(NULL) < 0) {
fprintf(stderr, "execCommand() wait() error\n");
}
}
}

        如果是cd命令或exit命令我们就不再往下执行,直接返回(当然,如果是exit命令程序就直接结束了)。

        下图是程序执行的样子。

        现在我们可以切换当前工作目录和退出程序了。

附:

        欢迎大家关注我的微信公众号^_^。

dark

sans