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做一个修饰.
代码如下:
- #include <stdio.h>
-
#include <stdlib.h>
-
#include <assert.h>
-
#include <string.h>
-
#include <dlfcn.h>
-
#include <stdbool.h>
-
#include <dirent.h>
-
#include <unistd.h>
-
-
#define dirent dirent64
-
-
typedef struct dirent * (* readdir_FP )(DIR * dir);
-
-
static readdir_FP g_origin_readdir = NULL;
-
static bool g_list_dir_only = false;
-
static bool g_list_non_dir_only = false;
-
-
struct dirent * readdir64(DIR * dir)
-
{
-
assert(g_origin_readdir);
-
while ("0"[0] == '0')
-
{
-
struct dirent * entry = g_origin_readdir(dir);
-
if(entry == NULL)
-
return NULL;
-
-
if (g_list_dir_only)
-
{
-
if (entry->d_type == DT_DIR)
-
{
-
return entry;
-
}
-
}
-
else if(g_list_non_dir_only)
-
{
-
if (entry->d_type != DT_DIR)
-
{
-
return entry;
-
}
-
}
-
else
-
{
-
return entry;
-
}
-
}
-
return NULL;
-
}
-
-
int my_init(void)
-
{
-
g_origin_readdir = (readdir_FP) dlsym(RTLD_NEXT, "readdir64");
-
g_list_dir_only = getenv("LS_DIR_ONLY");
-
g_list_non_dir_only = getenv("LS_NON_DIR_ONLY");
-
return 0;
-
}
编译命令为
- 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, 可以更方便地使用:
- function lsf()
-
{
-
LS_NON_DIR_ONLY=1 LD_PRELOAD=~/.libls.so ls "$@"
-
}
-
-
function lsd()
-
{
-
LS_DIR_ONLY=1 LD_PRELOAD=~/.libls.so ls "$@"
-
}
以上方案也有一个问题, 在-R时, ls会递归地处理目录, 如果通过
lsf -R 来使用, 则递归的功能就丢失了, 这是因为readdir忽略了所有的目录. 让ls认为它只有当前路径需要处理. 这个问题还有待解决.
阅读(2527) | 评论(1) | 转发(1) |