shell
主函数
- 先创建一个Shell类型的类
- 然后屏蔽信号ctrl+c与ctrl+D
- 接着打印提示符,接收命令的输入
- 判断该次命令是否含有exitclear!等信息,如果有,执行相应操作
- 通过调用解析函数解析本次输入的命令
屏蔽信号
- ctrl  +  C | 1
 | signal(SIGINT, SIG_IGN);
 |  
 
- ctrl  +  D | 12
 3
 4
 
 | struct 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);
 | 
参数说明:
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:在所有输出都被传输后更改属性,并丢弃所有未读的输入。
print
getlogin
strcspn
| 12
 3
 4
 
 | 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结构体的指针,其中包含要格式化的时间和日期。
| 12
 3
 4
 
 | chrono::system_clock::now();
 chrono::system_clock::to_time_t
 
 
 | 
mycd
getenv
| 1
 | char* getenv(const char* name);
 | 
参数说明:
- name是一个指向以null结尾的字符串,表示要获取的环境变量的名称.
根据指定的环境变量的名称,返回对应的环境变量的值。找到则返回一个null结尾的字符串,表示该环境变量的值.否则返回null.
| 12
 
 | 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
| 12
 3
 
 | #include <unistd.h>
 int dup2(int oldfd, int newfd);
 
 | 
- 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
| 12
 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
 
 | #include <iostream>using namespace std;
 #include <string>
 #include <vector>
 #include <sstream>
 #include <chrono>
 #include <unistd.h>
 #include <csignal>
 #include <algorithm>
 #include <termios.h>
 #include <cstdlib>
 #include <wait.h>
 #include <fstream>
 #include <sys/stat.h>
 #include <fcntl.h>
 
 class Shell
 {
 public:
 void print();
 void setlinebuf();
 string retlinebuf();
 void setsignal();
 bool isempty();
 void addHistory(const string& line);
 void setEOF();
 void printHistory();
 void searchAndReplay(const string &keyword);
 void mycd(vector<string> &s);
 void executeCommand(const vector<string> &command);
 void parse(string &s);
 private:
 string linebuf;
 vector<string> history;
 };
 
 | 
func.cpp
| 12
 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
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 
 | #include "shell.hpp"
 void Shell::print()
 {   cout << "----------------------------------------------------------------------------" << endl;
 cout << "           ___                                                     ___" << endl;
 cout << "  ______  /  /___       ______  _____  ___   ___       ____  _____/  /___" << endl;
 cout << " /  __  \\/  __   \\     /  ___ - ___  \\/  /  /  /     /_   / /  __/  __   \\" << endl;
 cout << "/  /_/  /  /  /  /    /  /  /  /  /  /  /__/  /       /  /_(__  )  /  /  /" << endl;
 cout << "\\______/__/  /__/    /__/  /__/  /__/\\____,  /       /____/____/__/  /__/" << endl;
 cout << "                                    /_______/" << endl;
 cout << "----------------------------------------------------------------------------" << endl;
 char cwd [1024];
 auto now = chrono::system_clock::now();
 time_t time = chrono::system_clock::to_time_t(now);
 tm* timeinfo = std::localtime(&time);
 char timeStr[9];
 strftime(timeStr, sizeof(timeStr), "%H:%M:%S", timeinfo);
 
 
 
 cout << "# future @ future-arch in " << getcwd(cwd,1024) << " [" << timeStr << "]" << endl;
 cout << "$ " ;
 fflush(stdout);
 }
 
 void Shell ::setlinebuf()
 {
 getline(cin,this->linebuf);
 }
 
 string Shell::retlinebuf ()
 {
 return this->linebuf;
 }
 
 void Shell:: setsignal()
 {
 signal(SIGINT, SIG_IGN);
 }
 
 bool Shell ::isempty()
 {
 if(this->linebuf.empty())
 {
 return true;
 }
 return false;
 }
 
 void Shell::addHistory(const string& line)
 {
 for(vector<string>::iterator it = history.begin(); it != history.end();it++)
 {
 if((*it).compare(line) == 0)
 return ;
 }
 history.push_back(line);
 }
 
 void Shell ::setEOF()
 {
 struct termios term;
 tcgetattr(STDIN_FILENO, &term);
 term.c_cc[VEOF] = _POSIX_VDISABLE;
 tcsetattr(STDIN_FILENO, TCSANOW, &term);
 }
 
 void Shell::printHistory()
 {
 for(vector<string>::iterator it = this->history.begin(); it != this->history.end();it++)
 {
 cout << *it << endl;
 }
 cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~"<< endl;
 }
 
 void Shell::searchAndReplay(const string& keyword)
 {
 for (const string& line : history)
 {
 if (line.find(keyword) != string::npos)
 {
 cout << "Replaying: " << line << endl;
 linebuf = line;
 break;
 }
 }
 }
 
 void Shell::parse(string& s)
 {
 vector<vector<string>> tokens;
 istringstream iss(s);
 string root;
 while (getline(iss, root, '|'))
 {
 istringstream root_iss(root);
 vector<string> root_tokens;
 string token;
 while (root_iss >> token)
 {
 root_tokens.push_back(token);
 }
 
 tokens.push_back(root_tokens);
 }
 if (tokens.size() == 1)
 {
 vector<string>& command = tokens[0];
 if (command[0] == "cd")
 {
 mycd(command);
 }
 else
 {
 executeCommand(command);
 }
 }
 else if (tokens.size() > 1)
 {
 int numPipes = tokens.size() - 1;
 vector<int> pipefds(2 * numPipes, 0);
 for (int i = 0; i < numPipes; ++i)
 {
 if (pipe(pipefds.data() + i * 2) < 0)
 {
 perror("pipe()");
 exit(1);
 }
 }
 int commandIndex = 0;
 for (auto& command : tokens)
 {
 pid_t pid = fork();
 if (pid < 0)
 {
 perror("fork()");
 exit(1);
 }
 if (pid == 0)
 {
 
 if (commandIndex != 0)
 {
 if (dup2(pipefds[(commandIndex - 1) * 2], STDIN_FILENO) < 0)
 {
 perror("dup2()");
 exit(1);
 }
 }
 if (commandIndex != numPipes)
 {
 if (dup2(pipefds[commandIndex * 2 + 1], STDOUT_FILENO) < 0)
 {
 perror("dup2()");
 exit(1);
 }
 }
 
 for (int i = 0; i < 2 * numPipes; ++i)
 {
 close(pipefds[i]);
 }
 if (command[0] == "cd")
 {
 mycd(command);
 }
 else
 {
 executeCommand(command);
 }
 
 exit(0);
 }
 else
 {
 
 ++commandIndex;
 }
 }
 
 for (int i = 0; i < 2 * numPipes; ++i)
 {
 close(pipefds[i]);
 }
 
 
 for (size_t i = 0; i < tokens.size(); ++i)
 {
 wait(NULL);
 }
 }
 }
 
 void Shell::mycd(vector<string>& s)
 {
 if (s.size() > 2)
 {
 cout << "cd: 函数调用参数过多" << endl;
 return;
 }
 else if (s.size() == 1 || s[1].compare("~") == 0)
 {
 const char* homeDir = getenv("HOME");
 if (homeDir)
 {
 if (chdir(homeDir) == -1)
 {
 perror("chdir()");
 }
 }
 else
 {
 cout << "cd: 无法获取家目录路径" << endl;
 return;
 }
 }
 else if (s[1].compare("-") == 0)
 {
 const char* lastDir = getenv("OLDPWD");
 if (lastDir)
 {
 if (chdir(lastDir) == -1)
 {
 perror("chdir()");
 }
 }
 else
 {
 cout << "cd: 无法找到上一个路径" << endl;
 }
 }
 else
 {
 const char* path = s[1].c_str();
 if (chdir(path) == -1)
 {
 perror("chdir()");
 }
 }
 
 char* currentDir = get_current_dir_name();
 if (currentDir)
 {
 if (setenv("OLDPWD", currentDir, 1) == -1)
 {
 perror("setenv()");
 }
 free(currentDir);
 }
 else
 {
 perror("get_current_dir_name()");
 }
 }
 
 void Shell::executeCommand(const vector<string>& command)
 {
 pid_t pid;
 fflush(NULL);
 pid = fork();
 if (pid < 0)
 {
 perror("fork()");
 }
 if (pid == 0)
 {
 vector<char*> args;
 vector<int> outputFds;
 
 for (size_t i = 0; i < command.size(); ++i)
 {
 if (command[i] == ">")
 {
 int fd = open(command[i + 1].c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
 if (fd == -1)
 {
 perror("open()");
 exit(1);
 }
 outputFds.push_back(fd);
 dup2(fd, STDOUT_FILENO);
 close(fd);
 i++;
 }
 else if (command[i] == ">>")
 {
 int fd = open(command[i + 1].c_str(), O_WRONLY | O_CREAT | O_APPEND, 0644);
 if (fd == -1)
 {
 perror("open()");
 exit(1);
 }
 outputFds.push_back(fd);
 dup2(fd, STDOUT_FILENO);
 close(fd);
 i++;
 }
 else if (command[i] == "<")
 {
 int fd = open(command[i + 1].c_str(), O_RDONLY);
 if (fd == -1)
 {
 perror("open()");
 exit(1);
 }
 dup2(fd, STDIN_FILENO);
 close(fd);
 i++;
 }
 else if (command[i] == "<<")
 {
 string delimiter = command[i + 1];
 string inputText;
 
 while (getline(cin, inputText))
 {
 if (inputText == delimiter)
 break;
 inputText += "\n";
 write(STDIN_FILENO, inputText.c_str(), inputText.size());
 }
 
 i++;
 }
 else
 {
 args.push_back(const_cast<char*>(command[i].c_str()));
 }
 }
 
 args.push_back(nullptr);
 execvp(args[0], args.data());
 
 
 perror("execvp()");
 exit(1);
 }
 else
 {
 wait(NULL);
 }
 }
 
 | 
shell.cpp
| 12
 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
 41
 42
 43
 44
 45
 46
 47
 48
 
 | #include "func.cpp"
 int main()
 {
 int num = 1;
 Shell mysh;
 mysh.setsignal();
 mysh.setEOF();
 while(num)
 {
 mysh.print();
 mysh.setlinebuf();
 string t = mysh.retlinebuf();
 if(t.empty())
 {
 cout << "No input" << endl;
 }
 else if(t.rfind("exit")!=string::npos)
 {
 break;
 }
 else if(t.find("!")==0)
 {
 string keyword = t.substr(1);
 mysh.searchAndReplay(keyword);
 }
 else if(t.find("clear")==0)
 {
 mysh.addHistory(t);
 
 system("clear");
 num++;
 }
 else
 {
 mysh.addHistory(t);
 num++;
 }
 mysh.parse(t);
 if(num%3==0)
 {
 cout << "~~~~~~~~~~~~~~~~~~~~~~~~~~~"<< endl;
 cout << " History Order :" << endl;
 mysh.printHistory();
 }
 }
 return 0;
 }
 
 | 
后续问题: 
- 在输入命令全为- 空格的时候,会直接推出程序,造成程序崩溃.
 - 
- 是在- parse中解析命令的时候,由于全为空格,所以会将一个空字符串token放入命令数组tokens,导致tokens.size()不为0造成exec函数的执行出现错误
 
- 所以在开始解析命令的时候加上全为- 空格的判断.
 | 12
 3
 4
 
 | if(std::all_of(s.begin(),s.end(),::isspace)){
 return {};
 }
 
 |  
 
- 在全为空格的时候直接返回,这样就可以返会空的二维数组. 
 
- 缺乏对乱码命令的提示:例如输入”djsakldjalkjsduj”,等命令没有类似- zsh : command not found : jdklasjdlak的提示,而是由于exec函数的无法执行出现的提示.
 
- 对于主题打印的提示,可以将- Oh my zsh的打印只放在初始的时候,而不是每一次都打印.
 
- 另外在输出主机名称时,输出的是固定的作者的主机名称,这里为了偷懒,未使用- gethostname()来获取主机名.
 
- 可以考虑对命令的存储进行改进,运用其他的结构存储,而不是二维数组. 
- 增加类与类之间的联系,而不是简单的创造一个类,这样的话就只是相当于类似头文件的作用,并没有过多的用到一些c++的特性之类的东西.