三、文件系統(tǒng)、目錄操作

1.文件系統(tǒng)

1.1文件存儲(chǔ)

首先了解如下文件存儲(chǔ)相關(guān)概念:inode、 dentry、 數(shù)據(jù)存儲(chǔ)、文件系統(tǒng)。

1.1.1inode

其本質(zhì)為結(jié)構(gòu)體,存儲(chǔ)文件的屬性信息。如:權(quán)限、類型、大小、時(shí)間、用戶、盤塊位置……也叫作文件屬性管理結(jié)構(gòu),大多數(shù)的inode都存儲(chǔ)在磁盤上。
少量常用、近期使用的inode會(huì)被緩存到內(nèi)存中。

1.1.3dentry

目錄項(xiàng),其本質(zhì)依然是結(jié)構(gòu)體,重要成員變量有兩個(gè) {文件名,inode,...},而文件內(nèi)容(data)保存在磁盤盤塊中。

1.1.4文件系統(tǒng)

文件系統(tǒng)是,一組規(guī)則,規(guī)定對(duì)文件的存儲(chǔ)及讀取的一般方法。文件系統(tǒng)在磁盤格式化過(guò)程中指定。
常見(jiàn)的文件系統(tǒng)有:fat32 ntfs exfat ext2 、ext3 、ext4

1.2 ext2文件系統(tǒng)
ext2 文件系統(tǒng)

我們知道,一個(gè)磁盤可以劃分成多個(gè)分區(qū),每個(gè)分區(qū)必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的文件系統(tǒng),然后才能存儲(chǔ)文件,格式化的過(guò)程會(huì)在磁盤上寫一些管理存儲(chǔ)布局的信息。下圖是一個(gè)磁盤分區(qū)格式化成ext2文件系統(tǒng)后的存儲(chǔ)布局。

文件系統(tǒng)中存儲(chǔ)的最小單位是塊(Block),一個(gè)塊究竟多大是在格式化時(shí)確定的,例如mke2fs的-b選項(xiàng)可以設(shè)定塊大小為1024、2048或4096字節(jié)。而上圖中啟動(dòng)塊(BootBlock)的大小是確定的,就是1KB,啟動(dòng)塊是由PC標(biāo)準(zhǔn)規(guī)定的,用來(lái)存儲(chǔ)磁盤分區(qū)信息和啟動(dòng)信息,任何文件系統(tǒng)都不能使用啟動(dòng)塊。啟動(dòng)塊之后才是ext2文件系統(tǒng)的開始,ext2文件系統(tǒng)將整個(gè)分區(qū)劃成若干個(gè)同樣大小的塊組(Block Group),每個(gè)塊組都由以下部分組成。

超級(jí)塊(Super Block) 描述整個(gè)分區(qū)的文件系統(tǒng)信息,例如塊大小、文件系統(tǒng)版本號(hào)、上次mount的時(shí)間等等。超級(jí)塊在每個(gè)塊組的開頭都有一份拷貝。

塊組描述符表(GDT,Group Descriptor Table) 由很多塊組描述符組成,整個(gè)分區(qū)分成多少個(gè)塊組就對(duì)應(yīng)有多少個(gè)塊組描述符。每個(gè)塊組描述符(Group Descriptor)存儲(chǔ)一個(gè)塊組的描述信息,例如在這個(gè)塊組中從哪里開始是inode表,從哪里開始是數(shù)據(jù)塊,空閑的inode和數(shù)據(jù)塊還有多少個(gè)等等。和超級(jí)塊類似,塊組描述符表在每個(gè)塊組的開頭也都有一份拷貝,這些信息是非常重要的,一旦超級(jí)塊意外損壞就會(huì)丟失整個(gè)分區(qū)的數(shù)據(jù),一旦塊組描述符意外損壞就會(huì)丟失整個(gè)塊組的數(shù)據(jù),因此它們都有多份拷貝。通常內(nèi)核只用到第0個(gè)塊組中的拷貝,當(dāng)執(zhí)行e2fsck檢查文件系統(tǒng)一致性時(shí),第0個(gè)塊組中的超級(jí)塊和塊組描述符表就會(huì)拷貝到其它塊組,這樣當(dāng)?shù)?個(gè)塊組的開頭意外損壞時(shí)就可以用其它拷貝來(lái)恢復(fù),從而減少損失。

塊位圖(Block Bitmap) 一個(gè)塊組中的塊是這樣利用的:數(shù)據(jù)塊存儲(chǔ)所有文件的數(shù)據(jù),比如某個(gè)分區(qū)的塊大小是1024字節(jié),某個(gè)文件是2049字節(jié),那么就需要三個(gè)數(shù)據(jù)塊來(lái)存,即使第三個(gè)塊只存了一個(gè)字節(jié)也需要占用一個(gè)整塊;超級(jí)塊、塊組描述符表、塊位圖、inode位圖、inode表這幾部分存儲(chǔ)該塊組的描述信息。那么如何知道哪些塊已經(jīng)用來(lái)存儲(chǔ)文件數(shù)據(jù)或其它描述信息,哪些塊仍然空閑可用呢?塊位圖就是用來(lái)描述整個(gè)塊組中哪些塊已用哪些塊空閑的,它本身占一個(gè)塊,其中的每個(gè)bit代表本塊組中的一個(gè)塊,這個(gè)bit為1表示該塊已用,這個(gè)bit為0表示該塊空閑可用。

為什么用df命令統(tǒng)計(jì)整個(gè)磁盤的已用空間非常快呢?因?yàn)橹恍枰榭疵總€(gè)塊組的塊位圖即可,而不需要搜遍整個(gè)分區(qū)。相反,用du命令查看一個(gè)較大目錄的已用空間就非常慢,因?yàn)椴豢杀苊獾匾驯檎麄€(gè)目錄的所有文件。

與此相聯(lián)系的另一個(gè)問(wèn)題是:在格式化一個(gè)分區(qū)時(shí)究竟會(huì)劃出多少個(gè)塊組呢?主要的限制在于塊位圖本身必須只占一個(gè)塊。用mke2fs格式化時(shí)默認(rèn)塊大小是1024字節(jié),可以用-b參數(shù)指定塊大小,現(xiàn)在設(shè)塊大小指定為b字節(jié),那么一個(gè)塊可以有8b個(gè)bit,這樣大小的一個(gè)塊位圖就可以表示8b個(gè)塊的占用情況,因此一個(gè)塊組最多可以有8b個(gè)塊,如果整個(gè)分區(qū)有s個(gè)塊,那么就可以有s/(8b)個(gè)塊組。格式化時(shí)可以用-g參數(shù)指定一個(gè)塊組有多少個(gè)塊,但是通常不需要手動(dòng)指定,mke2fs工具會(huì)計(jì)算出最優(yōu)的數(shù)值。

inode位圖(inode Bitmap) 和塊位圖類似,本身占一個(gè)塊,其中每個(gè)bit表示一個(gè)inode是否空閑可用。

**inode表(inode Table) **我們知道,一個(gè)文件除了數(shù)據(jù)需要存儲(chǔ)之外,一些描述信息也需要存儲(chǔ),例如文件類型(常規(guī)、目錄、符號(hào)鏈接等),權(quán)限,文件大小,創(chuàng)建/修改/訪問(wèn)時(shí)間等,也就是ls -l命令看到的那些信息,這些信息存在inode中而不是數(shù)據(jù)塊中。每個(gè)文件都有一個(gè)inode,一個(gè)塊組中的所有inode組成了inode表。inode表占多少個(gè)塊在格式化時(shí)就要決定并寫入塊組描述符中,mke2fs格式化工具的默認(rèn)策略是一個(gè)塊組有多少個(gè)8KB就分配多少個(gè)inode。由于數(shù)據(jù)塊占了整個(gè)塊組的絕大部分,也可以近似認(rèn)為數(shù)據(jù)塊有多少個(gè)8KB就分配多少個(gè)inode,換句話說(shuō),如果平均每個(gè)文件的大小是8KB,當(dāng)分區(qū)存滿的時(shí)候inode表會(huì)得到比較充分的利用,數(shù)據(jù)塊也不浪費(fèi)。如果這個(gè)分區(qū)存的都是很大的文件(比如電影),則數(shù)據(jù)塊用完的時(shí)候inode會(huì)有一些浪費(fèi),如果這個(gè)分區(qū)存的都是很小的文件(比如源代碼),則有可能數(shù)據(jù)塊還沒(méi)用完inode就已經(jīng)用完了,數(shù)據(jù)塊可能有很大的浪費(fèi)。如果用戶在格式化時(shí)能夠?qū)@個(gè)分區(qū)以后要存儲(chǔ)的文件大小做一個(gè)預(yù)測(cè),也可以用mke2fs的-i參數(shù)手動(dòng)指定每多少個(gè)字節(jié)分配一個(gè)inode。

inode表中每個(gè)inode的大小:ext2、ext3中128字節(jié),ext4中256字節(jié)。

數(shù)據(jù)塊(Data Block) 根據(jù)不同的文件類型有以下幾種情況:

  • 對(duì)于常規(guī)文件,文件的數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)塊中。
  • 對(duì)于目錄,該目錄下的所有文件名和目錄名存儲(chǔ)在數(shù)據(jù)塊中,注意文件名保存在它所在目錄的數(shù)據(jù)塊中,除文件名之外,ls -l命令看到的其它信息都保存在該文件的inode中。注意這個(gè)概念:目錄也是一種文件,是一種特殊類型的文件。
  • 對(duì)于符號(hào)鏈接,如果目標(biāo)路徑名較短則直接保存在inode中以便更快地查找,如果目標(biāo)路徑名較長(zhǎng)則分配一個(gè)數(shù)據(jù)塊來(lái)保存。
  • 設(shè)備文件、FIFO和socket等特殊文件沒(méi)有數(shù)據(jù)塊,設(shè)備文件的主設(shè)備號(hào)和次設(shè)備號(hào)保存在inode中。
文件系統(tǒng)

2.文件操作

2.1stat函數(shù)

獲取文件屬性(從inode結(jié)構(gòu)體中獲取)

int stat(const char *path, struct stat *buf);   
成返回0;失敗返回-1 設(shè)置errno為恰當(dāng)值。
        參數(shù)1:文件名
        參數(shù)2:inode結(jié)構(gòu)體指針 (傳出參數(shù))
文件屬性將通過(guò)傳出參數(shù)返回給調(diào)用者。

練習(xí):使用stat函數(shù)查看文件屬性

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc,char *argv[])
{
    
    struct stat buf;
 
    int ret = stat(argv[1], &buf);
    if(ret == -1){
        perror("stat error");
        exit(1);
    }

    //用man 2 stat查看stat結(jié)構(gòu)體
   printf("st_size = %ld\n", buf.st_size);

   //當(dāng)對(duì)軟連接查看文件屬性時(shí),stat會(huì)穿透這個(gè)連接別名直接找到原文件本身
   //所以查看到連接文件時(shí),是regular而不是link
   //避免這種情況,只能使用lstat函數(shù)
   if(S_ISREG(buf.st_mode)){
       printf("it is a regular\n");
   }else if(S_ISDIR(buf.st_mode)){
       printf("it is a dictionary\n");
   }else if(S_ISLNK(buf.st_mode)){
       printf("it is a sym link\n");
   }else if(S_ISFIFO(buf.st_mode)){
       printf("it is a pipe\n");
   }

    printf("st_ino = %ld\n", buf.st_ino);
    printf("st_nlink = %d\n", buf.st_nlink);
    printf("st_uid = %d\n", buf.st_uid);
    printf("st_gid = %d\n", buf.st_gid);

    return 0;
}
2.2lstat函數(shù)
int lstat(const char *path, struct stat *buf);
//成返回0;失敗返回-1 設(shè)置errno為恰當(dāng)值。

文件類型判斷方法:st_mode 取高4位。 但應(yīng)使用宏函數(shù):           
S_ISREG(m)      is it a regular file?
S_ISDIR(m)      directory?
S_ISCHR(m)      character device?
S_ISBLK(m)      block device?
S_ISFIFO(m)     FIFO (named pipe)?
S_ISLNK(m)      symbolic link?  (Not in POSIX.1-1996.)
S_ISSOCK(m)     socket?  (Not in POSIX.1-1996.)
穿透符號(hào)鏈接:stat:會(huì);lstat:不會(huì)

3.特殊權(quán)限位

包含三個(gè)二進(jìn)制位。依次是:設(shè)置組ID位setGID;設(shè)置用戶ID位setID;黏住位sticky

3.1黏住位

早起計(jì)算機(jī)內(nèi)存緊,只有精要的常用的程序可以常駐物理內(nèi)存,剩下的要暫存磁盤中。當(dāng)內(nèi)存不夠用的時(shí)候會(huì)將該部分程序存回磁盤,騰出內(nèi)存空間。若文件設(shè)置了黏住位,那么即使在內(nèi)存比較吃緊的情況下,也不會(huì)將該文件回存到磁盤上。由于現(xiàn)階段操作系統(tǒng)的虛擬內(nèi)存管理分頁(yè)算法完善。該功能已經(jīng)被廢棄。
但我們?nèi)匀豢梢?strong>對(duì)目錄設(shè)置黏住位。被設(shè)置了該位的目錄,其內(nèi)部文件只有:
① 超級(jí)管理員
② 該目錄所有者
③ 該文件的所有者
以上三種用戶有權(quán)限做刪除操作。其他用戶可以寫、讀但不能隨意刪除。

3.2setUID位

進(jìn)程有兩個(gè)ID:
EID(有效用戶ID),表示進(jìn)程履行哪個(gè)用戶的權(quán)限。
UID(實(shí)際用戶ID),表示進(jìn)程實(shí)際屬于哪個(gè)用戶。

例如:當(dāng)進(jìn)程執(zhí)行一個(gè)root用戶的文件,若該文件的setID位被設(shè)置為1, 那么執(zhí)行該文件時(shí),進(jìn)程的UID不變。EID變?yōu)閞oot,表示進(jìn)程開始履行root用戶權(quán)限。

EID和UID

setGID位于setID相類似。

3.3access函數(shù)

測(cè)試指定文件是否存在/擁有某種權(quán)限。

int access(const char *pathname,  int mode);
成功/具備該權(quán)限:0;
失敗/不具備 -1 設(shè)置errno為相應(yīng)值。
參數(shù)2:R_OK、W_OK、X_OK//讀權(quán)限,寫權(quán)限,操作權(quán)限
通常使用access函數(shù)來(lái)測(cè)試某個(gè)文件是否存在。F_OK
-------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int ret;
    if ((ret = access("abc", W_OK)) < 0) {
        printf("error ret = %d\n", ret);
        perror("abc");
        exit(1);
    }
    printf("ret = %d\n", ret);
    printf("abc is exist\n");

    return 0;
}
3.4chmod函數(shù)
修改文件的訪問(wèn)權(quán)限
int chmod(const char *path, mode_t mode);       
成功:0;失敗:-1設(shè)置errno為相應(yīng)值
mode:數(shù)字權(quán)限
int fchmod(int fd, mode_t mode);
3.5truncate函數(shù)
截?cái)辔募L(zhǎng)度成指定長(zhǎng)度。常用來(lái)拓展文件大小,代替lseek。
int truncate(const char *path, off_t length);//文件必須已經(jīng)存在     成功:0;失敗:-1設(shè)置errno為相應(yīng)值
int ftruncate(int fd, off_t length);//可以用文件描述符來(lái)修改
----------------------------------------
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

int main(void)
{
    int fd;

    fd = open("./file.t", O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) {
        perror("open error");
        exit(1);
    }

#if 0
    lseek(fd, 99, SEEK_SET);
    write(fd, "s", 1);
    close(fd);
#endif

    int ret = truncate("file.t", 200);
    if (ret < 0) {
        perror("open error");
        exit(1);
    }

    close(fd);
    return 0;
}
3.6link函數(shù)

思考,為什么目錄項(xiàng)要游離于inode之外,畫蛇添足般的將文件名單獨(dú)存儲(chǔ)呢??這樣的存儲(chǔ)方式有什么樣的好處呢?
其目的是為了實(shí)現(xiàn)文件共享。Linux允許多個(gè)目錄項(xiàng)共享一個(gè)inode,即共享盤塊(data)。不同文件名,在人類眼中將它理解成兩個(gè)文件,但是在內(nèi)核眼里是同一個(gè)文件。
link函數(shù),可以為已經(jīng)存在的文件創(chuàng)建目錄項(xiàng)(硬鏈接)。

int link(const char *oldpath,  const char *newpath);
成功:0;失敗:-1設(shè)置errno為相應(yīng)值
注意:由于兩個(gè)參數(shù)可以使用“相對(duì)/絕對(duì)路徑+文件名”的方式來(lái)指定,所以易出錯(cuò)。
如:link("../abc/a.c", "../ioc/b.c")若a.c,b.c都對(duì), 但abc,ioc目錄不存在也會(huì)失敗。

mv命令是修改了目錄項(xiàng),而并不修改文件本身。

3.7unlink函數(shù)
刪除一個(gè)文件的目錄項(xiàng);
int unlink(const char *pathname);   
成功:0;失敗:-1設(shè)置errno為相應(yīng)值

練習(xí):編程實(shí)現(xiàn)mv命令的改名操作    
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    if(link(argv[1], argv[2]) == -1){
        perror("link error");
        exit(1);
    }

    if(unlink(argv[1]) == -1){
        perror("unlink error");
        exit(1);
    }
    return 0;
}

注意Linux下刪除文件的機(jī)制:不斷將st_nlink -1,直至減到0為止。無(wú)目錄項(xiàng)對(duì)應(yīng)的文件,將會(huì)被操作系統(tǒng)擇機(jī)釋放。(具體時(shí)間由系統(tǒng)內(nèi)部調(diào)度算法決定)

因此,我們刪除文件,從某種意義上說(shuō),只是讓文件具備了被釋放的條件。

unlink函數(shù)的特征:清除文件時(shí),如果文件的硬鏈接數(shù)到0了,沒(méi)有dentry對(duì)應(yīng),但該文件仍不會(huì)馬上被釋放。要等到所有打開該文件的進(jìn)程關(guān)閉該文件,系統(tǒng)才會(huì)挑時(shí)間將該文件釋放掉。

/*
 *unlink函數(shù)是刪除一個(gè)dentry
 */
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>


int main(void)
{
    int fd;
    char *p = "test of unlink\n";
    char *p2 = "after write something.\n";

    fd = open("temp.txt", O_RDWR|O_CREAT|O_TRUNC, 0644);
    if(fd < 0){
        perror("open temp error");
        exit(1);
    }
    int ret = unlink("temp.txt");        //具備了被釋放的條件
    if(ret < 0){
        perror("unlink error");
        exit(1);
    }

    ret = write(fd, p, strlen(p));
    if (ret == -1) {
        perror("-----write error");
    }

    p[0] = 'H';//這個(gè)出錯(cuò)!

    printf("hi! I'm printf\n");
    ret = write(fd, p2, strlen(p2));
    if (ret == -1) {
        perror("-----write error");
    }

    printf("Enter anykey continue\n");
    getchar();
    close(fd);
    return 0;
}

3.8隱式回收

當(dāng)進(jìn)程結(jié)束運(yùn)行時(shí),所有該進(jìn)程打開的文件會(huì)被關(guān)閉,申請(qǐng)的內(nèi)存空間會(huì)被釋放。系統(tǒng)的這一特性稱之為隱式回收系統(tǒng)資源。

3.9symlink函數(shù)

創(chuàng)建一個(gè)符號(hào)鏈接

int symlink(const char *oldpath, const char *newpath);  
成功:0;失敗:-1設(shè)置errno為相應(yīng)值
3.10readlink函數(shù)

讀取符號(hào)鏈接文件本身內(nèi)容,得到鏈接所指向的文件名。

ssize_t readlink(const char *path, char *buf, size_t bufsiz);   
成功:返回實(shí)際讀到的字節(jié)數(shù);失敗:-1設(shè)置errno為相應(yīng)值。
3.11rename函數(shù)

重命名一個(gè)文件。

int rename(const char *oldpath, const char *newpath); 
成功:0;失敗:-1設(shè)置errno為相應(yīng)值

4.目錄操作

工作目錄:“./”代表當(dāng)前目錄,指的是進(jìn)程當(dāng)前的工作目錄,默認(rèn)是進(jìn)程所執(zhí)行的程序所在的目錄位置。

4.1getcwd函數(shù)

獲取進(jìn)程當(dāng)前工作目錄 (卷3,標(biāo)庫(kù)函數(shù))

char *getcwd(char *buf, size_t size);//sizeof(buf)  
成功:buf中保存當(dāng)前進(jìn)程工作目錄位置。失敗返回NULL。
4.2chdir函數(shù)

改變當(dāng)前進(jìn)程的工作目錄

int chdir(const char *path);    
成功:0;失敗:-1設(shè)置errno為相應(yīng)值

練習(xí):獲取及修改當(dāng)前進(jìn)程的工作目錄,并打印至屏幕。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    char buf[64];
    char *p;
    system("ls -l");

    int ret = chdir(argv[1]);   //改變當(dāng)前進(jìn)程工作目錄
    if(ret < 0){
        perror("chdir error");
        exit(1);
    }

    system("ls -l");
    chdir("..");            //改變當(dāng)前進(jìn)程當(dāng)前工作目錄

    system("ls -l");
    return 0;
}
4.3文件、目錄權(quán)限

注意:目錄文件也是“文件”。其文件內(nèi)容是該目錄下所有子文件的目錄項(xiàng)dentry。 可以嘗試用vim打開一個(gè)目錄

權(quán)限 r w x
文件 文件的內(nèi)容可以被查看:cat、more、less... 內(nèi)容可以被修改vi、> 可以運(yùn)行產(chǎn)生一個(gè)進(jìn)程 ./文件名
目錄 目錄可以被瀏覽ls、tree 創(chuàng)建、刪除、修改文件mv touch mkdir 可以被打開、進(jìn)入cd

目錄設(shè)置黏住位:若有w權(quán)限,創(chuàng)建不變,刪除、修改只能由root、目錄所有者、文件所有者操作。

4.4opendir/closedir函數(shù)

根據(jù)傳入的目錄名打開/關(guān)閉一個(gè)目錄 (庫(kù)函數(shù)) DIR * 類似于 FILE *

DIR *opendir(const char *name);  
成功返回指向該目錄結(jié)構(gòu)體指針,失敗返回NULL 

//關(guān)閉打開的目錄
int closedir(DIR *dirp);        
成功:0;失敗:-1設(shè)置errno為相應(yīng)值

參數(shù)支持相對(duì)路徑、絕對(duì)路徑兩種方式:例如:打開當(dāng)前目錄:① getcwd() , opendir() ② opendir(".");

#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>

int count(char *root)
{
    DIR *dp;
    struct dirent *item;
    int n = 0;

    dp = opendir(root);
    if (dp == NULL) {
        perror("opendir error");
        exit(1);        
        //count是次級(jí)函數(shù),出錯(cuò)應(yīng)返回一個(gè)自定義個(gè)錯(cuò)誤號(hào)給調(diào)用者處理
        //這里直接調(diào)用exit退出。
    }
    while ((item = readdir(dp))) {  //遍歷每一個(gè)目錄項(xiàng),NULL表示讀完
        struct stat statbuf;
        printf("=======想想程序?yàn)槭裁闯鲥e(cuò)?=======\n");
        /*取文件的屬性, lstat防止穿透*/
        if(lstat(item->d_name, &statbuf) == -1){
            perror("lstat error");
            exit(1);
        }

        if (S_ISREG(statbuf.st_mode)) {
            n++;
        } else if (S_ISDIR(statbuf.st_mode))
            n += count(item->d_name);
    }

    closedir(dp);

    return n;
}

int main(void)
{
    int total;

    total = count("./");

    //printf("There are %d files in %s\n", total, argv[1]);
    printf("There are %d files in ./\n", total);

    return 0;
}

4.5readdir函數(shù)

讀取目錄 (庫(kù)函數(shù))

struct dirent *readdir(DIR *dirp);  
成功返回目錄項(xiàng)結(jié)構(gòu)體指針;失敗返回NULL設(shè)置errno為相應(yīng)值
需注意返回值,讀取數(shù)據(jù)結(jié)束時(shí)也返回NULL值,所以應(yīng)借助errno進(jìn)一步加以區(qū)分。

struct 結(jié)構(gòu)體:

struct dirent {
      ino_t          d_ino;         inode編號(hào)
      off_t          d_off;       
     unsigned short  d_reclen;      文件名有效長(zhǎng)度
     unsigned char   d_type;        類型(vim打開看到的類似@*/等)
       char          d_name[256];   文件名
};
其成員變量重點(diǎn)記憶兩個(gè):d_ino、d_name。實(shí)際應(yīng)用中只使用到d_name。

實(shí)現(xiàn)ls不打印隱藏文件。每5個(gè)文件換一個(gè)行顯示。

#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    DIR *dp;
    struct dirent *sdp;
    int count = 0;

    dp = opendir(".");
    if(dp == NULL){
        perror("opendir error");
        exit(1);
    }

    while((sdp = readdir(dp)) != NULL){
        if(sdp->d_name[0] != '.')
            printf("%-10s%c", sdp->d_name, ++count % 5? '\t' : '\n');
    }
    closedir(dp);
    return 0;
}
4.6rewinddir函數(shù)

回卷目錄讀寫位置至起始。
void rewinddir(DIR *dirp); 返回值:無(wú)。

4.7telldir/seekdir函數(shù)
獲取目錄讀寫位置
long telldir(DIR *dirp); 
成功:與dirp相關(guān)的目錄當(dāng)前讀寫位置。失敗-1,設(shè)置errno

修改目錄讀寫位置
void seekdir(DIR *dirp, long loc); 
返回值:無(wú)
參數(shù)loc一般由telldir函數(shù)的返回值來(lái)決定。

遞歸遍歷目錄

查詢指定目錄,遞歸列出目錄中文件,同時(shí)顯示文件大小。

#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define PATH_LEN 256
//dir=/home/itcast/1110_linux    fcn=isfile

void fetchdir(const char *dir, void (*fcn)(char *))//該函數(shù)被調(diào)用,既已被判定為目錄
{
    char name[PATH_LEN];
    struct dirent *sdp;
    DIR *dp;

    if ((dp = opendir(dir)) == NULL) {     //打開目錄失敗,如:沒(méi)有x權(quán)限
        //perror("fetchdir can't open");
        fprintf(stderr, "fetchdir: can't open %s\n", dir);
        return;
    }

    //
    while ((sdp = readdir(dp)) != NULL) {
        if (strcmp(sdp->d_name, ".") == 0 || strcmp(sdp->d_name, "..") == 0) {    //防止出現(xiàn)無(wú)限遞歸
            continue; 
        }

        if (strlen(dir)+strlen(sdp->d_name)+2 > sizeof(name)) {
            fprintf(stderr, "fetchdir: name %s %s too long\n", dir, sdp->d_name);
        } else {
            sprintf(name, "%s/%s", dir, sdp->d_name);
            (*fcn)(name);                     //這是一個(gè)什么??  
        }
    }

    closedir(dp);
}

void isfile(char *name)          //處理目錄/文件
{
    struct stat sbuf;

   if (stat(name, &sbuf) == -1) {   //文件名無(wú)效
        fprintf(stderr, "isfile: can't access %s\n", name);
        exit(1);
    }
    if ((sbuf.st_mode & S_IFMT) == S_IFDIR) {  //判定是否為目錄
        fetchdir(name, isfile);                //回調(diào)函數(shù),誰(shuí)是回調(diào)函數(shù)呢?
    }

    printf("%8ld %s\n", sbuf.st_size, name);   //不是目錄,則是普通文件,直接打印文件名
}
//./ls_R ~/1110_linux
int main(int argc, char *argv[])
{
    if (argc == 1) 
        isfile(".");
    else
        while (--argc > 0)          //可一次查詢多個(gè)目錄 
            isfile(*++argv);        //循環(huán)調(diào)用該函數(shù)處理各個(gè)命令行傳入的目錄

    return 0;
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容