刚好把 Linux/UNIX 编程中的文件和IO部分学完了,就想编写个 ls 命令练习一下,本以为很简单,调用个 stat 就完事了,没想到前前后后弄了七八个小时,90%的时间都用在格式化(像 ls -l 中的对齐)输出了,反反复复改了好几遍。

一共实现了常用的四个选项:-a -h -l -d。可以从命令行参数中同时接受多个目录和文件,然后分开输出。

演示:

-a 命令:

-l 和 -h 命令:

-d 命令:

参数同时接受多个文件和目录名:

思路:

先使用 getopt 解析选项

然后判断参数有没有目录或文件(通过 operand 标志变量),没有的话仅输出进程当前目录

有的话将参数中的文件和目录分开(files 和 dirs数组),然后输出信息

因为要格式化输出,所以得将目录中所有项目读取完成后并转换成字符串形式(get_stat_str)才能获得长度最长的某个信息(如,文件名,尺寸),而目录中的文件数目又事先不可知,所以用链表结构(stat_str)将所有目录中所有文件的信息存储下来。

代码:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <unistd.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <pwd.h>
#include <grp.h>
#define err_exit(func) {perror(func); exit(EXIT_FAILURE);} //打印出错函数,并结束程序
#define max(a, b) ((a) > (b) ? (a) : (b)) typedef struct stat_str{ //字符串形式的stat信息
char fname[NAME_MAX]; //大概长度,不严谨
char user[20];
char group[20];
char size[16];
char time[13];
char mode[11];
char nlink[5];
struct stat_str *next; //链表结构,因为需要预先获得目录里的所有项(为了控制格式化),但数目不确定,所以用链表保存
} st_str; int nlink_maxlen = 0, user_maxlen = 0, group_maxlen = 0, size_maxlen = 0; //字符串形式的最大长度,为了格式化构造字符串
int ARG_L = 0, ARG_A = 0, ARG_H = 0, ARG_D = 0; //参数是否定义 //解析选项
void analyse_opt(int argc, char* argv[]);
//将文件大小(字节数)转换成易读(human readable)的形式
void human_readable(off_t bytes, char *szbuf);
//构造文件的详细信息
void build_line(const st_str *stat_strbuf, char* fmtstrbuf);
//打印目录信息
void print_dir_info(const char *dir);
//获得文件信息的str形式
void get_stat_str(const struct stat* stbuf, const char* fname, st_str *strbuf); int main(int argc, char* argv[]) {
struct stat stbuf;
st_str st_strbuf;
int no_operand = 1; //是否没有操作数
analyse_opt(argc, argv); //解析选项 //分别获得目录和其他文件,为了格式化输出
char *files[argc-1], *dirs[argc-1];
int nf = 0, nd = 0;
for(int i = 1; i < argc; i++){
if(argv[i][0] == '-') //跳过选项
continue;
else
no_operand = 0;
if(-1 == lstat(argv[i], &stbuf))
err_exit("lstat");
if(S_ISDIR(stbuf.st_mode))
dirs[nd++] = argv[i];
else
files[nf++] = argv[i];
} if(no_operand){
//命令行没有输入路径
print_dir_info(".");
} else {
//先列出文件的信息
for(int i = 0; i < nf; i++){
char fmtstrbuf[256];
if(-1 == lstat(files[i], &stbuf))
err_exit("lstat");
get_stat_str(&stbuf, files[i], &st_strbuf);
if(ARG_L){
build_line(&st_strbuf, fmtstrbuf);
puts(fmtstrbuf);
} else {
puts(files[i]);
}
}
//再列出目录的信息
for(int i = 0; i < nd; i++){
if(nf > 0)
printf("\n%s:\n", dirs[i]);
print_dir_info(dirs[i]);
}
}
return 0;
} void analyse_opt(int argc, char* argv[]){
int opt;
while((opt = getopt(argc, argv, "lahd")) != -1){
switch (opt) {
case 'l':
ARG_L = 1;
break;
case 'a':
ARG_A = 1;
break;
case 'h':
ARG_H = 1;
break;
case 'd':
ARG_D = 1;
break;
}
}
} void human_readable(off_t nbytes, char *szbuf){
if(nbytes < 1024)
sprintf(szbuf, "%ld\0", nbytes);
else if(nbytes < 1024 * 1024)
sprintf(szbuf, "%.1lfK\0", (double)nbytes / 1024);
else if(nbytes < 1024 * 1024 * 1024)
sprintf(szbuf, "%.1lfM\0", (double)nbytes / 1024 / 1024);
else
sprintf(szbuf, "%.1lfG\0", (double)nbytes / 1024 / 1024 / 1024);
} void get_stat_str(const struct stat* stbuf, const char* fname, st_str *strbuf){
//mode
sprintf(strbuf->mode, "%c%c%c%c%c%c%c%c%c%c",
S_ISREG(stbuf->st_mode) ? '-' : (
S_ISDIR(stbuf->st_mode) ? 'd' : (
S_ISBLK(stbuf->st_mode) ? 'b' : (
S_ISCHR(stbuf->st_mode) ? 'c' : 'l'
)
)
),
(S_IRUSR & stbuf->st_mode ) ? 'r' : '-',
(S_IWUSR & stbuf->st_mode ) ? 'w' : '-',
(S_IXUSR & stbuf->st_mode ) ? 'x' : '-',
(S_IRGRP & stbuf->st_mode ) ? 'r' : '-',
(S_IWGRP & stbuf->st_mode ) ? 'w' : '-',
(S_IXGRP & stbuf->st_mode ) ? 'x' : '-',
(S_IROTH & stbuf->st_mode ) ? 'r' : '-',
(S_IWOTH & stbuf->st_mode ) ? 'w' : '-',
(S_IXOTH & stbuf->st_mode ) ? 'x' : '-' );
//nlink
sprintf(strbuf->nlink, "%ld\0", stbuf->st_nlink);
nlink_maxlen = max(nlink_maxlen, strlen(strbuf->nlink));
//user, group
sprintf(strbuf->user, "%s\0", getpwuid(stbuf->st_uid)->pw_name);
sprintf(strbuf->group, "%s\0", getgrgid(stbuf->st_gid)->gr_name);
user_maxlen = max(user_maxlen, strlen(strbuf->user));
group_maxlen = max(group_maxlen, strlen(strbuf->group));
//size
if(ARG_H){
char szbuf[16];
human_readable(stbuf->st_size, szbuf);
sprintf(strbuf->size, "%s\0", szbuf);
} else {
sprintf(strbuf->size, "%ld\0", stbuf->st_size);
}
size_maxlen = max(size_maxlen, strlen(strbuf->size));
//time
strftime(strbuf->time, 13, "%b %d %H:%M\0", localtime(&(stbuf->st_mtime)));
//fname
sprintf(strbuf->fname, "%s\0", fname); } void build_line(const st_str *stat_strbuf, char* fmtstrbuf){
char fmt[32];
sprintf(fmt, "%%s %%%ds %%-%ds %%-%ds %%%ds %%s %%s\0", nlink_maxlen, user_maxlen, group_maxlen, size_maxlen);
// puts(fmt);
if((stat_strbuf->mode)[0] == 'd')
strcat(stat_strbuf->fname, "/");
sprintf(fmtstrbuf, fmt, stat_strbuf->mode, stat_strbuf->nlink, stat_strbuf->user,
stat_strbuf->group, stat_strbuf->size, stat_strbuf->time, stat_strbuf->fname);
} void print_dir_info(const char *dir){
if(ARG_D){
//显式目录本身的信息
st_str stat_strbuf;
struct stat stbuf;
char fmtstrbuf[256];
if(-1 == lstat(dir, &stbuf))
err_exit("lstat");
get_stat_str(&stbuf, dir, &stat_strbuf);
if(ARG_L){
build_line(&stat_strbuf, fmtstrbuf);
puts(fmtstrbuf);
} else {
puts(stat_strbuf.fname);
}
} else {
group_maxlen = nlink_maxlen = user_maxlen = size_maxlen = 0;
//列出目录所有项
struct DIR *pdir = opendir(dir);
if(pdir == NULL)
err_exit("opendir");
struct dirent *pdirent;
struct stat stbuf;
st_str * head_st_str = (st_str*)(malloc(sizeof(st_str))), *p = head_st_str; //链表头(字符串形式的stat)
//循环都目录
errno = 0;
while((pdirent = readdir(pdir)) != NULL){
if((pdirent->d_name)[0] != '.' || ARG_A) {
//是否显示隐藏文件
if(ARG_L) {
char path[256];
strcpy(path, dir); //!!!!! 找了一个多小时才找出来这个错误
strcat(path, "/"); //!!!!! d_name仅是个文件名而已
strcat(path, pdirent->d_name); //!!!!! 需要加上完整路径
if(-1 == lstat(path, &stbuf)){
err_exit("lstat");
}
p->next = (st_str*)(malloc(sizeof(st_str)));
p = p->next;
p->next = NULL;
get_stat_str(&stbuf, pdirent->d_name, p);
} else {
puts(pdirent->d_name);
}
}
}
if(errno != 0)
err_exit("readdir"); //输出信息链表的格式化内容
p = head_st_str->next;
while(ARG_L && p){
char fmtstrbuf[256];
build_line(p, fmtstrbuf);
puts(fmtstrbuf);
p = p->next;
}
if(-1 == closedir(pdir))
err_exit("closedir");
st_str *q = head_st_str->next; //释放链表
while(q){
free(head_st_str);
head_st_str = q;
q = q->next;
}
free(head_st_str);
}
}

  

Linux/UNIX编程:使用C语言实现简单的 ls 命令的更多相关文章

  1. 学习linux/unix编程方法的建议(转)

    假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过 我想大概可以分为4个阶段,水平从低到高从安装使用=>linux常用命令=>linux ...

  2. 学习linux/unix编程方法的建议,学习Linux的四个步骤(转)

    解答:学习Linux的四个步骤假设你是计算机科班出身,计算机系的基本课程如数据结构.操作系统.体系结构.编译原理.计算机网络你全修过我想大概可以分为4个阶段,水平从低到高从安装使用=>linux ...

  3. Linux/UNIX编程如何保证文件落盘

    本文转载自Linux/UNIX编程如何保证文件落盘 导语 我们编写程序write数据到文件中时,其实数据不会立马写入磁盘,而是会经过层层缓存.每层缓存都有自己的刷新时机,每层缓存都刷新后才会写入磁盘. ...

  4. Linux用户应知应会的7个‘ls’命令的独特技巧

    在前面我们系列报道的两篇文章中,我们已经涵盖了关于‘ls’命令的绝大多数内容.本文时‘ls命令’系列的最后一部分.如果你还没有读过该系列的其它两篇文章,你可以访问下面的链接. Linux中的15个基本 ...

  5. Linux/UNIX编程:实现简单 tee 命令

    思路很简单,从标准输入文件描述符读入数据,然后同时向标准输出和参数指定的文件写出数据:如果加了 -a 选项,则以追加的方式向文件写出数据.还没了解 getopt() 函数就没判断参数是否合法. #in ...

  6. Linux/Unix编程中的线程安全问题【转】

    转自:http://blog.csdn.net/zhengzhoudaxue2/article/details/6432984 在目前的计算机科学中,线程是操作系统调度的最小单元,进程是资源分配的最小 ...

  7. linux socket 编程(C语言)

    转自:http://blog.csdn.net/piaojun_pj/article/details/5920888 最近看了一些网络编程的书籍,一直以来总感觉网络编程神秘莫测,其实网络编程入门还是很 ...

  8. linux socket 编程(C语言)[转]

    最近看了一些网络编程的书籍,一直以来总感觉网络编程神秘莫测,其实网络编程入门还是很容易学的,下面这些代码是我在linux下编写的,已经运行过了,编译之后就可以运行了.有不足之处希望大家多多指出,共同学 ...

  9. Linux网络编程:客户端/服务器的简单实现

    一. Socket的基本知识 1. socket功能 Socket层次 Socket实质上提供了进程通信的端点,进程通信之前,双方必须首先各自创建一个端点,否则是没有办法建立联系并相互通信的. 每一个 ...

随机推荐

  1. python代码检查工具pylint 让你的python更规范

    1.pylint是什么? Pylint 是一个 Python 代码分析工具,它分析 Python 代码中的错误,查找不符合代码风格标准(Pylint 默认使用的代码风格是 PEP 8,具体信息,请参阅 ...

  2. 警惕SAP项目被“中间商赚差价”

    前段时间某买卖二手车的广告特别火,里面有一句话叫“没有中间商赚差价”特别有说服力.同样在做SAP项目的过程中也是要警惕各种“中间商”赚差价. 正常的SAP项目的都是甲方和乙方两边签署合同合作实施,并不 ...

  3. ABP开发框架前后端开发系列---(11)菜单的动态管理

    在前面随笔<ABP开发框架前后端开发系列---(9)ABP框架的权限控制管理>中介绍了基于ABP框架服务构建的Winform客户端,客户端通过Web API调用的方式进行获取数据,从而实现 ...

  4. 【linux杂谈】跟随大牛进行一次服务器间通讯问题的排查

    发现应用记录日志内,出现网络访问延迟较大的情况. 此类问题较为常见,特别是之前参与辅助一个朋友项目运维的过程中,经常因为网络访问延迟较大,朋友认为是遭到了ddos攻击或者是cc攻击.网络访问延迟较大常 ...

  5. Confluence安装、汉化及jira整合

    今天上午装了一下Confluence,刚开始装的时候成功了,成功后进入数据库配置阶段,本人想把jira和confluence整合一起用,刚开始提示数据库连接问题,后来一直问题提示Connection ...

  6. 为什么string是引用类型 值还不可以修改

    C#把数据类型分为值类型和引用类型.值类型操作简单,引用类型更省空间. C#一共有15个预定义类型,其中13个值类型(8个整型.2个浮点类型.decimal.bool.char),2个引用类型(str ...

  7. Spring Boot2从入门到实战:统一异常处理

    都说管理的精髓就是“制度管人,流程管事”.而所谓流程,就是对一些日常工作环节.方式方法.次序等进行标准化.规范化.且不论精不精髓,在技术团队中,对一些通用场景,统一规范是必要的,只有步调一致,才能高效 ...

  8. Java8新特性——lambda表达式.(案例:完全数分类)

    需求:输入一个数,判断其类型(完全数,过剩数,不足数) 完全数:自身之外所有因数和==自身 过剩数:自身之外所有因数和>自身 不足数:自身之外所有因数和<自身 package cn._3. ...

  9. 解决IE8placeholder属性问题

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  10. Vue的生命周期函数

    详解Vue Lifecycle 先来看看vue官网对vue生命周期的介绍 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.销毁等一系列过程,我们称 ...