linux编程-编写ls指令

131 阅读6分钟

编写ls命令

Is的默认动作是找出当前目录中所有文件的文件名,按字典序排序后输出。Is做了以下两件事:

  • 列出目录的内容
  • 显示文件的信息

注意Is对文件和目录所做的操作是不同的。Is能判定参数指定的是文件还是目录。

如果要自己写一个Is,以下三点是需要掌握的:

  • 如何列出目录的内容
  • 如何读取并显示文件的属性
  • 给出一个名字,如何能够判断出它是目录还是文件

目录是一种特殊的文件,它的内容是文件和目录的名字。与普通文件不同的是,目录文件永远不会空,每个目录都至少包含两个特殊的项——"和其中"表示当前目录,".."表示上一级目录。

  • 文件与目录:Unix将数据存放于文件中,目录是特殊的文件,它的内容就是其中的文件和子目录的名字,还包含自己的名字,Unix提供一系列函数对目录操作,打开、读、定位、关闭等,但不提供写目录的函数。
  • 用户与组:系统中的每个用户都有用户名和用户ID,人们可以用用户名登录到系统。系统通过UID来区分不同用户的文件。每个用户都属于至少一个组。每个组都有组名和组ID。
  • 文件属性:每个文件都有很多属性,程序通过系统调用stat来得到文件的属性。-
  • 文件的所有关系:每个文件都有文件所有者,文件所有者的ID是文件属性的一部分。同样的每个文件都属于某个组,组ID也是文件属性的一部分。
  • 许可权限:文件的许可权限规定了哪种用户可以进行哪些操作,有3种用户;文件所有者、同组用户和其他用户,3种操作:读、写和执行。

目录API

从目录读数据与从文件读数据是类似的,opendir打开一个目录,readdir返回目录中的当前项,closedir关闭一个目录,seekdir、telldir、rewinddir与Iseek的功能类似

目录是文件的列表,更确切地说,是记录的序列,每条记录对应一个文件或子目录。通过readdir来读取目录中的记录,readdir返回一个指向目录的当前记录的指针,记录的类型是structdirent,这个结构定义在/usr/include/dirent.h中,位于头文件<dirent.h>中

Is要做两件事情,一是列出目录的内容,二是显示文件的详细信息,这实际上是两件不同的工作,目录包含文件名,文件信息则需要从另外的途径获得

提取文件状态stat()

#include <sys/stat.h>
int result=stat(char * fname, struct stat * bufp)
  • fname 文件名
  • bufp 指向buffer的指针

stat把文件fname的信息复制到指针bufp所指的结构中。

struct stat infobuf ;	/* place to store info */
if ( stat( "7etc/passwd",&infobuf) == - 1 )	/* get info */
perror("etc/passwd");
else
printf ( " The size of /etc/passwd is % d\n",infobuf.st_size);

stat把文件的信息复制到结构infobuf中,程序从成员变量st_size中读到文件大小。

stat的联机帮助和头文件/usr/include/sys/stat.h描述了structstat的成员变量:

  • st_mode 文件类型和许可权限
  • st_uid 用户所有者的ID
  • st_gid 所属组的ID
  • st_size 所占的字节数
  • st_nlink 文件链接数
  • st_mtime 文件最后修改时间
  • st_atime 文件最后访问时间
  • st_ctime 文件属性最后改变时间,time_t类型,可以用ctime将其转化成字符串

使用掩码

st_mode是一个16位的二进制数,文件类型和权限被编码在这个数中。其中前4位用作文件类型,最多可以标识16种类型,目前已经使用了其中的7个。接下来的3位是文件的特殊属性,1代表具有某个属性,0代表没有。最后的9位是许可权限,分为3组,对应3种用户,它们是文件所有者、同组用户和其他用户。其他用户指与用户不在同一个组的人。每组3位,分别是读、写和执行的权限。相应的地方如果是1,就说明该用户拥有对应的权限,0代表没有。

为了比较,把不需要的地方置0,这种技术称为掩码(masking),就如同带上面具把其他部位都遮起来,就只留下眼睛在外面。这里用一系列掩码来把st_mode的值转化成Is-1要显示的字符串。掩码可以高效的表示信息,毕竟只有1bit,经常进行位操作来获取信息

子域编码(subfield coding)是系统编程中一种重要且常用的技术,以下从四方面详细介绍子域编码与掩码。

  • 掩码的概念:掩码会将不需要的字段置0,需要的字段的值不发生改变。
  • 整数是bit组成的序列:整数在计算机中是以bit序列的形式存在的
  • 掩码技术:与0作位与(&)操作可以将相应的bit置为0
  • 使用八进制数:直接处理二进制数是很枯燥乏味的。如同处理一长串十进制数时人们常将它们三位一组分开(如23,234,456,022)一样,一种简化的方法是将二进制数每三位分为一组来操作,这就是八进制数(0至7)。

文件类型在模式字段的第一个字节的前四位,可以通过掩码来将其他的部分置0,从而 得到类型的值。在<sys/stat. h>中有以下定义:

# define	S_IFMT	0170000	/* type of file */
# define	S_IFREG	0100000	/* reguleir */
# define	S_IFDIR	0040000	/* directory */
# define	S_IFBLK	0060000	/* block special */
# define	S_IFCHR	002000Q	/* character special */
# define	S_IFIFO	0010000	/* fifo */
# define	S_IFLNK	0120000	/* symbolic link */
#define	S_IFSOCK	0140000	/* socket */

S_IFMT是一个掩码,它的值是0170000,可以用来过滤岀前四位表示的文件类型。S_IFREG代表普通文件,值是0100000,SJFDIR代表目录文件,值是0040000。下面的代码:

if ((info. st_mode & 0170000) == 0040000 ) printf("this is a directory");

通过掩码把其他无关的部分置0,再与表示目录的代码比较,从而判断这是否是一个 目录。更简单的方法是用Vsys/stat. h>中的宏来代替上述代码:

#define	__S_ISTYPE(mode, mask)	(((mode) & __S_IFMT) == (mask))

#define	S_ISDIR(mode)	 __S_ISTYPE((mode), __S_IFDIR)
#define	S_ISCHR(mode)	 __S_ISTYPE((mode), __S_IFCHR)
#define	S_ISBLK(mode)	 __S_ISTYPE((mode), __S_IFBLK)
#define	S_ISREG(mode)	 __S_ISTYPE((mode), __S_IFREG)
#ifdef __S_IFIFO
# define S_ISFIFO(mode)	 __S_ISTYPE((mode), __S_IFIFO)
#endif
#ifdef __S_IFLNK
# define S_ISLNK(mode)	 __S_ISTYPE((mode), __S_IFLNK)
#endif

使用宏的话就可以这样写代码:

if (S_ISDIR(info.st_mode)) printf("this is a directory");

模式字段的最低9位是许可权限,它标识了文件所有者、组用户、其他用户的读、写、执行权限。Is将这些位转换为短横和字母的串。在<sys/stat. h>中每一位都有相应的掩码,下 面的代码给出了如何使用的例子:

        //文件权限,usr - group - other
        res += (mode & S_IRUSR == 0) ? '-' : 'r';
        res += (mode & S_IWUSR == 0) ? '-' : 'w';
        res += (mode & S_IXUSR == 0) ? '-' : 'x';

        res += (mode & S_IRGRP == 0) ? '-' : 'r';
        res += (mode & S_IWGRP == 0) ? '-' : 'w';
        res += (mode & S_IXGRP == 0) ? '-' : 'x';
        
        res += (mode & S_IROTH == 0) ? '-' : 'r';
        res += (mode & S_IWOTH == 0) ? '-' : 'w';
        res += (mode & S_IXOTH == 0) ? '-' : 'x';

用户/组信息pwd.h/grp.h

头文件pwd.h/grp.h中的getpwuid/getgrgid可以通过查找/etc/passwd或NIS返回uid和gid所对应的用户信息/组信息

#include <pwd.h>
#include <grp.h>
const char *uid_to_name(uid_t uid)
{
    return getpwuid(uid)->pw_name;
}
const char *gid_to_name(gid_t gid)
{
    return getgrgid(gid)->gr_name;
}

代码

3_ls.h

#include <string>
#ifndef _3_LS_
#define _3_LS_
//----------------------------------------------------------------
void ls(const char *dirname);
void do_ls(const char *dirname);
std::string get_stat_info(const char *filename);
const char *uid_to_name(uid_t uid);
const char *gid_to_name(gid_t gid);
#endif

3_ls.cpp

#include <sys/types.h>
#include <dirent.h>
#include <iostream>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include "3_ls.h"
using namespace std;

/**
 * @brief ls 只接受单个参数,如ls -l
 * 
 * @param dirname 
 */
void ls(const char *dirname)
{

    if (dirname == NULL)
    {
        do_ls(".");
    }
    else
    {
        do_ls(dirname);
    }
}
void do_ls(const char *dirname)
{
    DIR *dir_ptr;           //目录指针
    struct dirent *direntp; //目录信息
    //打开读取
    if ((dir_ptr = opendir(dirname)) == NULL)
    {
        perror("opendir error");
    }
    else
    {
        while ((direntp = readdir(dir_ptr)) != NULL)
        {
            cout << direntp->d_name << get_stat_info(direntp->d_name) << endl;
        }
    }
}

string get_stat_info(const char *filename)
{
    struct stat info;
    string res = " ";
    if (stat(filename, &info) != -1)
    {
        //模式信息
        mode_t mode = info.st_mode;
        //文件类别
        if (S_ISDIR(mode))
        {
            res += "dir ";
        }
        else if (S_ISREG(mode))
        {
            res += "file ";
        }
        else
        {
            res += "unknown ";
        }
        //文件权限,usr - group - other
        res += (mode & S_IRUSR == 0) ? '-' : 'r';
        res += (mode & S_IWUSR == 0) ? '-' : 'w';
        res += (mode & S_IXUSR == 0) ? '-' : 'x';

        res += (mode & S_IRGRP == 0) ? '-' : 'r';
        res += (mode & S_IWGRP == 0) ? '-' : 'w';
        res += (mode & S_IXGRP == 0) ? '-' : 'x';

        res += (mode & S_IROTH == 0) ? '-' : 'r';
        res += (mode & S_IWOTH == 0) ? '-' : 'w';
        res += (mode & S_IXOTH == 0) ? '-' : 'x';
        //用户名与组名
        res += " ";
        res += uid_to_name(info.st_uid);
        res += " ";
        res += gid_to_name(info.st_gid);
        //大小
        res += " ";
        res += info.st_size;
        res += " ";
        //时间
        res += ctime(&info.st_mtim.tv_sec);
    }
    return res;
}
/**
 * @brief 获取用户名与组名
 * 
 * @param uid 
 * @return const char* 
 */
const char *uid_to_name(uid_t uid)
{
    return getpwuid(uid)->pw_name;
}
const char *gid_to_name(gid_t gid)
{
    return getgrgid(gid)->gr_name;
}

image.png

内容来源

  1. Unix/Linux编程实践教程
  2. github.com/LSLWind/lin…