什么是当前路径 ?
当前进程所在 的 路径
当我们实现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
参数解释:
O_RDONLY: 只读方式打开文件。文件必须存在。
O_WRONLY: 只写方式打开文件。如果文件不存在,会创建一个新文件。
O_RDWR: 读写方式打开文件。文件必须存在。
这三个标志(O_RDONLY、O_WRONLY、O_RDWR)中必须指定一个,且只能指定一个,用于确定文件的访问模式。
O_CREAT: 若文件不存在,则创建它。通常与
O_WRONLY
或O_RDWR
结合使用。需要指定mode
参数来设置新文件的访问权限。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);
}