Chinaunix首页 | 论坛 | 博客
  • 博客访问: 61699
  • 博文数量: 21
  • 博客积分: 93
  • 博客等级: 民兵
  • 技术积分: 118
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-03 23:21
文章分类
文章存档

2013年(1)

2012年(15)

2011年(5)

我的朋友

分类:

2012-01-09 20:20:58

ls 选项众多, 但却没有一个功能可以只显示目录或只显示非目录(我是指除目录之外的所有其它类型, 比如pipe, socket等).

多年前, 我曾经在chinalinuxpub上贴过一个小的脚本, 生成了函数lsd, 和lsf, 顾名思义, lsd只列出目录, lsf只显示非目录.

PS: 很不幸地, 该网站显示"因为精力问题,中坚站网站关闭了", 所以我自己也找不回来了.

这个脚本并不完美, 我在linux一句话问答上也看到过其它的方案, 目前我已知的这些方案对于简单的用例, 都可以工作. 但也都存在一些问题.

比如 上给出的方案是ls -d */目录名可以后辍以/, -d 参数并非是只列出目录, 而是指列出目录项本身, 而非其中的内容. 对于只想列出当前目录的内容, 这是个不错的方案. 但ls 支持 -R, 如何递归处理呢?该贴中给出的方案是

shopt -s globstar
ls -d **/ 这对于新的bash来说, 是可行的, 如bash 4.1.5(1)上就可以. 

但如何反过来呢, 如何列出非目录呢? 这个办法就不灵了

另外, 任何通过在命令本身进行glob的方案, 都会有一个潜在的弊端: 命令行最大长度限制, 文件少了无所谓, 多了就会失败, 我在多年前rm *的办法来删除oralce的日志时, 就碰到过这种失败.

所以ls这个简单的命令可以列出当前目录下的内容, 但ls *却有可能失败.

linux一句话问答上, 让ls只列出目录的方案是
ls -lF | grep ^d
ls -lF | grep /$
但这个办法改变的原始的ls输出, ls在检测到标准输出不是终端时, 会一行显示一个文件.
而且这个办法在递归处理时, 如ls -lRF | grep ^d
就同时把某个目录的父目录信息给滤掉了, 这样得不到目录的层次结构, 而原始的
ls -lRF 则可以.

这个办法也不能处理"非目录"的情况, 另外使用管道把结果送给另一个程序过滤, 副作用是颜色信息没了. 另外也会慢一些(这个不是主要问题)

用find -type d 找到目录之后, 再用xargs传给ls列出来, 也是可行的, 但xargs处理过多的参数时, 也有个问题, 它会考虑命令行长度, 要处理的内容过多时, 它会分为多个批次多次运行ls, 每次传递一批参数, 这同样也改变了ls 的原始输出.

叽叽歪歪半天, 挑这个嫌那个, 无非是想做做铺垫, 隆重推出下面的解决办法:
在运行时修改ls的行为, 让它忽略目录或忽略非目录. 但我想避免修改ls的源代码, 而是动态是加一个钩子, 为原始的ls做一个修饰.

代码如下:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <assert.h>
  4. #include <string.h>
  5. #include <dlfcn.h>
  6. #include <stdbool.h>
  7. #include <dirent.h>
  8. #include <unistd.h>

  9. #define dirent dirent64

  10. typedef struct dirent * (* readdir_FP )(DIR * dir);

  11. static readdir_FP g_origin_readdir = NULL;
  12. static bool g_list_dir_only = false;
  13. static bool g_list_non_dir_only = false;

  14. struct dirent * readdir64(DIR * dir)
  15. {
  16.     assert(g_origin_readdir);
  17.     while ("0"[0] == '0')
  18.     {
  19.         struct dirent * entry = g_origin_readdir(dir);
  20.         if(entry == NULL)
  21.             return NULL;

  22.         if (g_list_dir_only)
  23.         {
  24.             if (entry->d_type == DT_DIR)
  25.             {
  26.                 return entry;
  27.             }
  28.         }
  29.         else if(g_list_non_dir_only)
  30.         {
  31.             if (entry->d_type != DT_DIR)
  32.             {
  33.                 return entry;
  34.             }
  35.         }
  36.         else
  37.         {
  38.             return entry;
  39.         }
  40.     }
  41.     return NULL;
  42. }

  43. int my_init(void)
  44. {
  45.     g_origin_readdir = (readdir_FP) dlsym(RTLD_NEXT, "readdir64");
  46.     g_list_dir_only = getenv("LS_DIR_ONLY");
  47.     g_list_non_dir_only = getenv("LS_NON_DIR_ONLY");
  48.     return 0;
  49. }

编译命令为

  1. gcc -Wall -Wextra -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Wl,-init,my_init -std=c99 -fPIC -shared -o libls.so lsd.c

使用:

LS_DIR_ONLY=1 LD_PRELOAD=./libls.so ls -R

两个特殊的环境变量:
LS_DIR_ONLY 如果定义了, 就会只显示目录.
LS_NON_DIR_ONLY, 如果定义了, 就会只显示文件.

对这两个环境变量进行特殊处理当然不在ls 中, 而是在libls.so中, 通过这样的处理, 原来的ls功能被完整地保留下来.

通过定义function lsd, lsf, 可以更方便地使用:
  1. function lsf()
  2. {
  3. LS_NON_DIR_ONLY=1 LD_PRELOAD=~/.libls.so ls "$@"
  4. }
  5. function lsd()
  6. {
  7. LS_DIR_ONLY=1 LD_PRELOAD=~/.libls.so ls "$@"
  8. }
以上方案也有一个问题, 在-R时, ls会递归地处理目录, 如果通过
lsf -R 来使用, 则递归的功能就丢失了, 这是因为readdir忽略了所有的目录. 让ls认为它只有当前路径需要处理. 这个问题还有待解决.
阅读(1138) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~