Friday, 29 March 2024
shell
主函数
- 先创建一个Shell类型的类
- 然后屏蔽信号ctrl+c与ctrl+D
- 接着打印提示符,接收命令的输入
- 判断该次命令是否含有
exitclear!等信息,如果有,执行相应操作 - 通过调用解析函数解析本次输入的命令
屏蔽信号
ctrl + C
1
signal(SIGINT, SIG_IGN);//将该信号的处理方式设置为忽略
ctrl + D
1
2
3
4struct termios term;
tcgetattr(STDIN_FILENO, &term);
term.c_cc[VEOF] = _POSIX_VDISABLE;
tcsetattr(STDIN_FILENO, TCSANOW, &term);
首先定义一个termios类型的结构体,用来存储终端相关的属性信息
然后通过调用tcgetattr函数将获取终端的相关属性,并将这些属性保存到 **term **结构体中,第一个参数表示标准输入,是一个预定的文件描述符常量
将结构体中的c_cc数组成员的VEOF设置为禁用. c_cc数组是包含终端特殊字符的数组,VEOF是其中的一个索引,第二个参数是一个特殊的宏,用于禁用该特殊字符
最后,
tcsetattr(STDIN_FILENO, TCSANOW, &term);调用tcsetattr()函数,它用于设置终端的属性。STDIN_FILENO表示标准输入文件描述符,TCSANOW表示立即生效的选项,&term是要设置的终端属性结构体。
tcgetattr
1 | int tcgetattr(int fd, struct termios *termios_p); |
参数说明:
fd是一个打开的终端设备文件描述符。termios_p是一个指向termios结构体的指针,用于存储获取到的终端属性。
tcgetattr 函数返回一个整数值,表示函数调用的成功与否。如果函数成功执行,返回值为0;如果出现错误,返回值为-1,并设置相应的错误码。在调用该函数时,你需要检查返回值以确保函数调用是否成功。
tcsetattr
1 | int tcsetattr(int fd, int optional_actions, const struct termios *termios_p); |
参数说明:
fd是一个打开的终端设备文件描述符。optional_actions是一个表示操作选项的整数值,用于指定何时应用属性的更改。termios_p是一个指向termios结构体的指针,其中包含了要应用的终端属性。
optional_actions 参数用于指定何时应用属性的更改,它可以取以下三个值之一:
TCSANOW:立即更改属性。TCSADRAIN:在所有输出都被传输后更改属性。TCSAFLUSH:在所有输出都被传输后更改属性,并丢弃所有未读的输入。
getlogin
1 | char* getlogin(void) |
strcspn
1 | size_t strscpn(const char *str1,const char *str2); |
strftime
1 | size_t strftime(char *str, size_t maxsize, const char *format, const struct tm *timeptr); |
参数说明:
str是一个指向字符数组的指针,用于存储格式化后的时间字符串。maxsize是str所指向的字符数组的最大容量。format是一个格式字符串,用于定义输出的时间格式。timeptr是一个指向struct tm结构体的指针,其中包含要格式化的时间和日期。
1 | chrono::system_clock::now(); |
mycd
getenv
1 | char* getenv(const char* name); |
参数说明:
- name是一个指向以null结尾的字符串,表示要获取的环境变量的名称.
根据指定的环境变量的名称,返回对应的环境变量的值。找到则返回一个null结尾的字符串,表示该环境变量的值.否则返回null.
1 | const char* path = s[1].c_str(); |
需要注意的是c_str()返回的指针指向的仍为string内部存储的字符数组,因此需要确保在使用path指针时,string对象仍然有效
get_current_dir_name
是一个标准的POSIX函数,用于获取当前工作目录的路径
由于get_current_dir_name( )分配了动态内存,因此使用完毕后需要调用free( )。
setenv
1 | int setenv(const char* name, const char* value, int overwrite); |
参数说明:
name是一个指向以 null 结尾的字符串,表示要设置或新建的环境变量的名称。value是一个指向以 null 结尾的字符串,表示要设置的环境变量的值。overwrite是一个整数值,指示在环境变量已经存在时是否进行覆盖。如果overwrite为非零值,则会覆盖现有环境变量;如果overwrite为零,则不进行覆盖。
parse
istringstream是C++标准库中的一个类,他是用于从字符串中进行输入操作的输入流类.
getline()
1 | std::istream& getline(std::istream& is, std::string& str, char delim); |
参数说明:
is是一个输入流对象,表示要从中读取文本的输入流,通常是std::cin(标准输入)或文件流对象。str是一个字符串对象的引用,表示读取的文本将存储在其中。delim是一个字符(默认为换行符\n),表示行的结束符。
pipe
“pipe”(管道)是一种在进程间进行通信的机制。它允许一个进程的输出直接成为另一个进程的输入,从而实现进程间的数据传输。
- 匿名管道:
- 匿名管道是最常见的管道类型。它是一种单向通信管道,只能在具有父子关系的进程之间使用。父进程创建管道后,可以通过文件描述符进行读取或写入。子进程继承了父进程的文件描述符,从而可以使用相反的操作进行读取或写入。
- 匿名管道是一种半双工通信方式,即数据只能在一个方向上流动。如果需要双向通信,需要创建两个匿名管道。
- 使用匿名管道的典型流程是:父进程创建管道,然后调用
fork()创建子进程。父进程关闭不需要的管道端口,子进程关闭另一个不需要的端口。然后父进程可以向管道写入数据,子进程可以从管道读取数据。
- 命名管道:
- 命名管道也称为FIFO(First-In-First-Out),它提供了一种无关进程关系的进程间通信方式。命名管道在文件系统中有一个与之相关联的路径名,多个进程可以通过这个路径名来进行通信。
- 命名管道是一种半双工通信方式,类似于匿名管道,只能在一个方向上流动数据。
- 命名管道可以使用
mkfifo函数创建,也可以使用命令行工具mkfifo创建。
管道的使用可以通过系统调用函数来实现,例如 POSIX 标准中的 pipe() 函数。在使用管道时,重要的是要了解管道的读取和写入端口以及数据的流动方向,以确保正确的通信。
1 | int pipe(int pipefd[2]); |
pipe()函数接受一个整型数组pipefd[2],用于存储管道的文件描述符。pipefd[0]表示管道的读取端口,pipefd[1]表示管道的写入端口。该函数返回值为 0 表示成功,-1 表示失败。以下是
pipe()函数的详细解释:
pipefd参数是一个包含两个元素的整型数组,用于存储管道的文件描述符。pipefd[0]是管道的读取端口,用于从管道中读取数据;pipefd[1]是管道的写入端口,用于向管道中写入数据。pipe()函数创建一个匿名管道,并将管道的读取端口和写入端口的文件描述符填充到pipefd数组中。- 匿名管道是一种半双工通信方式,数据只能在一个方向上流动。如果需要双向通信,需要创建两个匿名管道。
- 管道是内核中的一个缓冲区,用于在相关进程之间传输数据。数据写入管道后,可以从管道的读取端口读取。
- 管道的大小是有限的,一旦管道的缓冲区满了,进程写入数据时可能会被阻塞,直到有足够的空间来容纳数据。同样,如果管道为空,从管道读取数据时可能会被阻塞,直到有数据可读。
- 如果一个进程关闭了管道的读取端口,而另一个进程关闭了管道的写入端口,这两个进程之间的管道通信将结束。
dup2
1 |
|
oldfd参数是需要复制的文件描述符。它可以是任何有效的文件描述符,包括标准输入(0)、标准输出(1)和标准错误(2)。newfd参数是要复制到的新文件描述符。如果newfd已经打开,则会先关闭它,然后将oldfd复制到newfd。- 复制后,
newfd将和oldfd指向相同的文件表项,它们共享同一个文件偏移量和文件状态标志。- 如果
newfd等于oldfd,则dup2()函数不进行任何操作,直接返回newfd。dup2()函数的一个常见用途是重定向标准输入、标准输出和标准错误。例如,可以将标准输出重定向到一个文件,或者将标准错误重定向到一个套接字。
executeCommand
open
command[i + 1].c_str():command是一个字符串向量,command[i + 1]表示该向量中的第i + 1个元素。.c_str()将该元素转换为C风格的字符串,以便与open()函数的参数类型匹配。这个参数是要打开的文件的路径。O_WRONLY:是open()函数的一个打开模式,表示以只写方式打开文件。这意味着你可以向文件写入数据,但不能读取文件的内容。O_CREAT:是open()函数的一个打开模式,表示如果文件不存在,则创建该文件。如果文件已经存在,open()函数仍然会成功打开文件。O_TRUNC:是open()函数的一个打开模式,表示如果文件已经存在且以只写方式打开,那么在打开文件时将截断文件的长度为0。这意味着打开文件后,文件中已经存在的内容将被清空。0644:表示文件的权限。在这里,0644表示文件所有者具有读和写的权限,而其他用户只有读的权限。它是一个八进制数,其中第一个数字0表示这是一个八进制数,后面的三个数字分别表示文件所有者、文件所属组和其他用户的权限。
代码
shepp.hpp
1 | #include <iostream> |
func.cpp
1 |
|
shell.cpp
1 |
|
后续问题:
在输入命令全为
空格的时候,会直接推出程序,造成程序崩溃.是在
parse中解析命令的时候,由于全为空格,所以会将一个空字符串token放入命令数组tokens,导致tokens.size()不为0造成exec函数的执行出现错误所以在开始解析命令的时候加上全为
空格的判断.1
2
3
4if(std::all_of(s.begin(),s.end(),::isspace))
{
return {};
}在全为空格的时候直接返回,这样就可以返会空的二维数组.
缺乏对乱码命令的提示:例如输入”djsakldjalkjsduj”,等命令没有类似
zsh : command not found : jdklasjdlak的提示,而是由于exec函数的无法执行出现的提示.对于主题打印的提示,可以将
Oh my zsh的打印只放在初始的时候,而不是每一次都打印.另外在输出主机名称时,输出的是固定的作者的主机名称,这里为了偷懒,未使用
gethostname()来获取主机名.可以考虑对命令的存储进行改进,运用其他的结构存储,而不是二维数组.
增加类与类之间的联系,而不是简单的创造一个类,这样的话就只是相当于类似头文件的作用,并没有过多的用到一些c++的特性之类的东西.













Dear digiKam fans and users,