天天看點

LinuxC程式設計——實作自己的ls -R -a -l

要實作自己的ls指令首先要有自己的架構結構,完整的架構結構不至于讓你在編寫代碼的過程中突然沒了思路。我的思路如下:

  1. 首先就是解析參數,對于參數的解析,可以采用或運算來定義,比如将沒有參數定義為0,将-a定義為1,将-l定義為2,将-R定義為4等,這樣的優點實質就是采取了二進制位的或運算的優勢,對與每一位剛好代表不同的參數,這樣就可以将所有的參數儲存在一個int型的數中,當後面要檢視是否有某個參數時,隻需要判斷一下這一位是否是1,即就是在使用一次與運算來得到結果。
  2. 對于目錄的解析與參數是類似的,但是首先就是要将目錄的參數與ls的參數(-a, -l, -R)能夠區分開來,那麼一個明顯的标志就是ls指令本身的參數在之前是帶有字元 ‘-’ 的,因而我們可以很友善的将兩種參數區分開來,進而獲得路徑的參數。
  3. 對于顯示,應當是由目錄的解析開始,對于目錄,我們可以使用opendir()函數來進行讀取進而得到這一個檔案夾中檔案的名稱,并将其使用 strcat()函數連接配接在path之後,組合成為帶有檔案名的pathname,其實到這裡就等于将一個檔案的完整路徑包括檔案名在内的完整資訊的到了,然後反複利用opendir函數得到本目錄下所有的完整檔案路徑将其存入一個二維數組當中。
  4. 接下來就是顯示的工作,對于 ‘-a’ 參數和沒有參數差別在于是否顯示隐藏檔案。對于這樣的差別隻需要用一個if條件進行判斷檔案名的開頭是否是 ’ . ’ 即可。對于-l,則需要将檔案的所有資訊進行輸出,即就是在-l輸出函數内部要将檔案的讀取權限,連結數,所有者,所屬組,檔案名,時間資訊統統顯示出來,在往 ‘-l’ 資訊輸出函數的參數傳遞中就要将包含檔案明的完整路徑資訊以及檔案屬性資訊傳入到-l輸出函數中,然後在本函數中将屬性的所有資訊進行輸出即可。
  5. 最後就是-R參數的輸出,對于-R參數的最後的輸出實質依舊是利用以上的函數,隻是增加了遞歸調用,以及路徑名的改變。在這一遞歸函數中,起初也是傳入一個路徑名,然後利用與之前相同的-a将此目錄進行顯示輸出,不過,才此需要注意的是,到這裡隻是将目前路徑名下的檔案以及目錄進行了輸出,然後就是将目前path加上本檔案夾下的檔案夾的名稱組成 pathname當做path重新調用-R輸出函數,使之遞歸輸出。需要注意的是在輸出完成之後傳回上一層目錄,準備将下一個檔案夾接到path後面之前要先将之前path後面接的name删除掉,這樣每次遞歸調用傳回到上一層之後可以保證父目錄沒有被改變。

注意:

  1. 在執行輸出時有時侯打開檔案夾需要權限,是以可以再sudo下執行
  2. 在執行-R遞歸顯示時由于堆棧區大小限制,可能導緻堆棧區溢出,測試時可對低級目錄進行測試

具體代碼如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<sys/stat.h>
#include<unistd.h>
#include<sys/types.h>
#include<linux/limits.h>
#include<dirent.h>
#include<grp.h>
#include<pwd.h>
#include<errno.h>


#define PARAM_NONE 0    //無參數
#define PARAM_A 1   //-a :顯示所有檔案
#define PARAM_L 2 //-l :一行顯示一個檔案的詳細資訊
#define PARAM_r 4 //-r : 逆序排列輸出
#define MAXROWLEN 80 //一行最多顯示的字元數
void ls_R(int, char **);
int recursion(int, char *);
int g_leave_len = MAXROWLEN;    //一行剩餘長度,用于輸出對齊
int g_maxlen;   //存放某目錄下最長的檔案名的長度

//錯誤列印函數
void my_err(const char *err_string, int line)
{
    fprintf(stderr, "line:%d ", line);
    perror(err_string);
    exit();
}

//擷取檔案屬性并列印
void display_attribute(struct stat buf, char * name)
{
    char buf_time[];
    struct passwd *psd;     //從該結構體擷取檔案所有者的使用者名
    struct group *grp;      //從該結構體擷取檔案所有者所屬組的組名

    //擷取并列印檔案類型
    if (S_ISLNK(buf.st_mode))
    {
        printf("l");
    }
    else if (S_ISREG(buf.st_mode))
    {
        printf("-");
    }
    else if (S_ISDIR(buf.st_mode))
    {
        printf("d");
    }
    else if(S_ISCHR(buf.st_mode))
    {
        printf("c");
    }
    else if (S_ISBLK(buf.st_mode))
    {
        printf("b");
    }
    else if (S_ISFIFO(buf.st_mode))
    {
        printf("f");
    }
    else if(S_ISSOCK(buf.st_mode))
    {
        printf("s");
    }

    //擷取并列印檔案所有者的權限
    if(buf.st_mode & S_IRUSR)
    {
        printf("r");
    }
    else
    {
        printf("-");
    }
    if(buf.st_mode & S_IWUSR)
    {
        printf("w");
    }
    else
    {
        printf("-");
    }
    if(buf.st_mode & S_IXUSR)
    {
        printf("x");
    }
    else
    {
        printf("-");
    }

    //擷取并列印與檔案所有者同組的使用者對該檔案的操作權限
    if(buf.st_mode & S_IRGRP)
    {
        printf("r");
    }
    else
    {
        printf("-");
    }
    if(buf.st_mode & S_IWGRP)
    {
        printf("w");
    }
    else
    {
        printf("-");
    }
    if(buf.st_mode & S_IXGRP)
    {
        printf("x");
    }
    else
    {
        printf("-");
    }

    //擷取并列印其他使用者對該檔案的操作權限
    if (buf.st_mode & S_IROTH)
    {
        printf("r");
    }
    else
    {
        printf("-");
    }
    if (buf.st_mode & S_IWOTH)
    {
        printf("w");
    }
    else
    {
        printf("-");
    }
    if (buf.st_mode & S_IXOTH)
    {
        printf("x");
    }
    else
    {
        printf("-");
    }

    printf("    ");

    //根據uid與gid擷取檔案所有者的使用者名與組名
    psd = getpwuid(buf.st_uid);
    grp = getgrgid(buf.st_gid);
    printf("%4d ", (int)buf.st_nlink);   //列印檔案的連結數
    printf("%-12s", psd->pw_name);
    printf("%-12s", grp->gr_name);

    printf("%6d", (int)buf.st_size);     //列印檔案大小
    strcpy(buf_time, ctime(&buf.st_mtime));
    buf_time[strlen(buf_time) - ] = '\0';  //取掉換行符
    printf("  %s", buf_time);   //列印檔案的時間資訊
}

//在沒有使用-l選項時,列印一個檔案名,列印時保證上下對齊
void display_single(char *name)
{
    int i,len;

    //如果本行不足以列印一個檔案名則換行
    if(g_leave_len < g_maxlen)
    {
        printf("\n");
        g_leave_len = MAXROWLEN;
    }

    len = strlen(name);
    len = g_maxlen - len;
    printf("%-s", name);

    for (i = ; i < len; i++)
    {
        printf(" ");
    }
    printf("  ");
    g_leave_len -= (g_maxlen + );  //2訓示兩空格
}

//根據指令行參數和完整路徑名顯示目标檔案
//參數flag:指令行參數
//參數pathname:包含了檔案名的路徑名

void display(int flag, char *pathname)
{
    int i,j;
    struct stat buf;
    char name[NAME_MAX + ];

    //從路徑中解析出檔案名
    for(i = , j = ; i < strlen(pathname); i++)
    {
        if(pathname[i] == '/')
        {
            j = ;
            continue;
        }
        name[j++] = pathname[i];
    }
    name[j] = '\0';

    //用lstat而不是stat以友善解析連結檔案
    if(lstat(pathname, &buf) == -)
    {
        printf("權限不足!!\n");
        return ; 
        my_err("stat", __LINE__);
    }

    switch(flag)
    {
        case PARAM_NONE:    //沒有-l和-a選項
        if(name[] != '.')
        {
            display_single(name);
        }
        break;

        case PARAM_r:    //-r
        if(name[] != '.')
        {
            display_single(name);
        }
        break;

        case PARAM_A:   //-a:顯示包括隐藏檔案在内的所有檔案
        display_single(name);
        break;

        case PARAM_L:   //-l:每個檔案單獨占一行
        if(name[] != '.')
        {
            display_attribute(buf, name);
            printf(" %-s\n", name);
        }
        break;

        case PARAM_A + PARAM_L:     //同時有-a和-l選項的情況
        display_attribute(buf, name);
        printf(" %-s\n", name);
        break;

        case PARAM_r + PARAM_A:    //-ar
        display_single(name);
        break;

        case PARAM_L + PARAM_r:     //-rl
        if(name[] != '.')
        {
            display_attribute(buf, name);
            printf(" %-s\n", name);
        }
        break;

        defualt:
        break;
    }
}

void display_dir(int flag_param, char *path)
{
    DIR *dir;
    struct dirent *ptr;
    int count = ;
    char filename[][PATH_MAX+],temp[PATH_MAX + ];

    //擷取該目錄下檔案總數和最長檔案名
    dir = opendir(path);
    if (dir == NULL)
    {
        my_err("opendir", __LINE__);
    }
    while((ptr =readdir(dir)) != NULL)
    {
        if(g_maxlen < strlen(ptr->d_name))
        {
            g_maxlen = strlen(ptr->d_name);
        }
        count++;
    }
    closedir(dir);

    if(count > )
    {
        //printf("%d", count);
        my_err("too many file under this dir", __LINE__);
    }

    int i, j, len = strlen(path);
    dir = opendir(path);    //擷取該目錄下所有的檔案名
    for(i = ; i < count; i++)
    {
        ptr = readdir(dir);
        if(ptr == NULL)
        {
            my_err("readdir", __LINE__);
        }
        strncpy(filename[i], path, len);
        filename[i][len] = '\0';
        strcat(filename[i], ptr->d_name);
        filename[i][len + strlen(ptr->d_name)] = '\0';
    }
    closedir(dir);

    //使用冒泡法對檔案名進行排序,排序後檔案名按字母順序存儲與filename
    if(flag_param & PARAM_r)
    {
        for(i = ; i < count - ; i++)
        {
            for(j = ; j < count -  - i; j++)
            {
                if(strcmp(filename[j], filename[j+]) < )
                {
                    strcpy(temp, filename[j + ]);
                    temp[strlen(filename[j+])] = '\0';
                    strcpy(filename[j+], filename[j]);
                    filename[j+][strlen(filename[j])] = '\0';
                    strcpy(filename[j], temp);
                    filename[j][strlen(temp)] = '\0';
                }
            }
        }
    }
    else 
    {
        for(i = ; i < count - ; i++)
        {
            for(j = ; j < count -  - i; j++)
            {
                if(strcmp(filename[j], filename[j+]) > )
                {
                    strcpy(temp, filename[j + ]);
                    temp[strlen(filename[j+])] = '\0';
                    strcpy(filename[j+], filename[j]);
                    filename[j+][strlen(filename[j])] = '\0';
                    strcpy(filename[j], temp);
                    filename[j][strlen(temp)] = '\0';
                }
            }
        }
    }

    for(i = ; i < count; i++)
    {
        display(flag_param, filename[i]);
    }


    //如果指令行沒有-l選項,列印一個換行符          
    if((flag_param & PARAM_L) == )
    {
        printf("\n");
    }
}

int main(int argc, char **argv)
{
    int i, j, k, num;
    //char path[PARAM_NONE + 1];
    char param[];     //儲存指令行參數,目标檔案和目錄名不在此例
    int flag_param = PARAM_NONE;    //參數種類,即是否有-l和-a選項
    struct stat buf;

    char *path = (char*)malloc(PATH_MAX+);
    //指令行參數的解析,分析-l,-a,-al,-la選項
    j = ;
    num = ;
    for(i = ; i < argc; i++)
    {
        if(argv[i][] == '-')
        {
            for(k = ; k < strlen(argv[i]); k++, j++)
            {
                param[j] = argv[i][k];  //擷取'-'後面的參數儲存到數組param中
            }
            num++;      //儲存'-'的個數
        }
    }

    //隻支援a和l和r,如果含有其它選項就報錯
    for(i = ; i < j; i++)
    {
        if(param[i] == 'a')
        {
            flag_param |= PARAM_A;
            continue;
        }
        else if(param[i] == 'l')
        {
            flag_param |= PARAM_L;
            continue;
        }
        else if(param[i] == 'r')
        {
            flag_param |= PARAM_r;
            continue;
        }
        else if(param[i] == 'R')
        {
            free(path);
            ls_R(argc, argv);
        }
        else
        {
            printf("my_ls: invalid opention -%c\n", param[i]);
            exit();
        }
    }
    param[j] = '\0';

    //如果沒有輸入檔案名或目錄,就顯示目前目錄
    if((num + ) == argc)
    {
        strcpy(path, "./");
        path[] = '\0';
        display_dir(flag_param, path);
        return ;
    }

    i = ;
    do
    {
        //如果不是目标檔案名或目錄,解析下一個指令行參數
        if(argv[i][] == '-')
        {
            i++;
            continue;
        }
        else 
        {
            //printf("%d\n", i);
            //strcpy(path, argv[i]);
            strncpy(path, argv[i],strlen(argv[i])+);
            //如果目标檔案或目錄不存在,報錯并退出程式
            //printf("%d\n", i);
            //puts(path);
            if(stat(path, &buf) == -)
            {
                my_err("stat", __LINE__);
            }
            if(S_ISDIR(buf.st_mode))    //argv[i]是一個目錄
            {
                if(path[strlen(argv[i])-] != '/')  //如果目錄的最後一個字元不是'/',就加上'/'
                {
                    path[strlen(argv[i])] = '/';
                    path[strlen(argv[i]) + ] = '\0';
                }
                else
                {
                    path[strlen(argv[i])] = '\0';
                }
                display_dir(flag_param, path);
                i++;
            }
            else    //argv[i]是一個檔案
            {
                display(flag_param, path);
                i++;
            }
        }
    }while(i < argc);
    return ;
}

void ls_R(int argc, char **argv)
{
    int flag_param = ;
    struct stat buf;
    int i;
    char path[];
    for(i = ; i < argc; i++)
    {
        if(argv[i][] != '-') 
        {
            strcpy(path, argv[i]);
        }
    }
    printf("\n%s:\n", path);
    recursion(flag_param, path);
    exit();
}

int recursion(int flag, char *path)
{
    DIR *dir;
    struct dirent *ptr;     
    char pathname[];     //帶有檔案名稱的路徑
    int flag_param = ;
    struct stat buf;
    int len, i;
    display_dir(flag_param, path);
    //pathname = (char *)malloc(PATH_MAX);
    strcpy(pathname, path);
    dir = opendir(path);
    while()
    {
        ptr = readdir(dir);
        if(ptr == NULL)
        {
            closedir(dir);
            return ;
        }
        if(!(strcmp(ptr->d_name,".") && (strcmp(ptr->d_name, "..") && ptr->d_name[] != '.')))
        {
            continue;
        }
        strcat(pathname, ptr->d_name);
        lstat(pathname, &buf);
        if(S_ISDIR(buf.st_mode))
        {
            len = strlen(pathname);
            pathname[len] = '/';
            pathname[len + ] = '\0';
            printf("\n%s:\n", pathname);
            g_maxlen = ;
            g_leave_len = MAXROWLEN;
            recursion(flag, pathname);
        }
        for(i = strlen(path); i < len ; i++)
        {
            pathname[i] = '\0';
        }
    }
}
           

繼續閱讀