源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
//bufeer: 缓冲区
char g_myval[64];
//保存完整的命令行字符串
char cmd_line[NUM] ;
//保存打散后的字符串
char *g_argv[SIZE];
//定义宏
#define INPUT_REDTR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0
int redir_status = NONE_REDIR;
char *CheckRedir(char *start)
{
assert(start);
char* end = start + strlen(start) - 1 ; //类似迭代器,但指向的是最后一个元素/字符
while(end >= start)
{
if(*end == '>')
{
if(*(end-1) == '>')
{
redir_status = APPEND_REDIR ;
*(end - 1) = '\0' ;
end++ ;
break ;
}
redir_status = OUTPUT_REDIR ;
*end = '\0';
end++ ;
break;
}
else if(*end == '<')
{
redir_status = INPUT_REDTR;
*end = '\0';
end++;
break;
}
else
{
end--;
}
}
if(end >= start) //保证 << 出现在规范位置
{
return end; //end 此时指向要打开的文件名称第一个字符的位置,也就代表了要打开的文件名
}
else
{
return NULL ;
}
}
//shell主体
int main()
{
//environ是一个全局变量,它是一个字符指针数组,用于存储当前进程的环境变量列表。
extern char** environ;
// shell执行命令后不退出
while(1)
{
//打印提示信息
printf("[root@localhost myshell]#");
fflush(stdout);
memset(cmd_line, '\0', sizeof cmd_line ); //初始化
//获取用户输入指令
//从stdin中读取 sizeof cmd_line 大小的内容进入cmd_line,遇到结尾停止读取
if(fgets(cmd_line, sizeof cmd_line , stdin) == NULL)
{
continue ;
}
cmd_line[strlen(cmd_line) - 1 ] = '\0';
// 分析是否有重定向
char* sep = CheckRedir(cmd_line);
//将读到的字符串,以空格为标记打散,方便指令的执行
g_argv[0] = strtok(cmd_line , SEP) ; // 第一次调用strtok
int index = 1;
while(g_argv[index++] = strtok(NULL,SEP)) ; //后续循环调用 // 如果没有更多可以分割则返回NULL结束循环
// 支持export 指令
if(strcmp (g_argv[0] , "export") == 0 && g_argv[1] != NULL)
{
strcpy(g_myval, g_argv[1]);
int ret = putenv(g_myval); //putenv是系统接口
if(ret == 0)
{
printf(" %s export success \n", g_argv[1]) ;
}
continue;
}
//内置命令
if(strcmp(g_argv[0], "cd") == 0)
{
if(g_argv[1] != NULL)
{
chdir(g_argv[1]); // 切换当前工作目录/即进程所在目录 的系统接口
}
continue ;
}
// fork 执行其他命令
pid_t id = fork();
//重定向指令
if(id == 0) // 子进程执行
{
if(sep != NULL) // 返回值为空说明发生了重定向
{
int fd = -1; // ???
switch(redir_status)
{
case INPUT_REDTR:
fd = open(sep, O_RDONLY );
dup2(fd, 0); //new位置只会是0/1因为只会是输入/输出重定向
break;
case OUTPUT_REDIR:
fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT , 0666);
dup2(fd, 1);
break;
case APPEND_REDIR:
fd = open(sep,O_WRONLY | O_APPEND | O_CREAT , 0666); //0666为文件默认权限 //O_CREAT 文件不存在则创建文件
dup2(fd, 1);
break;
default:
printf("错误\n");
break;
}
}
execvp(g_argv[0],g_argv); exit(1);
}
int status = 0 ;
pid_t ret = waitpid(id , &status,0);
if(ret > 0)
{
printf("exit code: %d\n" , WEXITSTATUS(status)); //如果子进程正常退出,返回子进程的退出状态码。
}
}
}
相关函数
Linux C标准库提供的environ
environ是一个全局变量,它是一个字符指针数组,用于存储当前进程的环境变量列表。
环境变量是一组以key=value形式存储的字符串,用于在操作系统中传递配置信息和参数。例如,PATH=/usr/bin:/usr/local/bin就是一个环境变量的示例,表示可执行文件的搜索路径。
environ变量是一个指向环境变量列表的指针数组,每个元素都是一个指向以key=value形式表示的环境变量字符串的指针。environ数组的最后一个元素为NULL,用于表示环境变量列表的结束。
通过遍历environ数组,可以访问和操作当前进程的所有环境变量。例如,可以使用printf()函数打印出所有的环境变量及其对应的值:
#include <stdio.h>
extern char **environ;
int main() {
char **env = environ;
while (*env != NULL) {
printf("%s\n", *env);
env++;
}
return 0;
}
在这个示例中,通过访问全局变量environ来获取当前进程的环境变量列表。然后使用一个循环遍历environ数组,并使用printf()函数打印出每个环境变量的字符串。
需要注意的是,environ变量是标准C库提供的全局变量,定义在<unistd.h>头文件中。在使用environ变量之前,需要包含该头文件。
memset函数
memset函数
内存设置函数 memset(地址,设置的必须为整形的内容x,改变的字节数n) 用整形内容x替换地址容器中前n个字节的内容
void *memset(void *s, int c, size_t n);
fgets函数
文本行输入函数
char * fgets ( char * str, int num, FILE * stream );
从文件指针开始向str字符串中拷贝num个字符,并返回这段字符。(如遇到文件尾则立刻停止) 成功时,该函数返回str,str为字符串首地址指针。
strtok分割函数
char *strtok(char *str, const char *delim)
str – 要被分解成一组小字符串的字符串。
delim – 包含分隔符的 C 字符串(分割标记,本质就是分割标记被替换为\0)。
调用方法:分多次使用
例如:
第一次调用strtok(),传入的参数str是要被分割的字符串{aaa - bbb -ccc},而成功后返回的是第一个子字符串{aaa};
而第二次调用strtok的时候,传入的参数应该为NULL(char *str位置的参数,后面的参数还是要输入),使得该函数默认使用上一次未分割完的字符串继续分割 ,就从上一次分割的位置{aaa-}作为本次分割的起始位置,直到分割结束。
注意:
1.strtok函数会修改原始字符串,因此如果需要保留原始字符串,可以先创建一个副本进行分割操作。
2.如果没有更多的子字符串可供返回,则返回NULL
strcpy函数
char *strcpy(char *dest, const char *src)
将后者的字符串内容赋给前者
源字符串必须以 ‘\0’ 结束。
会将源字符串中的 ‘\0’ 拷贝到目标空间。
目标空间必须足够大,以确保能存放源字符串。
目标空间必须可变。
putenv函数
putenv()函数
在Linux中,putenv()函数用于设置环境变量的值。它的函数原型定义在<stdlib.h>头文件中。
以下是putenv()函数的函数原型:
#include <stdlib.h>
int putenv(char *string);
putenv()函数接受一个以key=value形式表示的字符串参数,用于设置环境变量的值。它会将该字符串添加到当前进程的环境变量中,或者如果该环境变量已经存在,则会更新其值。
putenv()函数返回一个整数值,表示执行结果。如果成功设置或更新环境变量,则返回0;如果出现错误,则返回非零值。
具体使用:
```cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
char *env_var = "MY_VAR=my_value";
putenv(env_var);
char *value = getenv("MY_VAR");
if (value != NULL) {
printf("MY_VAR value: %s\n", value);
} else {
printf("MY_VAR not found.\n");
}
return 0;
}
chdir函数
在Linux的C语言编程中,chdir
函数用于改变当前工作目录。它的原型如下:
int chdir(const char *path);
其中,path
是一个字符串,表示要切换到的目标目录的路径。
chdir
函数会将当前进程的工作目录更改为指定的目录。如果目录切换成功,则返回0;如果出现错误,则返回-1,并设置相应的错误码,可以通过errno
变量获取具体的错误信息。
需要注意的是,chdir
函数只影响当前进程的工作目录,并不影响其他进程。在多进程或多线程的程序中,使用chdir
函数只会改变当前进程的工作目录,不会影响其他进程或线程的工作目录。
下面是一个简单的示例:
#include <stdio.h>
#include <unistd.h>
int main() {
if (chdir("/path/to/directory") == 0) {
printf("目录切换成功\n");
} else {
perror("目录切换失败");
}
return 0;
}
上述示例将当前进程的工作目录切换到/path/to/directory
目录,并打印相应的结果。
open函数
在Linux的C语言编程中,open
函数用于打开文件或创建文件。它的原型如下:
#include <fcntl.h>
int open(const char *path, int flags, mode_t mode);
其中,path
是一个字符串,表示要打开或创建的文件的路径;flags
是一个整数,表示打开文件的方式和选项;mode
是一个文件权限的模式,用于创建新文件时指定权限。
open
函数会返回一个文件描述符(file descriptor),用于后续对文件的读写操作。如果打开或创建文件成功,则返回一个非负整数的文件描述符;如果出现错误,则返回-1,并设置相应的错误码,可以通过errno
变量获取具体的错误信息。
flags
参数可以使用以下常用的标志(可以使用按位或|
组合多个标志):
-
O_RDONLY
:以只读方式打开文件。 -
O_WRONLY
:以只写方式打开文件。 -
O_RDWR
:以读写方式打开文件。 -
O_CREAT
:如果文件不存在,则创建文件。 -
O_TRUNC
:如果文件存在且以写方式打开,则将文件截断为空文件。 -
O_APPEND
:以追加方式打开文件,每次写入都会添加到文件末尾。
mode
参数只在创建新文件时使用,指定新文件的权限。它通常使用八进制表示,例如0644
表示权限为rw-r--r--
。
下面是一个简单的示例:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "Hello, World!", 13);
close(fd);
printf("文件写入成功\n");
} else {
perror("文件打开失败");
}
return 0;
}
execve封装的函数
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1,exec函数只有出错的返回值而没有成功的返回值
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[])
wait和waitpid函数
wait()函数是一个系统调用,用于使当前进程进入等待状态,直到它的一个子进程结束或收到一个信号为止。当子进程结束时,它会向父进程发送一个信号,父进程通过wait()函数来获取子进程的退出状态。
wait()函数的原型如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
其中,status是一个指向整型变量的指针,用于存储子进程的退出状态。如果不关心子进程的退出状态,可以将status设置为NULL。wait()函数的返回值是子进程的进程ID,如果出错则返回-1。如果当前进程没有子进程,调用wait()函数会立即返回。
waitpid函数用于等待指定的子进程结束并获取其状态。它的原型如下:
其中,pid是要等待的子进程的进程ID,可以指定为以下值:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid > 0:等待具有指定进程ID的子进程结束。
pid == -1:等待任意子进程结束,相当于wait函数。
pid == 0:等待与调用进程属于同一个进程组的任意子进程结束。
pid < -1:等待指定进程组ID的任意子进程结束。
status是一个整型指针,用于存储子进程的退出状态。如果不关心子进程的退出状态,可以将status设置为NULL。
options是一个整数,用于指定等待子进程的行为和选项,常用的选项有:
WCONTINUED:等待被暂停的子进程继续执行。
WNOHANG:如果没有子进程结束,则立即返回,不阻塞。
WUNTRACED:等待被停止的子进程。
waitpid函数会阻塞调用进程,直到指定的子进程结束或满足指定的条件。如果成功等待到子进程结束,则返回子进程的进程ID;如果出现错误,则返回-1。文章来源:https://www.toymoban.com/news/detail-523196.html
解析宏
可以使用一些宏来解析waitpid函数的返回值,以获取子进程的退出状态。
以下是常用的宏:
WIFEXITED(status):如果子进程正常退出,则返回非零值。
WEXITSTATUS(status):如果子进程正常退出,返回子进程的退出状态码。
WIFSIGNALED(status):如果子进程被信号终止,则返回非零值。
WTERMSIG(status):如果子进程被信号终止,返回导致终止的信号编号。
WIFSTOPPED(status):如果子进程被暂停,则返回非零值。
WSTOPSIG(status):如果子进程被暂停,返回导致暂停的信号编号。文章来源地址https://www.toymoban.com/news/detail-523196.html
到了这里,关于Linux 简易shell 实现与分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!