什么是当前路径 ?

当前进程所在 的 路径

当我们实现shell的时候 cd pwd命令都是子进程运行而不是父进程

不需要子进程来执行 让shell 自己来执行的命令 ---内建 / 内置 命令

  • 空文件,也在磁盘占据空间

  • 文件=内容+属性

  • 文件操作 = 内容操作 属性操作

  • 操作文件使用文件路径+文件名称

  • 一个文件没有被打开不可以文件访问必须打开后才能访问

  • 对文件的操作是进程对文件的操作

C语言文件操作

#include <stdio.h>
#include <string.h>
​
int main(){
    FILE * fp = fopen("myfile","w");
    if(!fp){
            printf("fopen error");
    }
    const char * msg = "hello world\n";
    int count = 5;
    while(count --){
        fwrite(msg,strlen(msg),1,fp);
    }
    fclose(fp);  //关闭文件
    return 0;
}

C语言在进行文件操作的时候 w 写入 可以自动新建文件但是每次w都会清空文件内容重新进行写入

fwrite(msg,strlen(msg),1,fp);

msg 是指向要写入文件数据字符串指针

strlen(msg) 字符串的长度

1 代表写入数据项的数量

fp 要写入文件的指针

#include <stdio.h>
#include <string.h>
​
int main() {
    // 尝试打开名为"myfile"的文件进行读取
    FILE *fp = fopen("myfile", "r");
    // 如果文件打开失败,打印错误信息
    if (!fp) {
        printf("fopen error!\n");
    }
​
    // 定义一个字符数组用于存储从文件中读取的数据
    char buf[1024];
    // 定义一个字符串常量
    const char *msg = "hello world\n";
​
    // 循环读取文件内容
    while (1) {
        // 使用fread函数从文件中读取数据
        // 读取的字节数是msg字符串的长度
        size_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;
}
  • fread 返回成功读取的元素数量。如果返回值小于 count,则表示读取操作可能遇到了文件末尾(EOF)或发生了错误。

  • 如果返回值为 0,可能是因为到达文件末尾,或者发生了读取错误。

文件打开方式

"r":只读,文件必须存在。

"w":只写,文件会被截断(清空内容),不存在则创建。

"r+":读写,文件必须存在。

"w+":读写,文件会被截断,不存在则创建。

"a":追加,写操作在文件末尾,不存在则创建。

"a+":读追加,写操作在文件末尾,读操作任意位置,不存在则创建。

上面都是C语言库提供的接口,各种语言都有 其实是对操作系统库的封装

使用系统接口实现文件读写

写文件

#include <stdio.h>
#include <string.h>
#include <unistd.h>  // 提供了对POSIX操作系统API的访问
#include <sys/types.h> // 包含数据类型定义
#include <sys/stat.h>  // 包含文件状态标志定义
#include <fcntl.h>    // 包含文件控制选项定义
​
int main() {
    // 设置umask,umask用于设置文件权限掩码,这里设置为0表示不屏蔽任何权限
    umask(0);
​
    // 使用open函数以写入和创建模式打开文件"myfile"
    // O_WRONLY表示只写模式,O_CREAT表示如果文件不存在则创建
    // 0644是文件的权限设置,表示文件所有者有读写权限,组用户和其他用户有读权限
    int fd = open("myfile", O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
        // 如果打开文件失败,打印错误信息并退出程序
        perror("open");
        return 1;
    }
​
    // 定义一个整数变量count,用于控制写入次数
    int count = 5;
​
    // 定义一个字符串常量msg,包含要写入文件的内容
    const char *msg = "hello world!\n";
    // 获取字符串msg的长度212   `    
    int len = strlen(msg);
​
    // 循环,根据count的值写入msg到文件
    while (count--) {
        write(fd, msg, len);  // 使用write函数将msg写入文件
    }
​
    // 关闭文件描述符fd
    close(fd);
    return 0;  // 程序正常退出
}
void perror(const char *s);

调用 perror 时,它会打印出类似 "s: error message" 的文本,其中 "error message" 是由当前errno值决定的错误描述。

open 的返回值

  • 成功: open 成功时返回一个非负整数,即文件描述符。

  • 失败: 如果操作失败,open 返回 -1

读文件

#include <stdio.h>      // 包含标准输入输出函数
#include <sys/types.h>  // 包含数据类型定义
#include <unistd.h>     // 提供了对POSIX操作系统API的访问
#include <string.h>     // 包含字符串处理函数
#include <sys/stat.h>   // 包含文件状态标志定义
#include <fcntl.h>      // 包含文件控制选项定义
​
int main() {
    // 尝试以只读模式打开名为"myfile"的文件
    int fd = open("myfile", O_RDONLY);
    
    // 如果打开文件失败,打印错误信息并退出程序
    if (fd < 0) {
        perror("open");
        return 1;
    }
​
    // 定义一个字符串常量msg,包含要与读取的数据进行比较的文本
    const char *msg = "hello world\n";
​
    // 定义一个字符数组用于存储从文件中读取的数据
    char buf[1024];
​
    // 循环读取文件内容
    while (1) {
        // 使用read函数从文件描述符fd读取数据到buf
        // 读取的字节数是msg字符串的长度
        ssize_t s = read(fd, buf, strlen(msg));
        
        // 如果读取到数据,打印出来
        if (s > 0) {
            printf("%s", buf);
        } else {
            // 如果读取失败或到达文件末尾,跳出循环
            break;
        }
    }
    // 关闭文件描述符fd
    close(fd);
    return 0;  // 程序正常退出
}

chen@chen  ~/test_linux/iO_test  gcc -o test test.c chen@chen  ~/test_linux/iO_test  ./test hello world!,V hello world,V! hello worl,Vd! hello wor,Vld! hello wo,Vrld! ello wo,V% \

输入乱码解决方法

 buf[s] = '\0';  // 添加空字符以确保字符串正确终止


open 函数是 POSIX 标准中用于打开文件的函数,其原型如下:

int open(const char *pathname, int flags, mode_t mode);
  • pathname: 要打开或创建的文件的路径。

  • flags: 文件打开的标志,用于指定文件的访问模式和其他选项。

  • mode: 当创建新文件时,用于设置文件的访问权限(仅在 O_CREAT 标志被设置时使用)。

下面是一些常用的 flags 参数解释:

  1. O_RDONLY: 只读方式打开文件。文件必须存在。

  2. O_WRONLY: 只写方式打开文件。如果文件不存在,会创建一个新文件。

  3. O_RDWR: 读写方式打开文件。文件必须存在。

这三个标志(O_RDONLY、O_WRONLY、O_RDWR)中必须指定一个,且只能指定一个,用于确定文件的访问模式。

  1. O_CREAT: 若文件不存在,则创建它。通常与 O_WRONLYO_RDWR 结合使用。需要指定 mode 参数来设置新文件的访问权限。

  2. O_APPEND: 追加模式。所有写操作都会从文件末尾开始,不会覆盖现有内容。

除了上述标志,还有其他标志如 O_TRUNC(截断文件,使其大小为0)、O_EXCL(与 O_CREAT 一起使用,确保文件不存在时才创建)、O_SYNC(同步写入,确保数据写入磁盘)等,用于控制文件打开的行为。

使用 open 函数时,可以组合使用这些标志来满足不同的文件操作需求。例如,open("file.txt", O_WRONLY | O_CREAT, 0644) 表示以只写模式打开文件,如果文件不存在则创建它,文件权限设置为只读和写入权限给所有者,以及读权限给组和其他用户。

文件描述符号

linux文件描述符号 fd 是 open函数的返回值 进程的默认情况是三个默认打开的文件描述符号 分别是 0 1 2 而 我们添加的文件从上往下依次分配文件标识符号

关闭0 1 2 则也会 从上往下分配文件标识符号

其中0 1 2 代表

  • 0 标准输入 键盘

  • 1 标准输出 显示器

  • 2 标准错误 显示器

打开文件要创建相应的数据结构用来描述目标文件.所以使用file 结构体代表一个已经打开的对象,必须让进程和文件关联起来.每个进程都有一个指针指向file_struct 表,每个元素都是指向打开文件的指针,本质上文件描述符号就是数组下表 在file_struct 表中就可以找到相应的文件

当我们关闭 close(1)的时候本来应该输出到屏幕的内容输出到显示器上面

fd = 1 为 输出重定向 常见的输出重定向有 >> > <

重定向的本质是:上层调用的fd不变 在内核中更改对应的struct_file*的地址

dup2函数

dup2 是 将当前调用dup2的进程所对应的0123中文件的内容进行拷贝

dup2(oldfile,newfile) 将 输出的内容存储到文件中 dup2(fd,1);

  • oldfd:指定要复制的原始文件描述符。

  • newfd:指定目标文件描述符。

输出重定向

#include <stdio.h>
#include<sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main() {
    //int fd = open("myfile", O_WRONLY | O_CREAT | O_TRUNC, 0666); //写入
    int fd = open("myfile", O_WRONLY | O_CREAT | O_APPEND, 0666);  //追加
    if (fd < 0) {  // 打开文件失败
        perror("open");
        return 1;
    }
    dup2(fd, 1);  // 将标准输出重定向到文件
    printf("%s\n", "asdasdadad");
    fflush(stdout);  // 确保输出被写入文件
​
    close(fd);  // 关闭文件描述符
    return 0;
}

输入重定向

#include <stdio.h>
#include<sys/types.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
​
int main() {
    char buff[1024];
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("Error opening file");
        return 1;
    }
    int stdin_copy =  dup2(fd,0);
      while (fgets(buff, sizeof(buff), stdin) != NULL) {
        printf("%s", buff);
    }
    // 恢复原来的标准输入文件描述符
    dup2(stdin_copy, 0);
    close(stdin_copy);
    // 关闭文件描述符
    close(fd);
}

shell中添加重定向内容

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
​
​
//#define _GNU_SOURCE
​
#define MAX_CMD 1024
#define OPT 64
#define SEP " "
​
​
#define NeoRedir    0
#define OutputRedir 1 
#define AppenfRedir 2 
#define InputRedir  3
​
​
int redir = NeoRedir;  // 初始化重定向为无 
char * filename =  NULL;
​
char cwd[MAX_CMD];
char enval[MAX_CMD];
int lastcode = 0;
​
​
​
//#define SkipSpace(pos) do {  while(*pos && isspace(*pos)){ pos++; } } while (0); 
#define SkipSpace(pos) do { while (*pos && isspace(*pos)) pos++; } while (0);
​
​
​
​
char * homePath(){
​
    char * home = getenv("HOME");
    if(home){
      return home;
    } 
    return (char *)".";
}
​
const char * getUserName(){
    const char *name = getenv("USER");
    if(name){
        return name;
    }
    return "what can I say ?";
}
​
const char * getHostName(){
    
    const char * hostname = getenv("LOGNAME");
    if(hostname){
        return  hostname;
    }
    return "NONE";
}
​
const char * getCmd(){
    const char * cmd = getenv("PWD");
    if(cmd){
        return  cmd;
    }
    return "无家可归";
}
​
int getUserCommand(char * commond,int nums){
    
    printf("喵喵喵 = [%s@%s|%s]$",getUserName(),getHostName(),getCmd());
    
    char *r = fgets(commond,nums,stdin);
    if(r == NULL)return -1;
    int len = strlen(commond);
    if (len > 0 && commond[len-1] == '\n') {
        commond[len-1] = 0;
    }
    return strlen(commond);
}
​
void checkRedir(char userCommond[],int len){
​
    char * end = userCommond + len - 1;
    char * start = userCommond;
    while (start < end){
        if(*end == '>'){
            if(*(end-1) == '>'){
                *(end-1) = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = AppenfRedir;
                break;
            }
            else {
                *end = '\0';
                filename = end + 1;
                SkipSpace(filename);
                redir = OutputRedir;
                break;
            }
            
        }else if(* end == '<'){
              * end = '\0';
              filename = end + 1;
              SkipSpace(filename);
              redir = InputRedir;
              break;
        }else{
            * end --;
        }
​
    }
}
​
void commondSplit(char * in,char * out[]){
      int argc = 0;
      out[argc ++] = strtok(in,SEP);
//      while(out[argc ++] = strtok(NULL,SEP) != NULL);
        while ((out[argc] = strtok(NULL, SEP)) != NULL) {
            argc++;
        }  
#ifdef DEBUG
    for(int i =0;out[i];i ++){
        printf("%d:%s\n",i,out[i]);
    }
#endif /* ifdef DEBUG */
}
​
​
​
void cd(const char * path){
    chdir(path);  //改变当前目录
    char tmp[MAX_CMD];
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd, "PWD = %s",tmp);
    putenv(cwd);
}
​
​
​
​
int doBulidin(char *argv[]) {
    if (strcmp(argv[0], "cd") == 0) {
        char *path = NULL;
        if (argv[1] == NULL) {
            path = homePath();
        } else {
            path = argv[1];
        }
        cd(path);
        return 1;
    }/* else if (strcmp(argv[0], "export") == 0) {
        if (argv[1] == NULL) {
            return 1;
        }
        char *var = strtok(argv[1], "=");
        char *value = strtok(NULL, "=");
        if (var && value) {
            setenv(var, value, 1);
        }
        return 1;
    } */ else if (strcmp(argv[0], "echo") == 0) {
        if (argv[1] == NULL) {
            printf("\n"); // 没有参数打印换行
            return 1;
        }
        if (*(argv[1]) == '$' && strlen(argv[1]) > 1) {
            char *val = argv[1] + 1;
            if (strcmp(val, "?") == 0) {
                printf("%d\n", lastcode);
                lastcode = 0;
            } else {
                const char *enval = getenv(val);
                if (enval) {
                    printf("%s", enval);
                } else {
                    printf("\n");
                }
            }
        } else {
            printf("%s\n", argv[1]);
            return 1;
        }
    }
    return 0;
}
​
int execute(char * argv[]){
      pid_t id = fork();
      
      if(id < 0){
          return -1;
      }
      else if(id == 0){
          
          int fd  = 0;
          if(redir == InputRedir){
                fd = open(filename,O_RDONLY);
                
                dup2(fd,0);
          }
          else if(redir == OutputRedir){
                fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0666);
                dup2(fd,1);
          }
          else if(redir  == AppenfRedir){
                fd = open(filename, O_WRONLY | O_CREAT | O_APPEND,0666);
                dup2(fd,1);
          }
          if(redir != NeoRedir && fd != -1)close(fd);
          if(execvp(argv[0],argv) == -1){
              perror("RE");
          }
          exit(1);
      }
​
      else{
          int status = 0;
          pid_t rid = waitpid(id,&status,0);
          if(rid > 0){
              lastcode = WEXITSTATUS(status);
          } 
      }
​
      return 0;
}
​
​
void init(){
        redir = NeoRedir;  // 初始化重定向为无 
        filename =  NULL;
}
​
int main(){
​
    while(114514){
        
        init();
        char userCommond[MAX_CMD];
        char * argv[OPT]; // 存储用户命令
        int n = getUserCommand(userCommond,sizeof(userCommond));
        if(n <= 0){
            continue;         
        }
        checkRedir(userCommond,n);
        commondSplit(userCommond,argv);
        
        n = doBulidin(argv);
        
        if(n){continue;}
        execute(argv);
    } 
​
    return 0;
}

Linux 缓冲区

  • fwrite 是写入文件的函数 fwrite 是拷贝函数,将数据从进程拷贝到缓冲区或者外设中

缓冲区的刷新策略

  • 立即刷新 -- 无缓冲

  • 行刷新 -- 行缓存 | 显示器

  • 缓冲区满 -- 全缓冲 -- 磁盘文件

  • 特殊情况

    用户强制刷新

    进程退出 --- 缓冲区自动刷新

缓冲区在哪里 ? 是什么缓冲区?

缓冲区是在高级语言层面给我们提供的缓冲区

在stdout stdin stderr -> File * -> file结构体->fd 还有一个缓冲区

C语言手动模拟缓冲区

main.c
#include "myStdio.h"
#include <stdio.h>
​
int main()
{
    FILE_* fp = fopen_("./log.txt","w");
​
    if(fp == NULL)return 1;
​
    const char * msg = "Hello World\n";
    fwrite_(msg,strlen(msg),fp);
​
​
    fclose_(fp);
    
   // printf("test No Error");
    return 0;
}
myStdio.h
#pragma once 
#include <stdio.h>
​
#include <assert.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
​
​
#define SIZE 1024
#define SYNC_NOW   1 
#define SYNC_LINE  2 
#define SYNC_FULL  4 
​
typedef struct _FILE{
    int flags;  // 刷新方式
    int fileno;
    int cap;   //buffer的总容量
    int size;   //  buffer 的使用量
    char buffer[SIZE];
​
}FILE_;
​
​
FILE_ * fopen_(const char *path_name, const char * mode);
int fwrite_(const void * ptr,int num,FILE_ *fp);
void fclose_(FILE_ * fp);
void fflush_(FILE_ * fp);
​
myStdio.c
#include "myStdio.h"
#include <stdio.h>
​
FILE_ * fopen_(const char *path_name, const char * mode)
{
    int flags = 0;
    int defaultMode = 0666;
    if(strcmp(mode,"r") == 0)
    {
        flags |= O_RDONLY;
    }else if(strcmp(mode,"w") == 0)
    {
        flags |= (O_WRONLY | O_CREAT | O_TRUNC);
​
    }else if(strcmp(mode,"a") == 0)
    {
        flags |= (O_WRONLY|O_APPEND | O_CREAT);
    }else {
        // TODO
    }
    int fd = 0;
    // 按照特定的打开方式打开文件
    if(flags & O_RDONLY)
    {
        fd = open(path_name,flags);
    }
    else {    
        fd = open(path_name,flags,defaultMode);
    }
​
    if(fd < 0)
    {
        const char *err = strerror(errno);
        write(2,err,strlen(err));
        return NULL; // 打开文件失败会返回null
    }
​
    FILE_ *fp = malloc(sizeof(FILE_));
    assert("error");
    fp->flags = SYNC_LINE; //默认设置成为行刷新
    fp->fileno = fd;
    fp->cap = SIZE;
    fp->size = 0;
    // fp->buffer[0] = 0;
    memset(fp->buffer,0,SIZE);
    
    return fp;   // 打开文件返回 fp
}
int fwrite_(const void * ptr,int num,FILE_ *fp)
{
​
    // 写文件 将文件写入到了缓冲区
​
    //size 是曾经没有刷新出去的数据
    memcpy(fp->buffer + fp->size,ptr,num); 
    // 不考虑缓冲区溢出
    fp->size += num;
    // 是否需要刷新
    if(fp->flags & SYNC_NOW)
    {
        write(fp->fileno,fp->buffer,fp->size);
        fp->size = 0; // 清空缓冲区   惰性释放
        // 后面写入直接覆盖
    }else if(fp->flags & SYNC_FULL)
    { 
         if(fp->size == fp->cap)
        {
            write(fp->fileno,fp->buffer,fp->size);
            fp->size = 0;
        }
    }else if(fp->flags & SYNC_LINE){
        if(fp->buffer[fp->size - 1] == '\n')
        {
            write(fp->fileno,fp->buffer,fp->size);
            fp->size = 0;
        }
    }else {
​
​
    }
​
​
}
​
void fflush_(FILE_ * fp)
{
    // 刷新
    //
    if(fp->size > 0)
        write(fp->fileno,fp->buffer,fp->size);
    
    // Then
​
​
    return ;
}
​
void fclose_(FILE_ * fp)
{
    fflush_(fp);
    close(fp->fileno);
}