1.文件操作函数
1-1.理解文件
1-1-1狭义理解:
• ⽂件在磁盘⾥
• 磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的
• 磁盘是外设(即是输出设备也是输⼊设备)
• 磁盘上的⽂件本质是对⽂件的所有操作,都是对外设的输⼊和输出简称 IO
1-1-2广义理解
Linux下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘......这些都是抽象化的过程)
1-1-3文件操作的认知
(1) 文件大小为0,文件要在磁盘上占据空间,因为除了文件的内容,还有文件的属性
(2) 文件 = 内容 + 属性, 存取,操作,围绕内容+属性展开。(3) 访问文件,需要先打开文件!进程打开的文件,读文件的操作,本质是:进程读文件的操作
(4) 真正管理文件的是操作系统,通过文件相关的系统调用接口来实现(封装底层OS的文件系统调用),操作系统是硬件,磁盘的管理者。
(5) fopen,fclose...库
封装了底层OS的文件系统,操作系统西药把打开的文件管理起来,先描述再组织。
文件分为
1.内存级(被打开)文件
2. 磁盘级文件
当前进程的cwd是什么?
当前运行程序所在的目录
1-2文件操作函数
1-2-1.写入文件
文件的打开和写入:
(1)
snprintf是 C 标准库中的一个函数,用于将格式化的数据写入字符串。与sprintf类似,但snprintf允许你指定目标缓冲区的大小,从而防止缓冲区溢出。这在编写安全代码时非常重要。
cppint snprintf(char *str, size_t size, const char *format, ...);
str:目标字符串缓冲区。size:缓冲区的大小,包括终止字符\0。format:格式字符串,类似于printf的格式字符串。...:可变参数列表,表示要插入到格式字符串中的值- 成功时,返回写入字符串(不包括终止字符
\0)的字符数。- 如果返回值大于或等于
size,表示缓冲区不足以容纳整个格式化的字符串,字符串会被截断。
打开文件log.txt,然后向里面进行写入。


1-2-2.读文件fread
文件结束的判断。feof(fp) 判断文件是否读完了。
读文件fread代码。


buffer,
sizeof(buffer) - 1:读取127个
1:读取一个buffer
fp:文件描述符

如上:./myfile log.txt,
argc = 2,argv[1] = log.txt
1-2-3.输出信息到显示器的方式

C默认打开三个输入输出流文件, 所以就可以直接用。
stdin,标准你输入, stdout,标准输出, stderr,标准错误
问题:为什么程序要帮我们默认打开这三个文件?
程序是数据处理的,打开三个默认的, 给程序提供默认的数据源和数据结果, 程序不打开,那就需要我们自己打开,麻烦。我们可以关掉。
1-2-4.打开文件 方式
以读的方式打开:

1.w:
Truncate:默认要把文件先清空。
与 > log.txt :输出重定向一致, 默认重定向就是以写的方式打开文件
2.a:
追加方式打开文件
本质:不清空文件,追加写入, 与 >> 一致
指针移动函数
fseek:

ftell:

写字符串, 不要写\0,文件里就会多\0,文件打开就会乱码,如果需要写入就需要特殊处理, \0是C语言的规矩, 和文件没关系。
1-2-5:创建一个文件
(1)位图传标记位
cpp
#include <stdio.h>
#define ONE_FLAG (1<<0) // 0000 0000 0000...0000 0001
#define TWO_FLAG (1<<1) // 0000 0000 0000...0000 0010
#define THREE_FLAG (1<<2) // 0000 0000 0000...0000 0100
#define FOUR_FLAG (1<<3) // 0000 0000 0000...0000 1000
void Print(int flags)
{
if(flags & ONE_FLAG)
{
printf("One!\n");
}
if(flags & TWO_FLAG)
{
printf("Two\n");
}
if(flags & THREE_FLAG)
{
printf("Three\n");
}
if(flags & FOUR_FLAG)
{
printf("Four\n");
}
}
int main()
{
Print(ONE_FLAG);
printf("\n");
Print(ONE_FLAG | TWO_FLAG);
printf("\n");
Print(ONE_FLAG | TWO_FLAG | THREE_FLAG);
printf("\n");
Print(ONE_FLAG | TWO_FLAG | THREE_FLAG | FOUR_FLAG);
printf("\n");
Print(ONE_FLAG | FOUR_FLAG);
printf("\n");
return 0;
}
用位图来传标记位的方法,1个比特位传一个标记位。
(2)创建一个文件

文件名
标志位:其实本质就是宏
权限位:新建一个文件必须把权限位带上,默认权限0666
(3)代码:

创建一个文件
O_CREATE:创建
O_WRONLY:只读只写
0666:保证指定权限,不然权限都是乱的
实际上是664,umask权限掩码:会屏蔽一定的权限
加上umask(0),使得最后权限展示不受系统影响,显示就是666了
1-2-5.关闭文件:Close + 向文件里面进行写入: write

open
close(关闭文件)
write(int fd,文件描述符号,fd的返回值,const void *buf写入的buf,size_t count,大小):
向文件进行写入,不能写入\0,,所以不需要+1
1-2-6.文件覆盖写和清空写。

为什么没有清空?
只告诉要创建和写入,没告诉要清空。(没有在创建文件的时候加上O_TRUNC),加上了就会清空了。
O_RDONLY,:创建
O_WRONLY:写入
O_TRUNC: 清空写入
O_APPPEND:追加写入
清空写入和追加写入:

1-2-7文本写入和二进制写入
(1)write可以进行文本写入,也可以进行二进制写入:

(2)系统不关心是文本写入还是二进制写入,所以是void*类型
(3)文本写入vs二进制写入: 代码验证:

二进制写入 --- int a = 12345678,实际上是写入a,以二进制的形式写入,展现出来的是乱码
文本写入 --- a写入到buffer,buffer写入文件,显示1234567
结论:
系统不关心我的写入方式---随便写
fread - 二进制写入
fputc -文本写入
文本写入和二进制写入是语言层的概念
1-2-8.读文件read:从指定的文件描述符去读

结果展示:

推论:由于在OS层面只认文件描述符,我们可以预测,FILE是一个结构体,里面一定封装了文件描述符

2.文件描述符
2-1 代码: 打印stdin,stdout,stderr---fileno:filenum,文件描述符

打出: 0 1 2
理解fd是什么东西?
fd = 3;
n
0 1 2 去哪里了呢?
0 1 2 叫做 标准输入,标准输出,标准错误
FILE:是什么呢?
是c语言提供的一个结构体。typedef XXX{ ;;;;;;}FILE
在操作系统接口层面上,只认fd,即文件描述符
FILE里面一定封装了文件描述符。
fileno->filenum
2-2 跨平台:语言层为什么要做库语言级别的封装呢?

把不同平台直接的差异封装到库lib.so里面, C++和Java对里面包含市面上大多数语言(Windows,Linux,MAC)的封装,调用哪个语言,就把对应的语言的系统调用拿出来用,(裁剪进行条件编译)。
增加语言层的可移植性。
平台不一样,导致不可一致性,因为OS不一样,接口不一样,如C++把所有平台都实现一份(用底层进行封装),用户不用关心系统调用,所以语言层的代码就具有可移植性。
对外部表示的语言接口是一致的。
语言为什么要增加可移植性?
本质是为了让更多人用,不让自己被淘汰。
2-3 什么是文件描述符

struct file 表示被打开的文件, 对文件的管理就变成了对struct file链表的增删查改。
文件打开 -- 内核创建struct file
内容加载到文件缓冲区, 属性初始化到struct file
系统初始化,创建一个文件描述符表:struct files_struct
里面包含一个对象, struct file* fd_array[]指向不同的被打开的文件。
文件描述符的本质就是数组下标。
2-4文件描述符表:struct files_struct
文件描述符的本质就是文件描述符表的数组下标

read调用:
系统调用read函数,通过fd找到对应的struct file,再找到对应的文件缓冲区,从磁盘获得数据弄到缓冲区上, 文件缓冲区再拷贝数据到buffer上,我们就读到了。
对文件内容做任何操作:
都必须把文件加载到文件缓冲区上,也就是磁盘到内存的拷贝。
2-5重定向

重定向的原理:
文件描述符的分配原则:最小的,没有被使用的,作为新的fd给用户。
可以把标准输入,输出,错误关掉: close(0) --fd = 0;
close(2) -- fd = 2;
来个例子:
1.close(1) -- 把标准输出关了。
2.创建一个log.txt文件, 把fd = 1给log.txt
3.printf只能通过文件描述符1打出来。关掉stdout,再向fd = 1里面进行写入,变成向log.txt的写入。找不到stdout.
结果是打到了log.txt里,这就是重定向的原理。
2-6 dup操作:更改指针描述符的指针指向

dup2( int oldfd,int newfd):让新的newfd变成就得oldfd,也就是让newfd变成oldfd得拷贝。
newfd 和 oldfd一起指向oldfd.
2-7: 输入重定向:
dup(fd, 0):将0指向fd, 关闭fd, 默认输入的时候就会从fd里面获取数据
本来是从标准输入0读取,现在从打开的文件读了

重定向原理:
输出重定向: >
输入重定向: <
追加重定向: >>
3. 完善一点的自定的shell:增加重定向
< >> >
输出重定向:清除 >
输入重定向 <
追加重定向:追加 >>
在自定义shell里面实现重定向
实现:重定向分析,
左半部分:命令
右半部分:待执行文件
>>追加重定向
重定向:不能让父进程重定向,让子进程进行重定向不会影响父进程
子进程才能够检测重定向情况
void RedirCheck(char cmd[])重定向分析,分类处理
Execute:新增对重定向的情况的处理
cpp#include <iostream> #include <ctype.h> #include <cstdio> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <cstring> #include <unordered_map> #include <sys/stat.h> #include <fcntl.h> #define COMMAND_SIZE 1024 #define FORMAT "[%s@%s %s]# " // 下面是shell定义的全局数据 // 1. 命令行参数表 #define MAXARGC 128 char *g_argv[MAXARGC]; int g_argc = 0; // 2. 环境变量表 #define MAX_ENVS 100 char *g_env[MAX_ENVS]; int g_envs = 0; // 3. 别名映射表 std::unordered_map<std::string, std::string> alias_list; // 4. 关于重定向,我们关心的内容 #define NONE_REDIR 0 #define INPUT_REDIR 1 #define OUTPUT_REDIR 2 #define APPEND_REDIR 3 //#define TrimSpace(CMD,index) do{ // while(isspace(*(CMD+index))) // { // index++; // } //}while(0) int redir = NONE_REDIR; std::string filename; // for test char cwd[1024]; char cwdenv[1024]; // last exit code int lastcode = 0; const char *GetUserName() { const char *name = getenv("USER"); return name == NULL ? "None" : name; } const char *GetHostName() { const char *hostname = getenv("HOSTNAME"); return hostname == NULL ? "None" : hostname; } const char *GetPwd() { //const char *pwd = getenv("PWD"); const char *pwd = getcwd(cwd, sizeof(cwd)); if(pwd != NULL) { snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd); putenv(cwdenv); } return pwd == NULL ? "None" : pwd; } const char *GetHome() { const char *home = getenv("HOME"); return home == NULL ? "" : home; } void InitEnv() { extern char **environ; memset(g_env, 0, sizeof(g_env)); g_envs = 0; //本来要从配置文件来 //1. 获取环境变量 for(int i = 0; environ[i]; i++) { // 1.1 申请空间 g_env[i] = (char*)malloc(strlen(environ[i])+1); strcpy(g_env[i], environ[i]); g_envs++; } g_env[g_envs++] = (char*)"HAHA=for_test"; //for_test g_env[g_envs] = NULL; //2. 导成环境变量 for(int i = 0; g_env[i]; i++) { putenv(g_env[i]); } environ = g_env; } //command bool Cd() { // cd argc = 1 if(g_argc == 1) { std::string home = GetHome(); if(home.empty()) return true; chdir(home.c_str()); } else { std::string where = g_argv[1]; // cd - / cd ~ if(where == "-") { // Todu } else if(where == "~") { // Todu } else { chdir(where.c_str()); } } return true; } void Echo() { if(g_argc == 2) { // echo "hello world" // echo $? // echo $PATH std::string opt = g_argv[1]; if(opt == "$?") { std::cout << lastcode << std::endl; lastcode = 0; } else if(opt[0] == '$') { std::string env_name = opt.substr(1); const char *env_value = getenv(env_name.c_str()); if(env_value) std::cout << env_value << std::endl; } else { std::cout << opt << std::endl; } } } // / /a/b/c std::string DirName(const char *pwd) { #define SLASH "/" std::string dir = pwd; if(dir == SLASH) return SLASH; auto pos = dir.rfind(SLASH); if(pos == std::string::npos) return "BUG?"; return dir.substr(pos+1); } void MakeCommandLine(char cmd_prompt[], int size) { snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str()); //snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), GetPwd()); } void PrintCommandPrompt() { char prompt[COMMAND_SIZE]; MakeCommandLine(prompt, sizeof(prompt)); printf("%s", prompt); fflush(stdout); } bool GetCommandLine(char *out, int size) { // ls -a -l => "ls -a -l\n" 字符串 char *c = fgets(out, size, stdin); if(c == NULL) return false; out[strlen(out)-1] = 0; // 清理\n if(strlen(out) == 0) return false; return true; } // 3. 命令行分析 "ls -a -l" -> "ls" "-a" "-l" bool CommandParse(char *commandline) { #define SEP " " g_argc = 0; // 命令行分析 "ls -a -l" -> "ls" "-a" "-l" g_argv[g_argc++] = strtok(commandline, SEP); while((bool)(g_argv[g_argc++] = strtok(nullptr, SEP))); g_argc--; return g_argc > 0 ? true:false; } void PrintArgv() { for(int i = 0; g_argv[i]; i++) { printf("argv[%d]->%s\n", i, g_argv[i]); } printf("argc: %d\n", g_argc); } bool CheckAndExecBuiltin() { //如果内键命令做重定向,更改shell的标准输入,输出,错误 std::string cmd = g_argv[0]; if(cmd == "cd") { Cd(); return true; } else if(cmd == "echo") { Echo(); return true; } else if(cmd == "export") { } else if(cmd == "alias") { // std::string nickname = g_argv[1]; // alias_list.insert(k, v); } return false; } int Execute() { pid_t id = fork(); if(id == 0) { int fd = -1; // 子进程检测重定向情况 if(redir == INPUT_REDIR) { fd = open(filename.c_str(), O_RDONLY); if(fd < 0) exit(1); dup2(fd,0); close(fd); } else if(redir == OUTPUT_REDIR) { fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666); if(fd < 0) exit(2); dup2(fd, 1); close(fd); } else if(redir == APPEND_REDIR) { fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666); if(fd < 0) exit(2); dup2(fd, 1); close(fd); } else {} // 进程替换,会影响重定向的结果吗?不影响 //child execvp(g_argv[0], g_argv); exit(1); } int status = 0; // father pid_t rid = waitpid(id, &status, 0); if(rid > 0) { lastcode = WEXITSTATUS(status); } return 0; } void TrimSpace(char cmd[], int &end) { while(isspace(cmd[end])) { end++; } } void RedirCheck(char cmd[]) { redir = NONE_REDIR; filename.clear(); int start = 0; int end = strlen(cmd)-1; //"ls -a -l >> file.txt" > >> < while(end > start) { if(cmd[end] == '<') { cmd[end++] = 0; TrimSpace(cmd, end); redir = INPUT_REDIR; filename = cmd+end; break; } else if(cmd[end] == '>') { if(cmd[end-1] == '>') { //>> cmd[end-1] = 0; redir = APPEND_REDIR; } else { //> redir = OUTPUT_REDIR; } cmd[end++] = 0; TrimSpace(cmd, end); filename = cmd+end; break; } else { end--; } } } int main() { // shell 启动的时候,从系统中获取环境变量 // 我们的环境变量信息应该从父shell统一来 InitEnv(); while(true) { // 1. 输出命令行提示符 PrintCommandPrompt(); // 2. 获取用户输入的命令 char commandline[COMMAND_SIZE]; if(!GetCommandLine(commandline, sizeof(commandline))) continue; // 3. 重定向分析 "ls -a -l > file.txt" -> "ls -a -l" "file.txt" -> 判定重定向方式 RedirCheck(commandline); // printf("redir: %d, filename: %s\n", redir, filename.c_str()); // 4. 命令行分析 "ls -a -l" -> "ls" "-a" "-l" if(!CommandParse(commandline)) continue; //PrintArgv(); // 检测别名 // 5. 检测并处理内键命令 if(CheckAndExecBuiltin()) continue; // 6. 执行命令 Execute(); } //cleanup(); return 0; }




