智能cd

这是一个智能的时代,手机已经智能了,cd怎么智能?

好吧,这里说的cd不是用来听的cd,而是UNIX改变当前工作目录的shell自带命令, change directory的缩写,cd。

哦,那个cd啊,你恍然,这东西还能智能?

UNIX的设计遵循了一条非常有道理的原则,那就是底层提供机制(mechanism), 而非决策(policy)。决策要由上层决定如何使用底层的机制去实现。最早看到 这条原则是在LDD(Linux Device Driver)那本书里。

cd的机制就是 chdir 这条系统调用了,但作为shell里的一条命令,它自己应该 是可以定制的一种policy了。可惜在Bash里,我们看不到能怎么定制它。唯一提 供的一个定制是通过一个叫CDPATH的环境变量。设置了这个变量之后执行 cd XDIR 命令,如果当前目录下没有 XDIR ,则Bash会依次到CDPATH里指定的每个 路径下寻找名为XDIR的子目录,如果找到就chdir到那个子目录下,如果所有 CDPATH路径下都找不到则最终报错。

强烈建议你不要使用这个变量!!!

因为我曾经吃过苦头,设置了这个变量之后发现Android的编译莫名其妙的失败了。 查了半天才发现原来是CDPATH引起的。你Google一下Android CDPATH应该发现别 人也碰到了这个问题,可惜你自己查出问题原因之前Google也帮不了你。 Android的Makefile里指定了Shell用Bash而不是默认的sh,然后Makefile里的 $(shell cd XDIR && ls …)类似这样的命令就都无法正常工作了。因为这里的 cd也会尊重你设的那个CDPATH变量!

正确的定制cd的办法是写一个 function cd() {...} 。在函数体里计算出你最 后真正要chdir过去的那个目标目录后,调用 builtin cd XDIR 过去。注意这 里必须用 builtin (另一个Bash自带命令)指定这回我们要用Bash自带的那个 cd而不是我们这里定义出来的cd函数,要不然你就递归了(恭喜你:-)。

然后因为你没有用过 export -f cd ,Make子进程和它唤起的$(shell cd …)并不会看到这个cd函数,所以就不会存在CDPATH的那个问题。

基本上我自己定制过后的cd是这样工作的:

  1. 如果只带一个参数 X(谁说cd只 带一个参数?马上我就告诉你带多 个参数是什么意思),则:

    a) 如果当前目录下有X并且是一个目录,则chdir到X。这保证了与builtin cd的兼容性。

    b) 如果当前目录下有X并且是一个文件,则chdir到$(dirname X)。

    c) 如果依次往上级目录寻找一直到根目录 / 为止,能找到X(也就是在X前 加n个 ../ 后发现其存在),则依照a)或b)处理。

    d) 以上都失败,按2.处理

  2. 带多个参数或者带一个参数但在1.中尚未cd成功的情况。假设我们有X Y Z几 个参数,则到cd历史目录列表中(下面会解释)中匹配这些参数,匹配成功的 历史目录 $HISTDIR 要求必须:

    a) X Y Z每一个都是其子串

    b) X Y Z中至少有一个是其$(basename $HISTDIR)的子串。

    • 然后如果只有一条匹配的历史目录,则直接chdir过去。
    • 如果有多条匹配成功的历史目录,则调用一个my-select程序,让用户选哪 个才是他想要的。由于历史目录的记录方法,用户最近去过的目录会在前面 出现,如果是第一项的话,可以直接按回车。也可以继续输入一些单词U V W进行细化选择,但这个就是my-select的特性了,下次再聊它(有没有让你 想起Emacs下的Anything/Helm Mode?)。
    • 如果一个也没有匹配,则报错。

最后,如果有chdir发生的话,把新的当前工作目录push到历史目录列表的头部。

见图(中间我输入ext作了一次细化):

cd_bhj.png

最后,我的cd其实是个alias,真正的函数名叫cdbhj,可以在 这个.bashrc 里 找到。