文件操作
在進行 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。
上面都是我零碎的知識點總結一下備忘。