1.理解文件
1-1 狭义理解
• ⽂件在磁盘⾥。
• 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的。
• 磁盘是外设(即是输出设备也是输⼊设备)。
• 磁盘上的⽂件 本质是对⽂件的所有操作,都是对外设的输⼊和输出 简称 IO。
1-2 ⼴义理解
• Linux 下⼀切皆⽂件(键盘、显⽰器、网卡、磁盘…… 这些都是抽象化的过程)(后⾯会讲如何去理解)
1-3 文件操作的归类认知
• 对于 0KB 的空⽂件是占⽤磁盘空间的。
• ⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)。
• 所有的⽂件操作本质是⽂件内容操作和⽂件属性操作。
1-4 系统角度
• 对⽂件的操作本质是进程对⽂件的操作
• 磁盘的管理者是操作系统
• 文件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的
2. 回顾C文件接口
我们在c语言阶段的文章中,介绍了一部分文件操作的接口,现在我们复习一下。
2-1 hello.c打开文件
#include <stdio.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
while(1);
fclose(fp);
return 0;
}
打开的myfile⽂件在哪个路径下?
• 在程序的当前路径下,那系统怎么知道程序的当前路径在哪⾥呢?
可以使⽤ ls /proc/[进程id] -l 命令查看当前正在运⾏进程的信息:
[hyb@VM-8-12-centos io]$ ps ajx | grep myProc
506729 533463 533463 506729 pts/249
533463 R+
1002
7:45 ./myProc
536281 536542 536541 536281 pts/250
536541 R+
1002
0:00 grep --
color=auto myProc
[hyb@VM-8-12-centos io]$ ls /proc/533463 -l
total 0
......
-r--r--r-- 1 hyb hyb 0 Aug 26 17:01 cpuset
lrwxrwxrwx 1 hyb hyb 0 Aug 26 16:53 cwd -> /home/hyb/io
-r-------- 1 hyb hyb 0 Aug 26 17:01 environ
lrwxrwxrwx 1 hyb hyb 0 Aug 26 16:53 exe -> /home/hyb/io/myProc
dr-x------ 2 hyb hyb 0 Aug 26 16:54 fd
......
其中:
• cwd:指向当前进程运⾏⽬录的⼀个符号链接。
• exe:指向启动当前进程的可执⾏⽂件(完整路径)的符号链接。
打开文件,本质是进程打开,所以,进程知道自己在哪⾥,即便文件不带路径,进程也知道。由此OS就能知道要创建的文件放在哪里。
2-2 hello.c写文件
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
const char *msg = "hello bit!\n";
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
}
fclose(fp);
return 0;
}
2-3 hello.c读文件
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = fopen("myfile", "r");
if(!fp){
printf("fopen error!\n");
return 1;
}
char buf[1024];
const char *msg = "hello bit!\n";
while(1){
//注意返回值和参数,此处有坑,仔细查看man⼿册关于该函数的说明
ssize_t s = fread(buf, 1, strlen(msg), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
稍作修改,实现简单cat命令:
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("argv error!\n");
return 1;
}
FILE *fp = fopen(argv[1], "r");
if(!fp){
printf("fopen error!\n");
return 2;
}
char buf[1024];
while(1){
int s = fread(buf, 1, sizeof(buf), fp);
if(s > 0){
buf[s] = 0;
printf("%s", buf);
}
if(feof(fp)){
break;
}
}
fclose(fp);
return 0;
}
2-4 输出信息到显⽰器,你有哪些方法
#include <stdio.h>
#include <string.h>
int main()
{
const char *msg = "hello fwrite\n";
fwrite(msg, strlen(msg), 1, stdout);
printf("hello printf\n");
fprintf(stdout, "hello fprintf\n");
return 0;
}
2-5 stdin & stdout & stderr
• C默认会打开三个输⼊输出流,分别是stdin, stdout, stderr。
• 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,⽂件指针。
#include <stdio.h>
extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;
我们的问题是——系统为什么会默认打开这三个文件呢?
答:“因为程序是用来做数据处理的,做数据处理就是对文件内容的数据的处理,有一些我们程序启动一定会用到的程序,如果系统不打开,那么就要我们自己默认打开,所以系统默认的打开这几个文件的目的还是为了我们能更高效地编写程序”。
2-6 打开文件的方式
r
Open text file for reading.
The stream is positioned at the beginning of the file.
r+
Open for reading and writing.
The stream is positioned at the beginning of the file.
w
Truncate(缩短) file to zero length or create text file for writing.
The stream is positioned at the beginning of the file.
w+
Open for reading and writing.
The file is created if it does not exist, otherwise it is truncated.
The stream is positioned at the beginning of the file.
a
Open for appending (writing at end of file).
The file is created if it does not exist.
The stream is positioned at the end of the file.
a+
Open for reading and appending (writing at end of file).
The file is created if it does not exist. The initial file position
for reading is at the beginning of the file,
but output is always appended to the end of the file.
w:我们翻翻译一下,以w的方式打开文件的话,会先清空文件内容,然后我们可以往文件写入内容。这个我们在c语言部分讲过。
我们再来看输出重定向其实也有这样的特性:
往一个文件中使用输入重定向输入一些字符,则文件中的内容完全被替换,其实是文件中的内容在打开之前就被删除了。
a:以a方式打开的文件,不会删除内容,如果要往文件中写入内容,会以追加的方式写入,也就是在保留文件原有内容的基础上增加我们输入的内容,这就叫追加。
而追加重定向就相当于以a方式打开文件进行追加写入。
如上,是我们之前学的⽂件相关操作。
3. 系统文件I/O
打开文件的方式不仅仅是fopen,ifstream等流式,语⾔层的⽅案,其实系统才是打开文件最底层的方案。不过,在学习系统⽂件IO之前,先要了解下如何给函数传递标志位,该方法在系统⽂件IO接口中会使用到:
我们来看一个函数“open”,它是一个系统调用,不论我们使用什么方式来打开文件,底层都会调用open,它有三种传递参数的方式,我们先介绍前面两个。
第一种就是我们标红框框的那一栏,它的第一个参数是路径,第二个参数就是我们要介绍的标记位,它就像位图一样,我们知道,一个整型占32个比特位,每个比特位可以是0或者1,每个标志位在32位比特位中只有一位是1,其他均为0。
3-1 ⼀种传递标志位的方法
#include <stdio.h>
#define ONE
0001 //0000 0001
#define TWO
0002 //0000 0010
#define THREE 0004 //0000 0100
void func(int flags) {
if (flags & ONE) printf("flags has ONE! ");
if (flags & TWO) printf("flags has TWO! ");
if (flags & THREE) printf("flags has THREE! ");
printf("\n");
}
int main() {
func(ONE);
func(THREE);
func(ONE | TWO);
func(ONE | THREE | TWO);
return 0;
}
3.2 接口介绍
open函数
我们来看几个标志位:
说明时候使用第二种传参呢,看图:
第三个参数在文件不存在时使用,因为如果我们要打开的文件不存在,就会在当前路径下新建这个文件,但是它的权限是乱的,所以要加上权限,文件的默认权限是666,所以给它设置为0666就可以了。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的⽬标⽂件
flags: 打开⽂件时,可以传⼊多个参数选项,⽤下⾯的⼀个或者多个常量进⾏“或”运算,构成
flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定⼀个且只能指定⼀个
O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问
权限
O_APPEND: 追加写
返回值:
成功:新打开的⽂件描述符
失败:-1
mode_t理解:直接 man ⼿册,⽐什么都清楚。
open 函数具体使⽤哪个,和具体应⽤场景相关,如⽬标⽂件不存在,需要open创建,则第三个参数表示创建⽂件的默认权限,否则,使⽤两个参数的open。
write read close lseek ,类⽐C⽂件相关接口。
write函数
write函数也是一个系统调用:
第一个参数为文件描述符,第二个参数是我们要写入字符串的指针,第三个参数为我们要写入的数量,返回值为实际写入的数量,如果第三个参数为1000,而只写入了500,那么返回值就是500。
我们来使用它写入一些字符:
来看结果:
现在还是正常的,我们再写入看看,这次只写入一行:
然后我们就得到了这样的结果:
文件不是被打开就被清空了吗,为什么在这里是覆盖写入呢?事实上,这是由open函数的第二个参数——我们传入的标志位决定的:
因为我们只传入了两个标志位,一个为不存在就创建,一个为只读,我们只需要在这里再传入一个个代表清空的标志位就可以了:
我们再来运行这个程序,来看结果:
之前的内容就被清空了,这样的场景是不是很像文件操作中的”W“方式写入呢?
那么这种场景就是”a“权限了。
问题:我们写到显示器的”123456“是数字还是字符呢?
我们来看现象,向文件写入一个数字:
看结果 :
我们的文件中的内容变成了乱码,这是为什么呢?因为我们写入的数字是以二进制的方式写入的,文件识别不了二进制字符,就变成了乱码,我们再以文本的方式写入:
看结果:
此时就正常显示了。所以答案是以字符显示。
结论:显示器是一个字符设备。
read函数
read函数也是一个系统调用,使用方法与前面的几个函数类似,就不过多介绍,我们演示一遍:
结果:
3-3 open函数返回值
在认识返回值之前,先来认识⼀下两个概念: 系统调用 和 库函数
• 上⾯的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数。
(libc)。
• 而 open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接口。
• 回忆⼀下我们讲操作系统概念时,画的⼀张图。
系统调⽤接⼝和库函数的关系,一目了然。
4.文件描述符fd
通过对open函数的学习,我们知道了⽂件描述符就是⼀个⼩整数。
4.1 0 & 1 & 2
我们在之前讲过我们在编写程序时,系统会默认帮我们打开三个文件,分别是标准输入,标准输出,标准错误。
而这三个文件也有自己的fd,那就是0,1,2,我们来看一个例子,写一个程序:
新建四个文件,并打印他们各自的fd:
它们竟然是从3开始标记的,这么说我们只需要打印那三个文件的fd就能验证了,没错。
在验证之前,我们解释一下我们熟悉的关键字——FTIE,我们需要对文件进行读写时,都会用到它:
那么它到底是什么呢?它其实是一个结构体,它的内部封装了文件的fd,因为系统调用只认fd,所以我们只需要将这几个结构体里面的fd成员打印出来就可以验证了:
来看结果:
果然,0,1,2被这几个文件占据了。
所以:
• Linux进程默认情况下会有3个缺省打开的⽂件描述符,分别是标准输⼊0, 标准输出1, 标准错
误2.
• 0,1,2对应的物理设备⼀般是:键盘,显示器,显示器 。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
现在知道,⽂件描述符就是从0开始的⼩整数。当我们打开⽂件时,操作系统在内存中要创建相应的数据结构来描述⽬标⽂件。于是就有了file结构体。表⽰⼀个已经打开的⽂件对象。⽽进程执⾏open系统调⽤,所以必须让进程和⽂件关联起来。每个进程都有⼀个指针*files, 指向⼀张表files_struct,该表最重要的部分就是包含⼀个指针数组,每个元素都是⼀个指向打开⽂件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着⽂件描述符,就可以找到对应的⽂件。
对文件的操作:
读:拿着对应的fd(文件描述符)找到这个文件的FILE结构体的指针,通过这个结构体找到文件缓冲区,这个缓冲区中有该文件的内容被加载到上面来,我们只需要拷贝一份拿到上层就实现了读操作。
写:前面步骤同上,只是需要携带我们输入的内容,将这份内容拷贝到缓冲区中,然后再将缓冲区的内容写回文件。
改:将文件内容拷贝到缓冲区,将它改变,再将内容拷贝回磁盘文件。
结论:对文件内容做任何操作,都必须将文件内容先加载到内核对应的文件缓冲区中!
4.2 文件描述符的分配规则
直接看代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
输出发现是 fd: 3
关闭0或者2,在看
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
发现是结果是: fd: 0 或者 fd 2 ,可⻅,⽂件描述符的分配规则:在files_struct数组当中,找到
当前没有被使用 的最⼩的⼀个下标,作为新的⽂件描述符。
4.3 重定向
那如果关闭1呢?看代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
时,我们发现,本来应该输出到显⽰器上的内容,输出到了⽂件 myfile 当中,其中,fd=1。这
种现象叫做输出重定向。常⻅的重定向有: > , >> , <。
那重定向的本质是什么呢?
就像我们之前将1关闭,其实将数组中标准输出FILE的指针删除了,然后新建了一个文件,通过分配规则可知,系统将1下标的空间分配给了我们新建的文件,那么这个文件的fd就是1了,使用printf打印的原理是找到fd为1的文件然后写入,而显然stdout的地址已经不在数组中了,被而是被替换成了我们新建的文件,所以printf将我们要打印到显示器的内容写入到了我们新建的文件。那么重定向的本质就是更改文件描述符表的指针指向!
我们再来看一个现象,写一份代码:
新建一个文件并写入内容,然后关闭,来看结果:
文件中什么内容都没有,占据内存为0 ,这是怎么一回事呢?我们后面再讲。
4.4 使用 dup2 系统调用
既然重定向的本质就是更改文件描述符表的指针指向,那么我们就可以通过修改指针指向来实现重定向操作。
函数原型如下:
#include <unistd.h>
int dup2(int oldfd, int newfd);
oldfd填我们要被修改的fd,newfd填我们要修改为的fd,也就是说如果我的fd为3,要将它的fd改为1号,那么代码为:
dup2(3, 1);
⽰例代码:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("./log", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}
printf是C库当中的IO函数,⼀般往 stdout 中输出,但是stdout底层访问⽂件的时候,找的还是fd:1,
但此时,fd:1下标所表⽰内容,已经变成了myfifile的地址,不再是显⽰器⽂件的地址,所以,输出的任何消息都会往⽂件中写⼊,进⽽完成输出重定向。那追加和输⼊重定向如何完成呢?
4.5 在minishell中添加重定向功能
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <ctype.h>
using namespace std;
const int basesize = 1024;
const int argvnum = 64;
const int envnum = 64;
// 全局的命令⾏参数表
char *gargv[argvnum];
int gargc = 0;
// 全局的变量
int lastcode = 0;
// 我的系统的环境变量
char *genv[envnum];
// 全局的当前shell⼯作路径
char pwd[basesize];
char pwdenv[basesize];
// 全局变量与重定向有关
#define NoneRedir
0
#define InputRedir 1
#define OutputRedir 2
#define AppRedir
3
int redir = NoneRedir;
char *filename = nullptr;
// "
"file.txt
#define TrimSpace(pos) do{\
while(isspace(*pos)){\
pos++;\
}\
}while(0)
string GetUserName()
{
string name = getenv("USER");
return name.empty() ? "None" : name;
}
string GetHostName()
{
string hostname = getenv("HOSTNAME");
return hostname.empty() ? "None" : hostname;
}
string GetPwd()
{
if(nullptr == getcwd(pwd, sizeof(pwd))) return "None";
snprintf(pwdenv, sizeof(pwdenv),"PWD=%s", pwd);
putenv(pwdenv); // PWD=XXX
return pwd;
//string pwd = getenv("PWD");
//return pwd.empty() ? "None" : pwd;
}
string LastDir()
{
string curr = GetPwd();
if(curr == "/" || curr == "None") return curr;
// /home/whb/XXX
size_t pos = curr.rfind("/");
if(pos == std::string::npos) return curr;
return curr.substr(pos+1);
}
string MakeCommandLine()
{
// [whb@bite-alicloud myshell]$
char command_line[basesize];
snprintf(command_line, basesize, "[%s@%s %s]# ",\
GetUserName().c_str(), GetHostName().c_str(), LastDir().c_str());
return command_line;
}
void PrintCommandLine() // 1. 命令⾏提⽰符
{
printf("%s", MakeCommandLine().c_str());
fflush(stdout);
}
bool GetCommandLine(char command_buffer[], int size)
// 2. 获取⽤⼾命令
{
// 我们认为:我们要将⽤⼾输⼊的命令⾏,当做⼀个完整的字符串
// "ls -a -l -n"
char *result = fgets(command_buffer, size, stdin);
if(!result)
{
return false;
}
command_buffer[strlen(command_buffer)-1] = 0;
if(strlen(command_buffer) == 0) return false;
return true;
}
void ResetCommandline()
{
memset(gargv, 0, sizeof(gargv));
gargc = 0;
// 重定向
redir = NoneRedir;
filename = nullptr;
}
void ParseRedir(char command_buffer[], int len)
{
int end = len - 1;
while(end >= 0)
{
if(command_buffer[end] == '<')
{
redir = InputRedir;
command_buffer[end] = 0;
filename = &command_buffer[end] + 1;
TrimSpace(filename);
break;
}
else if(command_buffer[end] == '>')
{
if(command_buffer[end-1] == '>')
{
redir = AppRedir;
command_buffer[end] = 0;
command_buffer[end-1] = 0;
filename = &command_buffer[end]+1;
TrimSpace(filename);
break;
}
else
{
redir = OutputRedir;
command_buffer[end] = 0;
filename = &command_buffer[end]+1;
TrimSpace(filename);
break;
}
}
else
{
end--;
}
}
}
void ParseCommand(char command_buffer[])
{
// "ls -a -l -n"
const char *sep = " ";
gargv[gargc++] = strtok(command_buffer, sep);
// =是刻意写的
while((bool)(gargv[gargc++] = strtok(nullptr, sep)));
gargc--;
}
void ParseCommandLine(char command_buffer[], int len) // 3. 分析命令
{
ResetCommandline();
ParseRedir(command_buffer, len);
ParseCommand(command_buffer);
//printf("command start: %s\n", command_buffer);
// "ls -a -l -n"
// "ls -a -l -n" > file.txt
// "ls -a -l -n" < file.txt
// "ls -a -l -n" >> file.txt
//printf("redir: %d\n", redir);
//printf("filename: %s\n", filename);
//printf("command end: %s\n", command_buffer);
}
void debug()
{
printf("argc: %d\n", gargc);
for(int i = 0; gargv[i]; i++)
{
printf("argv[%d]: %s\n", i, gargv[i]);
}
}
//enum
//{
//
FILE_NOT_EXISTS = 1,
//
OPEN_FILE_ERROR,
//};
void DoRedir()
{
// 1. 重定向应该让⼦进程⾃⼰做!
// 2. 程序替换会不会影响重定向?不会
// 0. 先判断 && 重定向
if(redir == InputRedir)
{
if(filename)
{
int fd = open(filename, O_RDONLY);
if(fd < 0)
{
exit(2);
}
dup2(fd, 0);
}
else
{
exit(1);
}
}
else if(redir == OutputRedir)
{
if(filename)
{
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);
if(fd < 0)
{
exit(4);
}
dup2(fd, 1);
}
else
{
exit(3);
}
}
else if(redir == AppRedir)
{
if(filename)
{
int fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);
if(fd < 0)
{
exit(6);
}
dup2(fd, 1);
}
else
{
exit(5);
}
}
else
{
// 没有重定向,Do Nothong!
}
}
// 在shell中
// 有些命令,必须由⼦进程来执⾏
// 有些命令,不能由⼦进程执⾏,要由shell⾃⼰执⾏ --- 内建命令 built command
bool ExecuteCommand()
// 4. 执⾏命令
{
// 让⼦进程进⾏执⾏
pid_t id = fork();
if(id < 0) return false;
if(id == 0)
{
//⼦进程
DoRedir();
// 1. 执⾏命令
execvpe(gargv[0], gargv, genv);
// 2. 退出
exit(7);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
if(WIFEXITED(status))
{
lastcode = WEXITSTATUS(status);
}
else
{
lastcode = 100;
}
return true;
}
return false;
}
void AddEnv(const char *item)
{
int index = 0;
while(genv[index])
{
index++;
}
genv[index] = (char*)malloc(strlen(item)+1);
strncpy(genv[index], item, strlen(item)+1);
genv[++index] = nullptr;
}
// shell⾃⼰执⾏命令,本质是shell调⽤⾃⼰的函数
bool CheckAndExecBuiltCommand()
{
if(strcmp(gargv[0], "cd") == 0)
{
// 内建命令
if(gargc == 2)
{
chdir(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 1;
}
return true;
}
else if(strcmp(gargv[0], "export") == 0)
{
// export也是内建命令
if(gargc == 2)
{
AddEnv(gargv[1]);
lastcode = 0;
}
else
{
lastcode = 2;
}
return true;
}
else if(strcmp(gargv[0], "env") == 0)
{
for(int i = 0; genv[i]; i++)
{
printf("%s\n", genv[i]);
}
lastcode = 0;
return true;
}
else if(strcmp(gargv[0], "echo") == 0)
{
if(gargc == 2)
{
// echo $?
// echo $PATH
// echo hello
if(gargv[1][0] == '$')
{
if(gargv[1][1] == '?')
{
printf("%d\n", lastcode);
lastcode = 0;
}
}
else
{
printf("%s\n", gargv[1]);
lastcode = 0;
}
}
else
{
lastcode = 3;
}
return true;
}
return false;
}
// 作为⼀个shell,获取环境变量应该从系统的配置来
// 我们今天就直接从⽗shell中获取环境变量
void InitEnv()
{
extern char **environ;
int index = 0;
while(environ[index])
{
genv[index] = (char*)malloc(strlen(environ[index])+1);
strncpy(genv[index], environ[index], strlen(environ[index])+1);
index++;
}
genv[index] = nullptr;
}
int main()
{
InitEnv();
char command_buffer[basesize];
while(true)
{
PrintCommandLine(); // 1. 命令⾏提⽰符
// command_buffer -> output
if( !GetCommandLine(command_buffer, basesize) )
// 2. 获取⽤⼾命令
{
continue;
}
//printf("%s\n", command_buffer);
//ls
//"ls -a -b -c -d"->"ls" "-a" "-b" "-c" "-d"
//"ls -a -b -c -d">hello.txt
//"ls -a -b -c -d">>hello.txt
//"ls -a -b -c -d"<hello.txt
ParseCommandLine(command_buffer, strlen(command_buffer)); // 3. 分析命令
if ( CheckAndExecBuiltCommand() )
{
continue;
}
ExecuteCommand();
// 4. 执⾏命令
}
return 0;
}
本章完。