基础IO_系统文件IO | 重定向【Linux】

基础IO_系统文件IO | 重定向【Linux】

文章目录

一、 理解"文件"

1、狭义理解

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
  • 磁盘是外设(即是输出设备也是输入设备)
  • 磁盘上的文件 本质是对文件的所有操作,都是对外设的输入和输出 简称 IO

2、广义理解

  • Linux 下一切皆文件(键盘、显示器、网卡、磁盘…… 这些都是抽象化的过程)

3、文件操作的归类认知

  • 对于 0KB 的空文件是占用磁盘空间的
  • 文件是文件属性(元数据)和文件内容的集合(文件 = 属性(元数据)+ 内容)
  • 所有的文件操作本质是文件内容操作和文件属性操作

4、系统角度

  • 对文件的操作本质是进程对文件的操作
  • 磁盘的管理者是操作系统
  • 文件的读写本质不是通过 C 语言 / C++ 的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

二、回顾C文件接口

1、hello.c打开文件

#include<stdio.h>intmain(){ FILE *fp =fopen("myfile","w");if(!fp){printf("fopen error!\n");}while(1);fclose(fp);return0;}

打开的myfile文件在哪个路径下?

xz@xzlinux:~$ ps ajx |head -1;ps ajx |grep catme PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 336450336595336595336435 pts/0 336595 R+ 10000:02 ./catme 336584336599336598336569 pts/1 336598 S+ 10000:00 grep--color=auto catme xz@xzlinux:~$ ls /proc/336595 -l total 0...... -r--r--r-- 1 xz xz 0 May 815:34 cpuset lrwxrwxrwx 1 xz xz 0 May 815:34 cwd -> /home/xz/z/IOleran -r-------- 1 xz xz 0 May 815:34 environ lrwxrwxrwx 1 xz xz 0 May 815:34 exe -> /home/xz/z/IOleran/catme dr-x------ 2 xz xz 4 May 815:34 fd ......

其中:

  • cwd:指向当前进程运行目录的一个符号链接。
  • exe:指向启动当前进程的可执行文件(完整路径)的符号链接。

打开文件,本质是进程打开,所以,进程知道自己在哪里,即便文件不带路径,进程也知道。由此OS就能知道要创建的文件放在哪里。

2、hello.c写文件

#include<stdio.h>#include<string.h>intmain(){ FILE *fp =fopen("myfile","w");if(!fp){printf("fopen error!\n");}constchar*msg ="hello bit!\n";int count =5;while(count--){fwrite(msg,strlen(msg),1, fp);}fclose(fp);return0;}

3、hello.c读文件

#include<stdio.h>#include<string.h>intmain(){ FILE *fp =fopen("myfile","r");if(!fp){printf("fopen error!\n");return1;}char buff[1024];constchar*msg ="hello bit!\n";while(1){ssize_t s =fread(buff,1,strlen(msg),fp);if(s >0){ buff[s]=0;printf("%s",buff);}if(feof(fp)){break;}}fclose(fp);return0;}

稍作修改,实现简单cat命令:

#include<stdio.h>#include<string.h>//简单实现cat命令intmain(int argc,char*argv[]){if(argc !=2){printf("argv error!\n");return1;} FILE *fp =fopen(argv[1],"r");if(!fp){printf("fopen error!\n");return2;}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);return0;}

4、输出信息到显示器,你有哪些方法

#include<stdio.h>#include<string.h>intmain(){constchar*msg ="hello fwrite\n";fwrite(msg,strlen(msg),1,stdout);printf("hello printf\n");fprintf(stdout,"hello fprintf\n");return0;}

5、stdin & stdout & stderr

#include<stdio.h>extern FILE *stdin;extern FILE *stdout;extern FILE *stderr;

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 po‐ sitioned at the beginning of the file. w+ Open for reading and writing. The file is created if it does not exist, other‐ wise it is truncated. The stream is positioned at the beginning of the file. a Open forappending(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. Output is always appended to the end of the file. POSIX is silent on what the initial read position is when using this mode. For glibc, the initial file position for reading is at the beginning of the file, but for An‐ droid/BSD/MacOS, the initial file position for reading is at the end of the file.

如上,是文件相关操作。还有 fseek ftell rewind 的函数,在C部分已经有所涉猎。

三、系统文件I/O

打开文件的方式不仅仅是fopen,ifstream等流式,语言层的方案,其实系统才是打开文件最底层的方案。不过,在学习系统文件IO之前,先要了解下如何给函数传递标志位,该方法在系统文件IO接口中会使用到:

1、一种传递标志位的方法

#include<stdio.h>#include<string.h>#defineONE0001//0000 0001#defineTWO0002//0000 0001#defineTHREE0004//0000 0001voidfunc(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");}intmain(){func(ONE);func(THREE);func(ONE | THREE);func(ONE | THREE | TWO);return0;}

操作文件,除了上面的C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问, 先来直接以系统代码的形式,实现和上面一模一样的代码:

2、hello.c 写文件:

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>intmain(){umask(0);int fd =open("myfile", O_WRONLY | O_CREAT,0644);if(fd <0){perror("open");return1;}int count =5;constchar* msg ="hello xz!\n";int len =strlen(msg);while(count--){write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址。//len: 本次读取,期望写入多少个字节的数据。 //返回值:实际写了多少字节数据}close(fd);return0;}

3、hello.c读文件

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>intmain(){int fd =open("myfile", O_RDONLY);if(fd <0){perror("open");return1;}constchar* msg ="hello bit!\n";char buf[1024];while(1){//ssize_tssize_t s =read(fd, buf,strlen(msg));//类比writeif(s >0){printf("%s", buf);}else{break;}}close(fd);return0;}

4、接口介绍

#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intopen(constchar*pathname,int flags);intopen(constchar*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文件相关接口。

5、open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用库函数

  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数
    (libc)。
  • open close read write lseek 都属于系统提供的接口,称之为系统调用接口

回忆一下我们讲操作系统概念时,画的一张图

在这里插入图片描述

系统调用接口和库函数的关系,一目了然。
所以,可以认为, f# 系列的函数,都是对系统调用的封装,方便二次开发。

6、文件描述符fd

  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数

6.1、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>intmain(){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));}return0;}
在这里插入图片描述

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

对于以上原理结论我们可通过内核源码验证:

首先要找到 task_struct 结构体在内核中为位置,地址为: /usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/sched.h(3.10.0-1160.71.1.el7.x86_64是内核版本,可使用 uname -a 自行查看服务器配置, 因为这个文件夹只有一个,所以也不用刻意去分辨,内核版本其实也随意)

  • 要查看内容可直接用vscode在windows下打开内核源代码
  • 相关结构体所在位置

struct task_struct/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/sched.h
struct files_struct/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fdtable.h
struct file/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h

在这里插入图片描述

6.2、文件描述符的分配规则

直接看代码:

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>intmain(){int fd =open("myfile", O_RDONLY);if(fd <0){perror("open");return1;}printf("fd: %d\n", fd);close(fd);return0;}

输出发现是 fd: 3
关闭0或者2,再看

#include<stdio.h>#include<sys/types.h>#inc lude <sys/stat.h>#include<fcntl.h>intmain(){close(0);//close(2);int fd =open("myfile", O_RDONLY);if(fd <0){perror("open");return1;}printf("fd: %d\n", fd);close(fd);return0;}

发现是结果是: fd: 0 或者 fd: 2 ,可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

6.3、重定向

那如果关闭1呢?看代码:

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<stdlib.h>intmain(){close(1);int fd =open("myfile", O_WRONLY|O_CREAT,00644);if(fd <0){perror("open");return1;}printf("fd: %d\n", fd);fflush(stdout);close(fd);exit(0);}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有: > , >> , <

那重定向的本质是什么呢?

6.4、使用 dup2 系统调用

函数原型如下:

#include<unistd.h>intdup2(int oldfd,int newfd);

示例代码

#include<stdio.h>#include<unistd.h>#include<fcntl.h>intmain(){int fd =open("./log", O_CREAT | O_RDWR);if(fd <0){perror("open");return1;}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);}return0;}

printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

6.5、在minishell中添加重定向功能

重定向myshell—https://gitee.com/xiaozhi

Read more

人工智能、机器学习和深度学习,其实不是一回事

人工智能、机器学习和深度学习,其实不是一回事

一、人工智能、机器学习与深度学习的真正区别 在当今科技领域,我们经常听到人工智能、机器学习和深度学习这三个词。它们虽然相关,但含义不同。 1.1 人工智能 人工智能是计算机科学的一个分支,旨在研究如何合成与分析能够像人一样行动的计算主体。简单来说,AI 的目标是利用计算机来模拟甚至替代人类大脑的功能。 一个理想的 AI 系统通常具备以下特征:像人一样思考、像人一样行动、理性地思考与行动。 1.2 机器学习 机器学习是实现人工智能的一种途径。它的核心定义是:赋予计算机在没有被显式编程的情况下进行学习的能力。 与传统的基于规则的编程不同,机器学习不依赖程序员手写每一条逻辑指令,而是通过算法让机器从大量数据中寻找规律,从而对新的数据产生预测或判断。 1.3 深度学习 深度学习是机器学习的一种特殊方法,也称为深度神经网络。它受人类大脑结构的启发,通过设计多层的神经元网络结构,来模拟万事万物的特征表示。 1.4 三者之间的层级关系 厘清这三者的关系对于初学者至关重要。人工智能 AI是最宏大的概念,包含了所有让机器变聪明的技术。机器学习 ML是 AI

By Ne0inhk
被问爆的Agent实战:从0到1搭建可落地AI智能体

被问爆的Agent实战:从0到1搭建可落地AI智能体

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 文章目录: * 【前言】 * 一、先搞懂:2026年爆火的AI Agent,到底是什么? * 1.1 Agent的核心定义 * 1.2 Agent的4大核心能力 * 1.3 2026年Agent的3个热门落地场景 * 二、框架选型:2026年6大主流Agent框架,新手该怎么选? * 三、实战环节:从0到1搭建可落地的“邮件处理Agent”(全程代码+步骤) * 3.1 实战准备:环境搭建(10分钟搞定) * 3.1.1 安装Python环境 * 3.1.2 创建虚拟环境(避免依赖冲突) * 3.

By Ne0inhk
2026年 Trae 收费模式改变 —— AI 编程“免费午餐”终结后的生存法则

2026年 Trae 收费模式改变 —— AI 编程“免费午餐”终结后的生存法则

关键词:Trae, Cursor, AI 编程成本, Token 计费, Agent 模式, 职业转型 大家好,我是飞哥!👋 2026年,AI编辑器Trae 也将收费模式改为按 Token 收费。 有些开发者开始动摇:“AI 编辑器越来越贵,是不是应该放弃使用,回归纯手写代码?” 对于用户来说,这无疑是一次涨价。但在飞哥看来,这次涨价背后释放了两个非常关键的信号: 1. AI 技术已进入稳定成熟期: 厂商不再需要通过“免费/低价补贴”来换取用户数据进行模型迭代。产品已经足够成熟,有底气接受市场真实定价的检验。 2. 倒逼用户进化,优胜劣汰: 涨价是一道筛子。它在要求用户大幅提升自己的 AI 使用水平(如 Prompt 技巧、Context 管理)。 * 低级使用者(只会问“怎么写代码”

By Ne0inhk

OpenClaw 控制你的 Mac 和 Windows 电脑,支持 SKill 的 GitHub 神器。

逛 GitHub 的时候,发现一个叫 TuriX-CUA 的开源项目。这是一个 Computer-Use Agent,电脑使用智能体框架。 它可以让 AI 大模型可以像人类一样,直接在桌面电脑上看屏幕 + 动手操作。 完成跨应用的复杂任务,而不是只在对话框里输出文字。 它不像传统 RPA 或基于 API 的集成方式,用如果人能点到的地方,TuriX 也能点的方式,实现跨应用自动化。 通过自然语言描述任务,AI 自动规划并执行,操纵的应用不提供 API 也没事儿。 而且,现在有专门的 Skill,能让你的 OpenClaw 或 Claude Code 使用TuriX-CUA。 目前在 Skill 广场中,Computer Use Agent 里排最高: 01 开源项目简介

By Ne0inhk