(笨方法)利用stat函数实现ls -l filename

学习了一段时间的Linux了,但是我感觉我做不出来啥子,后头选择利用系统IO函数实现命令,先从ls走起吧。先来看看ls -lfilename给我们显示了什么吧 :

可以看到,一共八项:文件类型、用户权限、文件硬连接数目、文件所有者、文件所属组、占用空间大小、文件修改日期、文件名。下面我们一个一个实现他们。但在此之前,我们需要了解一下ls需要用到的xitongio函数:stat();该函数的原型:int stat(const char *pathname, struct stat *buf);第一个参数是指针,指向文件名字符串。第二个是一个stat结构体。文件名字符串可以在运行的时候输入,但是stat结构体需要我们自己定义。所以我定义了一个全局变量:static struct stat st;接下来我们来看看stat中包含了哪些内容:

(笨方法)利用stat函数实现ls -l filename

可以知道,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是文件类型和文件存取的权限;它一个变量包含了多个信息,那么它自身就不简单了,我们可以继续看:

(笨方法)利用stat函数实现ls -l filename

可以看到,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("符号链接文件 ");

}

elseif (S_ISREG(st.st_mode))

{

printf("一般文件 ");

}

elseif (S_ISDIR(st.st_mode))

{

printf("目录文件 ");

}

elseif (S_ISCHR(st.st_mode))

{

printf("字符设备文件 ");

}

elseif (S_ISBLK(st.st_mode))

{

printf("块设备文件 ");

}

elseif (S_ISSOCK(st.st_mode))

{

printf("socket文件 ");

}

elseif (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文件内容是什么样的:(笨方法)利用stat函数实现ls -l filename

可以知道,每一行当中,用户名、用户id、组id之间有":"分隔开的。知道这个之后,我们就可以获取了。函数实例:

void File_owner(intid)//这样设计是用来找到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(intid)//这样设计是用来找到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(intnum, char*str, intradix)

{/*索引表*/

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;

}

returnstr;

}

接下来的工作就很简单了,直接在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(intid);

void Group_of_files(void);

void File_size(void);

void File_modification_time(void);

void File_name(char *file);

char* itoa(intnum, char *str, intradix);//进制转换

staticstructstat st;

int main(inta, 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

部分图片非原创,侵权请告知,方便处理。

相关推荐