(笨方法)利用stat函數實現ls -l filename
學習了一段時間的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函數的使用不大一樣,使用方法見谷歌百度。這裏主要說明幾點:
- s參數必須設置為數組的形式,而不是字符串常量(如:char *str="2,張三,89,99,66″;),因為strtok在執行過程中會對str進行修改,必須保證str是可寫的。
- 該函數實際上對參數s的操作:第一次使用該函數的時候將在參數s中向後掃描,找出所有的delim,依次把他們替換為NULL(‘\0‘)。
- 第一次它會返回第一個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