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