Linux檔案IO操作

DeRoy發表於2021-10-30

來源:微信公眾號「程式設計學習基地」

檔案操作

在進行 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 對二進位制流檔案的讀寫有兩種體系:

  1. fopen,fread,fwrite ;
  2. 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。

上面都是我零碎的知識點總結一下備忘。

相關文章