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 對二進位流文件的讀寫有兩種體系:

  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。

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