Linux文件IO操作
來源:微信公眾號「編程學習基地」
文件操作
在進行 Linux 文件操作之前,我們先簡單了解一下 Linux 文件系統
Linux文件類型
Linux中文件類型只有以下這幾種:
符號 | 文件類型 |
---|---|
– | 普通文件 |
d | 目錄文件,d是directory的簡寫 |
l | 軟連接文件,亦稱符號鏈接文件,s是soft或者symbolic的簡寫 |
b | 塊文件,是設備文件的一種(還有另一種),b是block的簡寫 |
c | 字元文件,也是設備文件的一種(這就是第二種),c是character的文件 |
s | 套接字文件,這種文件類型用於進程間的網路通訊 |
p | 管道文件,這種文件類型用於進程間的通訊 |
怎麼判斷文件類型?
$ ls -l
total 8
drwxrwxr-x 2 ubuntu ubuntu 4096 Oct 25 15:30 dir
prw-rw-r-- 1 ubuntu ubuntu 0 Oct 25 15:30 FIFOTEST
-rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
lrwxrwxrwx 1 ubuntu ubuntu 6 Oct 25 15:28 main.c-temp -> main.c
srwxrwxr-x 1 ubuntu ubuntu 0 Oct 25 15:24 test.sock
ls -l 第一個字母代表文件類型
Linux文件許可權
文件許可權是文件的訪問控制許可權,那些用戶和組群可以訪問文件以及可以執行什麼操作
查看文件許可權
文件類型後面緊跟著的就是文件許可權
簡單介紹下文件許可權,如下圖所示:
因為Linux是一個多用戶登錄的作業系統,所以文件許可權跟用戶相關。
修改文件許可權
1.以二進位的形式修改文件許可權
什麼是二進位形式?以main.c
的許可權為例
-rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
文件的許可權為rw-rw-r--
,對應的二進位為664
,如何計算呢,看下錶
可讀 | 可寫 | 可執行 | |
---|---|---|---|
字元表示 | r | w | x |
數字表示 | 4 | 2 | 1 |
所有者的許可權為rw-
,對應著4+2+0,也就是最終的許可權6,以此類推,用戶組的許可權為6,其他用戶的許可權為4.
修改文件許可權需要用到chmod
命令,如下所示
$ ls -l
-rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
$ chmod 666 main.c
$ ls -l
-rw-rw-rw- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
二進位的計算不要算錯了
2.以加減賦值的方式修改文件許可權
還是用到chmod
命令,直接上手
$ ls -l
-rw-rw-rw- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
$ chmod o-w main.c
$ ls -l
-rw-rw-r-- 1 ubuntu ubuntu 2 Oct 25 15:25 main.c
文件所有者 user | 文件所屬組用戶 group | 其他用戶 other |
---|---|---|
u | g | o |
+
和-
分別表示增加和去掉相應許可權
簡單的了解了Linux下的文件操作之後就開始進入程式碼編程階段
Linux error
獲取系統調用時的錯誤描述
Linux下的文件操作屬於系統調用,Linux中系統調用的錯誤都存儲於 errno
中,例如文件不存在,errno置 2,即宏定義 ENOENT
,對應的錯誤描述為 No such file or directory
。
列印系統調用時的錯誤描述需要用到 strerror
,定義如下
#include <string.h>
char *strerror(int errnum);
查看系統中所有的 errno
所代表的含義,可以採用如下的程式碼:
/* Function: obtain the errno string
* char *strerror(int errno)
*/
#include <stdio.h>
#include <string.h> //for strerror()
//#include <errno.h>
int main()
{
int tmp = 0;
for(tmp = 0; tmp <=256; tmp++)
{
printf("errno: %2d\t%s\n",tmp,strerror(tmp));
}
return 0;
}
可以自己手動運行下,看下輸出效果
列印錯誤資訊
之前談到Linux系統調用的錯誤都存儲於 errno
中,errno
定義如下
#include <errno.h>
int errno;
除了 strerror
可以輸出錯誤描述外,perror
也可以,而且更加方便
列印系統錯誤消息 perror
,函數原型及頭文件定義如下
#include <stdio.h>
void perror(const char *s);
使用示例:
/**
* @brief 文件不存在打開失敗時列印錯誤描述
*/
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h> //strerror
#include <errno.h> //errno
int main() {
int fd = open("test.txt", O_RDWR);
if(fd == -1) {
perror("open");
//printf("open:%s\n",strerror(errno));
}
close(fd);
return 0;
}
當文件 test.txt
不存在時列印如下
./main
open: No such file or directory
系統IO函數
UNIX環境下的C 對二進位流文件的讀寫有兩種體系:
- fopen,fread,fwrite ;
- open, read, write;
fopen 系列是標準的C庫函數;open系列是 POSIX 定義的,是UNIX系統里的system call。
文件操作不外乎 open
,close
,read
,write
,lseek
,從打開文件開始介紹
open/close
open
定義如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
– pathname:要打開的文件路徑
– flags:對文件的操作許可權設置還有其他的設置
O_RDONLY, O_WRONLY, O_RDWR 這三個設置是互斥的
– mode:八進位數,表示創建出的新的文件的操作許可權,例如:0775
close
定義如下
#include <unistd.h>
int close(int fd);
打開文件
通過open
打開一個存在的文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("test.txt", O_RDONLY);// 打開一個文件
if(fd == -1) {
perror("open");
}
// 讀寫操作
close(fd); // 關閉
return 0;
}
如果文件存在,打開文件;文件不存在,打開失敗,錯誤描述為No such file or directory
。
創建文件
通過open
創建一個新的文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
// 創建一個新的文件
int fd = open("test.txt", O_RDWR | O_CREAT, 0777);
if(fd == -1) {
perror("open");
}
// 關閉
close(fd);
return 0;
}
這裡有個東西需要注意一下,先看輸出結果
$ ls -l test.txt
-rwxrwxr-x 1 dengzr dengzr 0 Oct 27 19:50 test.txt
創建文件的同時賦予文件許可權,在上面的Linux文件許可權中已經介紹過了文件許可權
創建文件時賦值的許可權為777
,但是創建的文件許可權為775
,這是我們需要注意的地方。
在linux系統中,我們創建一個新的文件或者目錄的時候,這些新的文件或目錄都會有默認的訪問許可權。默認的訪問許可權通過命令umask
查看。
$ umask
0002
用戶創建文件的最終許可權為mode & ~umask
。
例如Demo中創建的文件許可權mode
= 0777
,所以最終許可權為 0775
777 -> 111111111
~002 -> 111111101 &
775 -> 111111101
修改默認訪問許可權
更改umask值,可以通過命令 umask mode
的方式來更改umask
值,比如我要把umask
值改為000,則使用命令 umask 000 即可。
$ umask
0002
$ umask 000
$ umask
0000
刪除test.txt
重新運行程式創建
$ ./create
$ ls -l test.txt
-rwxrwxrwx 1 dengzr dengzr 0 Oct 27 20:06 test.txt
ps:這種方式並不能永久改變umask
值,只是改變了當前會話的umask
值,打開一個新的shell
輸入umask
命令,可以看到umask
值仍是默認的002
。要想永久改變umask
值,則可以修改文件/etc/bashrc
,在文件中添加一行 umask 000
。
read/write
文件I/O最基本的兩個函數就是read和write,在《unix/linux編程實踐教程》也叫做unbuffered I/O。
這裡的ubuffered,是指的是針對於read和write本身來說,他們是沒有快取機制(應用層無緩衝)。
read定義如下
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
參數:
– fd:文件描述符
– buf:保存讀取數據的緩衝區
– count:讀取數據的大小
返回值:
– 成功:
>0: 返回實際的讀取到的位元組數
=0:文件已經讀取完了
– 失敗:-1 ,並且設置errno
簡單應用一下,示例Demo
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h> //errno
int main() {
int fd = open("test.txt", O_RDWR);
if(fd == -1) {
perror("open");
}
char buff[1024];
memset(buff,'\0',1024);
int readLen = read(fd,buff,1024);
printf("readLen:%d,data:%s",readLen,buff);
close(fd);
return 0;
}
Demo讀取 test.txt
裡面的數據並顯示
$ ./main
readLen:5,data:text
write定義如下
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
參數:
– fd:文件描述符
– buf:待寫入數據塊
– count:寫入數據的大小
返回值:
– 成功:實際寫入的位元組數
– 失敗:返回-1,並設置errno
同樣簡單應用一下,Demo如下
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> //errno
int main() {
int fd = open("test.txt", O_WRONLY | O_CREAT ,0775);
if(fd == -1) {
perror("open");
}
char buf[256] = "text";
int Len = write(fd,buf,strlen(buf));
printf("readLen:%d,data:%s\n",Len,buf);
// close(fd);
return 0;
}
在Demo中注釋掉close,數據寫入成功
$ ./main
readLen:4,data:text
$ cat test.txt
text
對比fwrite等標準的C庫函數,write是沒有緩衝的,不需要fflush
或者close
將緩衝數據寫入到磁碟中但是程式open後一定要close,這是一個良好的編程習慣。
ps:其實write是有緩衝的,在用戶看不到的系統層,我們可以理解為沒有緩衝
lseek
作用:對文件文件指針進行文件偏移操作
lseek定義如下
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
參數:
– fd:文件描述符
– offset:偏移量
– whence:
SEEK_SET :設置文件指針的偏移量
SEEK_CUR :設置偏移量:當前位置 + 第二個參數offset的值
SEEK_END:設置偏移量:文件大小 + 第二個參數offset的值
返回值:返迴文件指針的位置
lseek和標準C庫函數fseek沒有什麼區別,幾個作用簡單了解一下
1.移動文件指針到文件頭
lseek(fd, 0, SEEK_SET);
2.獲取當前文件指針的位置
lseek(fd, 0, SEEK_CUR);
3.獲取文件長度
lseek(fd, 0, SEEK_END);
示例Demo如下
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main() {
int fd = open("test.txt", O_RDWR);
if(fd == -1) {
perror("open");
return -1;
}
// 獲取文件的長度
int len = lseek(fd, 0, SEEK_END);
printf("file len:%d\n",len);
// 關閉文件
close(fd);
return 0;
}
./main
file len:4
linux下的標準輸入/輸出/錯誤
在文件IO操作裡面一直講到文件描述符,那我就不得不提一下linux中的標準輸入/輸出/錯誤
在C語言的學習過程中我們經常看到的stdin,stdout和stderr,這3個是被稱為終端(Terminal)的標準輸入(standard input),標準輸出( standard out)和標準錯誤輸出(standard error),這對應的是標準C庫中的fopen/fwrite/fread。
但是在在Linux下,作業系統一級提供的文件API都是以文件描述符來表示文件,對應的的標準輸入,標準輸出和標準錯誤輸出是0,1,2,宏定義為STDIN_FILENO、STDOUT_FILENO 、STDERR_FILENO ,這對應系統API介面庫中的open/read/write。
標準輸入(standard input)
在c語言中表現為調用scanf函數接受用戶輸入內容,即從終端設備輸入內容。也可以用fscanf指明stdin接收內容或者用read接受,對應的標準輸入的文件標識符為0或者STDIN_FILENO。
#include<stdio.h>
#include<string.h>
int main()
{
char buf[1024];
//C語言下標準輸入
scanf("%s",buf);
//UNIX下標準輸入 stdin
fscanf(stdin,"%s",buf);
//作業系統級 STDIN_FILENO
read(0,buf,strlen(buf));
return 0;
}
ps:注意read不可以和stdin搭配使用
標準輸出(standard out)
在c語言中表現為調用printf函數將內容輸出到終端上。使用fprintf指明stdout也可以把內容輸出到終端上或者wirte輸出到終端,對應的標準輸出的文件標識符為1或者STDOUT_FILENO 。
#include<stdio.h>
#include<string.h>
#include <unistd.h>
int main()
{
char buf[1024];
//C語言下標準輸入 輸出
scanf("%s",buf);
printf("buf:%s\n",buf);
//UNIX下標準輸入 stdin
fscanf(stdin,"%s",buf);
fprintf(stdout,"buf:%s\n",buf);
//作業系統級 STDIN_FILENO
read(STDIN_FILENO,buf,strlen(buf));
write(STDOUT_FILENO,buf,strlen(buf));
return 0;
}
標準錯誤輸出(standard error)
標準錯誤和標準輸出一樣都是輸出到終端上, 標準C庫對應的標準錯誤為stderr,系統API介面庫對應的標準錯誤輸出的文件標識符為2或者STDERR_FILENO。
#include<stdio.h>
#include<string.h>
int main()
{
char buf[1024]="error";
fprintf(stderr,"%s\n",buf);
write(2,buf,strlen(buf));
return 0;
}
既然有標準輸出,為什麼要有標準錯誤呢?既生瑜何生亮?
一個簡單的Demo讓你了解一下,諸葛的牛逼
#include <stdio.h>
int main()
{
fprintf(stdout, "stdout");
fprintf(stderr, "stderr");
return 0;
}
猜一下是先輸出stdout還是stderr,按照正常思維是先輸出stdout,再輸出stderr。
但是stderr屬於諸葛流,喜歡搶佔先機,所以先輸出stderr,再輸出stdout。
~咳咳,扯遠了,實際上stdout是塊設備,stderr不是。對於塊設備,只有當下面幾種情況下才會被輸入:遇到回車;緩衝區滿;flush被調用。而stderr因為沒有緩衝所以直接輸出。
談一下stdin和STDIN_FILENO區別
以前我一直沒搞明白,以為stdin等於0,其實stdin類型為 FILE*;STDIN_FILENO類型為 int,不能相提並論,其次stdin屬於標準I/O,高級的輸入輸出函數,對應的fread、fwrite、fclose等;STDIN_FILENO屬於系統API介面庫,對應的read,write,close。
上面都是我零碎的知識點總結一下備忘。