1. 程式人生 > >(笨方法)利用stat函數實現ls -l filename

(笨方法)利用stat函數實現ls -l filename

一個 case break 基本上 說明 方式 恢復 bcd 只讀

學習了一段時間的Linux了,但是我感覺我做不出來啥子,後頭選擇利用系統IO函數實現命令,先從ls走起吧。先來看看ls -l filename給我們顯示了什麽吧 :

可以看到,一共八項:文件類型、用戶權限、文件硬連接數目、文件所有者、文件所屬組、占用空間大小、文件修改日期、文件名。下面我們一個一個實現他們。但在此之前,我們需要了解一下ls需要用到的xitongio函數:stat();該函數的原型:int stat(const char *pathname, struct stat *buf);第一個參數是指針,指向文件名字符串。第二個是一個stat結構體。文件名字符串可以在運行的時候輸入,但是stat結構體需要我們自己定義。所以我定義了一個全局變量:static struct stat st;接下來我們來看看stat中包含了哪些內容:

技術分享圖片

可以知道,ls -l需要的基本上都有。現在我們來聲明函數

void file_typeAnd_permissions(char *file);//獲取文件類型和文件權限
void File_hard_connection_number(void);//文件的硬連接數目
void File_owner(int id);//文件所有者
void Group_of_files(void);//文件所屬組
void File_size(void);//文件大小
void File_modification_time(void);//文件修改時間
void File_name(char *file);//文件名
char* itoa(int num,char *str,int radix);//進制轉換,我們需要將st_mode轉化為8進制數

剛才我提到了一個st_mode,由上面的圖片我們知道,st_mode是文件類型和文件存取的權限;它一個變量包含了多個信息,那麽它自身就不簡單了,我們可以繼續看:

技術分享圖片

可以看到,st_mode一共有16位。那麽我們怎麽知道這個文件的類型呢?沒事,Linux早就定義好了一些宏(在man文檔中可以找到)供我們使用,接下來上函數:

void file_typeAnd_permissions(char *file)

{

char num[16] = { ‘\0‘ };

int s_ret = stat(file, &st);

itoa((int)st.st_mode, num, 8);//將st.st_mode轉化為八進制數,用於判斷權限。

//註意,itoa函數b並不是c標準庫函數,在其他編譯平臺或許支持,但是gcc就是不認它。我們可以編寫一個itoa函數,而他的用法可以百度一下,或者看聲明

//註:此函數並不由我編寫,我是去百度百科上面找的

// printf(" st_mode=%o num=%s ",st.st_mode,num);

// printf("sizeof(num)=%lu",sizeof(num));

if (-1 == s_ret)//做失敗處理措施

{

perror("stat");

exit(1);

}

//利用宏來判斷文件類型

if (S_ISLNK(st.st_mode))

{

printf("符號鏈接文件 ");

}

else if (S_ISREG(st.st_mode))

{

printf("一般文件 ");

}

else if (S_ISDIR(st.st_mode))

{

printf("目錄文件 ");

}

else if (S_ISCHR(st.st_mode))

{

printf("字符設備文件 ");

}

else if (S_ISBLK(st.st_mode))

{

printf("塊設備文件 ");

}

else if (S_ISSOCK(st.st_mode))

{

printf("socket文件 ");

}

else if (S_ISFIFO(st.st_mode))

{

printf("管道文件 ");

}

//判斷權限

int bit_num = 3;//控制在i的基礎上加幾位.我在查看一個目錄的時候,發現st_mode轉化的八進制數只有5位,這是前面有一位被轉化之後出現的合並。具體原因可以百度,這裏不做解釋。畢竟st_mode的是十六位

bit_num -= (6 - strlen(num));

for (int i = 0; i != 3; i++)

{

switch (num[bit_num + i])

{

case ‘0‘:printf("無權限 "); break;

case ‘1‘:printf("執行 "); break;

case ‘2‘:printf("寫 "); break;

case ‘3‘:printf("執行寫 "); break;

case ‘4‘:printf("讀 "); break;

case ‘5‘:printf("執行讀 "); break;

case ‘6‘:printf("讀寫 "); break;

case ‘7‘:printf("執行讀寫"); break;

default:printf("該位對應的值:%d。無匹配操作! ", num[3 + i]);

}

}

}

如何查看權限,我是將st_mode轉化為八進制數,然後查找後三位來實現的。其中用到的itoa函數不是c標準庫中的函數,在Linux中使用gcc的話不會認可它的,所以我就自己編寫了一個。strlen函數需要頭文件string.h。由於st_mode中包含了文件類型和文件權限。所以我們就一個函數解決兩個問題了,但是我還是建議不要這樣,一個函數最好是解決一個問題。接下來就是硬鏈接數目了,但是要註意,目錄的硬連接數目初始為0;文件的硬鏈接數目初始為1;

void File_hard_connection_number(void)

{

printf("%d", (int)st.st_nlink);

}

這個比較簡單,直接輸出就是。然後我們來看看所屬用戶和所屬組了,我們能直接用stat函數獲取到用戶id(uid)和組id(gid),但是這並不是我們想要的,我們還是習慣於看字符串。但是id和passwd文件有對應關系,passwd文件在/etc目錄中。我們來看看psswd文件內容是什麽樣的:技術分享圖片

可以知道,每一行當中,用戶名、用戶id、組id之間有":"分隔開的。知道這個之後,我們就可以獲取了。函數實例:

void File_owner(int id)//這樣設計是用來找到uid和gid一樣的字符串

{

//知道了用戶id之後,我們就可以到目錄/etc中去找到文件passwd找尋對應的用戶名和組名

FILE *fd = fopen(" / etc / passwd", "r"); //以只讀方式打開passwd文件,也可以用系統io函數open,read函數的,但是不大好控制,所以就選擇了c庫的文件操作函數

int i = 0; //用於標記讀取到了幾個‘:‘,好做判斷。至於為什麽要這麽做,可以打開passwd文件看看每一行的格式就明白了。

int Break = 0; //當我們在文件中間位置找到了我們需要的數據後,用於控制循環退出的一個變量

char pass[512] = { 0 }; //存儲每一行的數據

char p[30] = { ‘\0‘ }; //用於存儲id,與id對比

// printf("uid=%d gid=%d ",st.st_uid,st.st_gid);

if (NULL == fd)//處理文件操作出錯的代碼必不可少,也要記得,打開就要記得關閉

{

printf("打開文件失敗\n");

exit(1);

}

while ((fgets(pass, 500, fd)) && (!Break))//gfgets函數用於讀取一行數據,具體的用法,可百度

{

for (int j = 0, i_ = 0; i_ != 70; i_++)

{

//j控制p

if (‘:‘ == pass[i_])

{

i++;

}

if (2 == i)//此時意味著我們找到了當前行的id

{

if (‘:‘ != pass[i_])

{

p[j] = pass[i_];

j++;

}

}

if (3 == i)

{

// printf("進入");

int a = atoi(p);//將字符串轉化為數字的一個函數

// printf("a=%d p=%s ",a,p);

if (a == id)//若用戶id和id相等,此時我們就要舍棄p原有的數據,然後用來存儲用戶(組)名了

{

// printf("進入");

for (int i = 0; i != 30; i++)//將p恢復

{

p[i] = ‘\0‘;

}

Break = 1;

int i = 0;

while (pass[i] != ‘:‘)

{

p[i] = pass[i];

i++;

}

printf("%s ", p);//然後輸出用戶(組)名

break;

}

}

}

i = 0;//在此行沒找到

for (int i = 0; i != 30; i++)//沒找到,為了避免在找到之前有的idb‘ni‘wo‘men比我們的id長,那麽必須進行清除操作

{

p[i] = ‘\0‘;

}

}

fclose(fd);

}

void Group_of_files(void)

{

//當用戶id和組id一樣的時候,那麽用戶名和組名一樣,若是uid不等於gid那麽我我們就要去找尋uid和此時的gid一樣的用戶。這也是為何上一個函數要有一個id參數的原因

if (st.st_uid == st.st_gid)

{

File_owner(st.st_uid);

}

else

{

File_owner(st.st_gid);

}

}

//oh,真的,我去試過了strtok,但是我總是得到一個段錯誤。這讓我很是氣餒。不過還好,我找到錯誤了,真的,這個錯誤讓我很心累:就是我錯誤的把:弄成了; 先上代碼:

void File_owner(int id)//這樣設計是用來找到uid和gid一樣的字符串

{

//知道了用戶id之後,我們就可以到目錄/etc中去找到文件passwd找尋對應的用戶名和組名

FILE *fd = fopen(" / etc / passwd", "r"); //以只讀方式打開passwd文件,也可以用系統io函數open,read函數的,但是不大好控制,所以就選擇了c庫的文件操作函數

int i = 0; //用於標記讀取到了幾個‘:‘,好做判斷。至於為什麽要這麽做,可以打開passwd文件看看每一行的格式就明白了。

int Break = 1; //當我們在文件中間位置找到了我們需要的數據後,用於控制循環退出的一個變量

char pass[] = { 0 }; //存儲每一行的數據

char *p_1; //用於存儲用戶名

char *p_2; //用於存儲不需要的字符串

char *p_3; //存儲uid,與id參數對比

if (NULL == fd)//處理文件操作出錯的代碼必不可少,也要記得,打開就要記得關閉

{

printf("打開文件失敗\n");

exit(1);

}

//char *strtok(char s[], const char *delim);//strtok的原型

while (fgets(pass, , fd))

{

p_1 = strtok(pass, ":");

p_2 = strtok(NULL, ":");

p_3 = strtok(NULL, ":");

int pid = atoi(p_3);

if (pid == id)

{

printf("%s ", p_1);

break;

}

}

}

strtok函數的使用不大一樣,使用方法見谷歌百度。這裏主要說明幾點:

  1. s參數必須設置為數組的形式,而不是字符串常量(如:char *str="2,張三,89,99,66″;),因為strtok在執行過程中會對str進行修改,必須保證str是可寫的。
  2. 該函數實際上對參數s的操作:第一次使用該函數的時候將在參數s中向後掃描,找出所有的delim,依次把他們替換為NULL(‘\0‘)。
  3. 第一次它會返回第一個delim前面的字符串,第二次需要讓他返回第二個串的話就要將參數s置為NULL。

用庫函數明顯優於我們自己造輪子,以後還是盡可能的使用庫函數。我獲取組名是依靠了它:void File_owner(int id)的,我的想法是,uid=gid的時候,用戶名和組名就相同,可以理解為,此時的用戶名就是組名;獲取組名的時候,若是uid=gid,那麽好說;若不是,那麽我們就要去找到另一個uid等於我們的st_gid的用戶名。

下面就是文件的大小了,很簡單,這個:

void File_size(void)//size的大小可以直接輸出。

{

printf("%d ", (int)st.st_size);

}

然後是文件的修改日期,要註意,這裏使用時間函數,需要頭文件time.h

void File_modification_time(void)//輸出時間需要頭文件time.h。

{

char *my_time = ctime(&st.st_mtime);//ctime函數會自動在字符串後面加上一個換行符。但是這不符合ls -ld命令的輸出形式。所以要做處理

int i = 0;

while (my_time[i] != ‘\n‘)

{

i++;

}

my_time[i] = ‘\0‘;

// printf("%lu\n",st.st_mtime);//打印time_th格式的時間,s單位為s

printf("%s ", my_time);//打印ctimeh格式的時間,為字符串

// printf("%s\n",asctime(localtime(&st.st_mtime)));//打印sasctime格式的時間,這行和上一行效果一樣

}

幾種時間函數我已經試過了,就看大家使用什麽了。

然後是輸出文件名,這也簡單,不過要記住輸出的時候就該換行了:

void File_name(char *file)

{

printf("%s\n", file);

}

itoa函數的實現我還是貼出來吧,要註意的是,itoa函數不是C標準庫的函數,gcc不認它:

char* itoa(int num, char*str, int radix)

{/*索引表*/

char index[] = "0123456789ABCDEF";

unsigned unum;/*中間變量*/

int i = 0, j, k;

/*確定unum的值*/

if (radix == 10 && num<0)/*十進制負數*/

{

unum = (unsigned)-num;

str[i++] = ‘ - ‘;

}

else unum = (unsigned)num;/*其他情況*/

/*轉換*/

do {

str[i++] = index[unum % (unsigned)radix];

unum /= radix;

} while (unum);

str[i] = ‘\0‘;

/*逆序*/

if (str[0] == ‘ - ‘)k = 1;/*十進制負數*/

else k = 0;

char temp;

for (j = k; j <= (i - 1) / 2; j++)

{

temp = str[j];

str[j] = str[i - 1 + k - j];

str[i - 1 + k - j] = temp;

}

return str;

}

接下來的工作就很簡單了,直接在main函數中調用就是:

#include <stdio.h>

#include <time.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <unistd.h>

//int stat(const char *file_name, struct stat *buf )函數原型

void file_typeAnd_permissions(char *file);

void File_hard_connection_number(void);

void File_owner(int id);

void Group_of_files(void);

void File_size(void);

void File_modification_time(void);

void File_name(char *file);

char* itoa(int num, char *str, int radix);//進制轉換

static struct stat st;

int main(int a, char *file[])

{

if (a<2)

{

printf("參數過少!\n");

exit(1);

}

file_typeAnd_permissions(file[1]);

File_hard_connection_number();

File_owner(st.st_uid);

Group_of_files();

File_size();

File_modification_time();

File_name(file[1]);

return 0;

}

好了,接下來就編譯他們就是了:gcc my_LS.c -o LS

使用 ./LS my_LS.c

部分圖片非原創,侵權請告知,方便處理。

(笨方法)利用stat函數實現ls -l filename