学习了一段时间的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
部分图片非原创,侵权请告知,方便处理。