目錄
FILE *fopen(char* filename,char* mode);(文件名稱,打開方式)
size_t fread(char* buf,size_t block_size,size_t block_count,FILE* fp);(緩衝區,塊大小,塊個數,文件流指針);
size_t fwrite(char*data,size_t block_size,size_t block_count,FILE* fp);(數據首地址,塊大小,塊個數,文件流指針);
int fseek(FILE* fp,long offset,int whence);(將文件的讀寫指針從whence位置偏移offset個字節)- - - - -跳轉文件讀寫位置
int fclose(FILE* fp);- - - - - 關閉文件流指針,釋放資源
標準的IO接口(都是庫函數,而庫函數就是對系統調用接口的一層封裝)
int open(char* filename,int flag,mode_t mode);
ssize_t write(int fd,char* buf,size_t count);
ssize_t read(int fd,char* buf,size_t len);
off_t lseek(int fd,off_t offset,int whence);
int close(int fd);通過文件描述符關閉文件,釋放資源;
int dup2(int oldfd,int newfd);- - - - -描述重定向函數
基礎IO(文件的輸入輸出操作)
例:fopen/fwrite/fread/fseek/fclose stdin/stdout/stderr
FILE *fopen(char* filename,char* mode);(文件名稱,打開方式)
打開方式:*r(只讀); r+(讀寫);w(只寫);w+(讀寫);a(追加寫);a+(追加讀寫);b(二進制操作)。
追加寫:每次寫入數據總是寫入到文件末尾
r+的讀寫和w+的讀寫的區別:r+讀寫打開文件,若文件不存在則報錯;w+讀寫打開文件,若不存在則創建,若存在則清空原有內容。
a:不僅是追加寫,並且文件不存在會創建新文件
b:默認清空如果不指定b,則認爲文件是文本操作,加上b則認爲是二進制操作;區別在於:有時候一個特殊字符,只是一個字符但是佔據兩個字節的內存(例:讀取一個100字節大小的文件,文本操作最終讀取出來的數據,不一定是100個字節)
返回值:返回實際一個FILE*的文件流指針作爲文件的操作句柄;失敗則返回NULL;
size_t fread(char* buf,size_t block_size,size_t block_count,FILE* fp);(緩衝區,塊大小,塊個數,文件流指針);
size_t fwrite(char*data,size_t block_size,size_t block_count,FILE* fp);(數據首地址,塊大小,塊個數,文件流指針);
- 注意:fread/fwrite操作的數據實際大小=塊大小*塊個數;(例:塊大小爲10,塊個數爲2,則需要寫入/讀取20個字節的數據);
- 返回值:返回實際操作的塊個數;(例:讀取一個文件size爲10,count爲2,如果文件大小足夠則返回2,但是若文件大小隻有16個字節,則返回1,因爲第二塊沒有讀滿;fread如果讀到了文件末尾則會返回0)
- 如果讀取1000個字節,塊個數爲1,文件大小隻有512字節,雖然讀取了512個數據但是也會返回0
int fseek(FILE* fp,long offset,int whence);(將文件的讀寫指針從whence位置偏移offset個字節)- - - - -跳轉文件讀寫位置
int fclose(FILE* fp);- - - - - 關閉文件流指針,釋放資源
fread/fwrite的塊大小一般設定爲1,塊個數設爲想要操作的數據長度
fseek:文件沒有數據也可以跳轉到讀寫位置;
對文件數據進行字符串操作是時候要注意文件數據中\0這種數據
#include <stdio.h>
#include <string.h>
int main()
{
FILE *fp = NULL;
fp = fopen("./test.txt", "r+");
if (fp == NULL) {
perror("fopen error");//打印上一個系統調用接口的使用錯誤原因
return -1;
}
//fseek跳轉讀寫位置 SEEK_SET-從文件起始偏移 SEEK_CUR-從當前讀寫位置開偏移
//SEEK_END-從文件末尾偏移
fseek(fp, 10, SEEK_END); //將文件的讀寫位置偏移到末尾
//sizeof獲取的是一塊空間的大小 / strlen獲取的是字符串的長度遇到\0截止
char buf[] = "It's a fine day\0 today~~\n";
int ret = fwrite(buf, strlen(buf), 1, fp);//(數據,塊大小,塊個數,流指針);
if (ret == 0) {
perror("fwrite error");
return -1;
}
printf("write ret:%d\n", ret);
fseek(fp, 0, SEEK_SET);
char tmp[1024] = {0};
ret = fread(tmp, 1, 1023, fp);//在塊大小爲1情況下不會出現讀取到數據依然返回0的情況
if (ret == 0) {
printf("have no data or error\n");
}
printf("ret:%d-[%s]\n", ret, tmp);
fclose(fp);
return 0;
}
標準的IO接口(都是庫函數,而庫函數就是對系統調用接口的一層封裝)
系統調用IO接口的學習:open\read\write\seek\close
int open(char* filename,int flag,mode_t mode);
filename:要打開的文件名稱;
flag:選項參數,文件的打開方式,必選項/可選項
- 必選項(只能選擇其一):O_WRONLY(只寫);O_RDWR(讀寫);O_TRUNC(打開文件的同時清空原有內容);O_APPEND(追加寫,總是將數據寫入到文件末尾)。
- mode:權限,如果使用了O_CREAT有可能創建新文件,就一定要指定文件權限,八進制數字形式。
- 返回值:一個非負數,文件描述符,文件的操作句柄;失敗返回-1;
ssize_t write(int fd,char* buf,size_t count);
fd:open返回的文件描述符,文件操作句柄,通過這個fd指定要往那個文件寫入數據
buf:要寫入文件的數據的空間首地址
count:要寫入的數據大小
返回值:返回實際寫入文件的數據字節長度;失敗則返回-1
ssize_t read(int fd,char* buf,size_t len);
fd:open返回的文件描述符,文件的操作句柄;
buf:從文件中讀取數據放到哪個緩衝區中的首地址;
len:想要讀取的數據長度,注意這個len不能大於緩衝區的大小;
返回值:返回的是實際讀取到的數據字節長度,錯誤返回-1;
off_t lseek(int fd,off_t offset,int whence);
fd:open返回的文件描述符;
offset:偏移量;
whence:從哪開始偏移;SEEK_SET(文件起始位置),SEEK_CUR(文件當前讀寫位置),SEEK_END(文件末尾);
返回值:成功返回當前位置相對於起始位置的偏移量;失敗返回-1;
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fd = -1;
umask(0);//將當前進程的文件創建權限掩碼設置位0-僅當前進程有效
fd = open("./test.txt", O_RDWR|O_CREAT, 0777);
if (fd < 0) {
perror("open error");
return -1;
}
lseek(fd, 10, SEEK_END);// 目的讀寫位置跳轉到文件末尾
char ptr[1024] = "hello world~\n";
int ret = write(fd, ptr, strlen(ptr));
if (ret < 0) {
perror("write error");
return -1;
}
printf("ret:%d\n", ret);
lseek(fd, 0, SEEK_SET);// 跳轉到文件的起始位置
char buf[1024] = {0};
ret = read(fd, buf, 1024);
if (ret < 0) {
perror("read error");
return -1;
}
printf("ret:%d-[%s]\n", ret, buf);
close(fd);
return 0;
}
int close(int fd);通過文件描述符關閉文件,釋放資源;
爲什麼打開一個文件,如果不操作一定要關閉,釋放資源?
答:因爲文件描述符實際上是有限的,若不關閉文件,文件描述符用光,則在進程中就打不開新文件了。
一個進程運行起來,進程會默認打開三個文件:標準輸入 0-stdin/標準輸出 1-stdout/標準錯誤 2-stderr;
文件描述符的分配原則:最小未使用;
print打印數據到標準輸出,close(1)是把標準輸出關閉了;打開新文件後,printf並沒有把數據打印出來,而是在刷新緩衝區之後,兩數據寫入到了文件中。
printf並非幀的一定要把數據寫入標準輸出文件,而是因爲printf函數中操作文件的時候操作的描述符是1。原本向1中寫入數據就是向標準輸出寫入,然後當1指向了新的文件後,這個printf就會將數據寫入到新的文件中重定向:將數據不再寫入原本的文件,而是寫入新的指定的文件中,實現方式就是替換這個描述符對應的文件描述信息。實際上是描述符的重定向,改變描述符所指向的文件,就是改變了數據的流向。
int dup2(int oldfd,int newfd);- - - - -描述重定向函數
功能:讓newfd這個描述符也指向oldfd所指向的文件,這時候oldfd和newfd都能夠操作oldfd所指向的文件。
**重定向實現原理**:每個文件描述符都是一個內核中文件描述信息數組的下標,對應有一個文件的描述信息用於操作文件,而重定向就是在不改變所操作的文件描述符的情況下,通過改變描述符對應的文件描述信息進而實現改變所操作的文件。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//close(1);//關閉0號描述符-就是關閉了標準輸入
/*
umask(0);
int fd = open("./test.txt", O_RDWR|O_CREAT, 0664);
if (fd < 0) {
perror("open error");
return -1;
}
dup2(fd, 1);//將1重定向到test.txt這個文件
printf("fd=%d\n", fd);
fflush(stdout);//刷新標準輸出緩衝區
close(fd);
*/
FILE *fp = fopen("./test.txt", "r+");
fp->_fileno = 1;//將文件流指針中的文件描述符改成標準輸出的描述符了
fwrite("hello world\n", 1, 12, fp);
fclose(fp);
}
在minishell中實現>/>>標準輸出重定向
>清空重定向open(O_CREAT|O_TRUNC);
>>追加重定向opend(O_CREAT|O_APPEND);
a.txt fd = open(a.txt);
dup2(fd,1); //子進程在運行指令的時候,ls本身要將數據寫入標準輸出
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main()
{
while(1) {
//增加一個shell提示
printf("[san@minishell]$ ");
fflush(stdout);//刷新標準輸出緩衝區
//1. 等待標準輸入
char buf[1024] = {0};
fgets(buf, 1023, stdin);
buf[strlen(buf)-1] = '\0'; //buf[...]='\n'
//1.5 解析重定向
//ls -l > a.txt
char *ptr = buf;
int redirect_flag = 0;
char *redirect_file = NULL;
while(*ptr != '\0') {
if (*ptr == '>') {
redirect_flag = 1;//這是清空重定向
*ptr = '\0';//將>替換成結尾標誌,則命令的解析到此位置就完畢了
ptr++;
if (*ptr == '>') {//有第二個>則是追加重定向
redirect_flag = 2;
*ptr = '\0';
ptr++;
}
while(*ptr == ' ' && *ptr != '\0') ptr++;//將a.txt之前的空格走完
redirect_file = ptr;//redirect_file這個指針指向了a.txt中a的位置
while(*ptr != ' ' && *ptr != '\0') ptr++; // 將a.txt字符走完
*ptr = '\0';
}
ptr++;
}
//2. 對輸入命令數據進行解析
char *argv[32] = {NULL};
int argc = 0;
ptr = buf;
// [ ls -a -l ]
while(*ptr != '\0') {
if (*ptr != ' ') {
argv[argc] = ptr;
argc++;
while(*ptr != ' ' && *ptr != '\0') {
ptr++;
}
*ptr = '\0';
}
ptr++;
}
argv[argc] = NULL;//最後一個參數的下一個位置置NULL
//3. 創建子進程 4. 在子進程中程序替換
pid_t pid = fork();
if (pid == 0) {
if (redirect_flag == 1) {//清空重定向
int fd = open(redirect_file, O_WRONLY|O_CREAT|O_TRUNC, 0664);
dup2(fd, 1);//將標準輸入重定向到redirect_file;原本要打印的數據就會被寫入文件
}else if (redirect_flag == 2){ // 追加重定向
int fd = open(redirect_file, O_WRONLY|O_CREAT|O_APPEND, 0664);
dup2(fd, 1);//將標準輸入重定向到redirect_file;原本要打印的數據就會被寫入文件
}
//execvp(char *file, char *argv[]) file--新程序名稱
execvp(argv[0], (char**)argv);//程序替換成功就去運行新程序了,32行以後就不會運行了
//能夠走到第33行,那麼肯定程序替換失敗了
perror("execvp error");//打印上一次系統調用接口使用的錯誤原因
exit(0);
}
//5. 進程等待
wait(NULL);
}
return 0;
}
文件描述符與文件流指針的關係:
文件描述符:是一個非負整數,系統調用的IO接口;在進程中每打開一個文件,都會創建有相應的文件描述信息struct file,這個描述信息被添加在pcb的struct files_struct中,以數組的形式進行管理,隨即向用戶返回數組的下標作爲文件描述符,用於操作文件。
文件流指針:FILE結構體,typedef struct _IO_FILE FILE - - - - -庫函數IO接口的操作句柄
通過文件流指針進行最終文件操作的時候,依然還要能夠找到文件對應的文件描述符纔可以,文件流指針是一個結構體,結構體中有很多的成員變量,其中有一個叫做_fileno- - -這就是文件描述符;
向文件寫入數據,並不會直接寫入文件,而是先寫入緩衝區中,刷新緩衝區的時候纔會寫入文件;(只有庫函數才存在這個緩衝區)。
系統調用接口是直接將數據寫入文件的,系統調用接口是沒有這個緩衝區的。
exit( )退出會刷新緩衝區,而_exit( )退出時不會刷新緩衝區。