V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
skywind3000
V2EX  ›  Linux

更懂你的路径切换工具 - z.lua(集 autojump / z / fasd 大成)

  •  4
     
  •   skywind3000 ·
    skywind3000 · 2019-02-01 01:00:33 +08:00 · 9918 次点击
    这是一个创建于 1883 天前的主题,其中的信息可能已经有所发展或是发生改变。

    z.lua 是一个快速路径切换工具,它会跟踪你在 shell 下访问过的路径,通过一套称为 Frecent 的机制(源自 FireFox ),经过一段简短的学习之后,z.lua 会帮你跳转到所有匹配正则关键字的路径里 Frecent 值最高的那条路径去。

    正则将按顺序进行匹配,"z foo bar" 可以匹配到 /foo/bar,但是不能匹配 /bar/foo。

    项目地址:skywind3000/z.lua

    有啥特点?

    • 性能比 z.sh 快三倍,比 fasd / autojump 快十倍以上。
    • 支持 Posix Shell ( bash, zsh, dash, sh, ash, busybox )及 Fish Shell。
    • 支持 Windows cmd 终端 (使用 clink),cmder 和 ConEmu。
    • 无依赖,不会像 fasd/z.sh 那样对 awk/gawk 有特殊的版本要求。
    • 兼容 lua 5.1, 5.2 和 5.3 以上版本。
    • 新增:环境变量 "$_ZL_ADD_ONCE" 设成 1 的话性仅当前路径改变时才更新数据库。
    • 新增:增强匹配模式,将环境变量 "$_ZL_MATCH_MODE" 设置成 1 可以启用。
    • 新增:交互选择模式,如果有多个匹配结果的话,跳转前允许你进行选择。

    如何安装?

    Posix Shells ( Bash、zsh、dash、sh 或 BusyBox 等)中,在你的 .bashrc, .zshrc 或者 .profile 文件中按 shell 类型添加对应语句:

      eval "$(lua /path/to/z.lua  --init bash)"   # BASH 初始化
      eval "$(lua /path/to/z.lua  --init zsh)"    # ZSH 初始化
      eval "$(lua /path/to/z.lua  --init posix)"  # Posix shell 初始化
    

    用下面参数初始化会进入“增强匹配模式”:

      eval "$(lua /path/to/z.lua  --init bash once enhanced)"   # BASH 初始化
      eval "$(lua /path/to/z.lua  --init zsh once enhanced)"    # ZSH 初始化
      eval "$(lua /path/to/z.lua  --init posix once enhanced)"  # Posix shell 初始化
    

    同时 zsh 支持 antigen/oh-my-zsh 等包管理器,可以用下面路径:

      skywind3000/z.lua
    

    进行安装,比如 antigen 的话,在 .zshrc 中加入:

      antigen bundle skywind3000/z.lua
    

    就可以了(主要要放在 antigen apply 语句之前)。Windows 和 Fish Shell 的初始化见文档。

    Matching

    z.lua 提供两种路径匹配算法:

    • 设置 $_ZL_MATCH_MODE=0:默认匹配算法,兼容 z.sh
    • 设置 $_ZL_MATCH_MODE=1:增强匹配算法,更懂你的高效匹配算法。

    除了设置环境变量外,还可以通过:

    eval "$(lua /path/to/z.lua --init bash enhanced)"
    

    来进入增强模式。

    默认匹配

    默认情况下 z.lua 使用和 z.sh 类似的匹配算法,成为默认匹配法。给定路径会按顺序匹配各个正则表达式。

    • cd 到一个包含 foo 的路径:

      z foo
      
    • cd 到一个以 foo 结尾的路径:

      z foo$
      
    • 使用多个参数进行跳转:

      假设路径历史数据库(~/.zlua )中有两条记录:

      10   /home/user/work/inbox
      30   /home/user/mail/inbox
      

      "z in"将会跳转到 /home/user/mail/inbox 因为它有更高的权重,同时你可以传递更多参数给 z.lua 来更加精确的指明,如 "z w in" 则会让你跳到 /home/user/work/inbox

    增强匹配

    你可以通过设置环境变量来启用增强匹配模式:

    export _ZL_MATCH_MODE=1
    

    或者使用下面语句:

    eval "$(lua /path/to/z.lua --init bash enhanced)"
    

    进行初始化,他们是等效的,记得把上面的 bash 可以根据你的 shell 改为 zsh 或者 posix

    对于一个给定的正则关键字序列(即 z 命令后面的参数),某路径有且只有满足下面两个条件才算作 “匹配成功”:

    1. 正则关键字将按顺序进行匹配(这条和默认匹配法相同)。
    2. 最后一个关键字可以和路径名的最后一段相匹配。

    如果两条规则同时启用找不到任何结果,那么将会退回到只用规则 1 进行筛选,这两条规则是参考 fasd 引入的。

    • 匹配路径名的最后一段:

      假设数据库内容为:

      10   /home/user/workspace
      20   /home/user/workspace/project1
      30   /home/user/workspace/project2
      40   /home/user/workspace/project3
      

      在增强模式下使用 "z wo" 的话,只有 /home/user/work 满足匹配,因为按照第二条规则,这是唯一一条最有一段名称匹配 wo 的路径。

      因为最后一级目录名称总是最容易记住的,所以给到它比较高的优先级。在默认匹配算法中,你同样可以用 "z space$" 来达到相同的目的,但是 "z wo" 可以打更少的字。

      小技巧:

      • 如果你在增强匹配算法下,想让最后一个关键字不当匹配最后一段路径名,还可以像默认匹配算法中一样匹配路径的其他部分的话,你可以在最后加一个独立的 '$' 参数,比如:"z wo $"
      • 如果你在增强匹配算法下,想让最后一个关键字匹配最后一段路径名以前的部分,那么可以增加一个斜杆参数,比如:"z wo /"
    • 如果没法匹配,同时又存在一条路径名和关键字相同,那么 cd 过去:

      有时候如果你输入:

      z foo
      

      但是数据库里又没有任何匹配 foo 的记录,然后却存在一个可以在当前位置访问的目录,刚好名字是 "foo",那么 "z foo" 的效果将会和下面的命令效果相同:

      cd foo
      

      因此,在增强匹配算法中,你总可以象 cd 命令一样使用 z 命令,而不必当心目标路径是否被记录过。

    • 忽略当前路径:

      如果你使用 z xxx 但是当前路径恰好是最佳匹配结果,那么 z.lua 会使用次优结果进行跳转。假设有如下数据:

      10   /Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems
      20   /Library/Ruby/Gems/2.0.0/gems
      

      默认情况下,当我使用 z gems 时,我会被带到 /Library/Ruby/Gems/2.0.0/gems,因为它有更高权重,但是可能并不是我想要去的地方,这时我按一下方向键上键,再次执行 z gems,那么我就能被带到 /Users/Great_Wall/.rbenv/versions/2.4.1/lib/ruby/gems 目录中,而这正是我想去的地方。

      我当然可以每次使用z env gems 来精确指明,但是每当我输入 z xxx 我必然是想进行路径跳转的,而不是呆在原地,所以使用增强匹配模式,即便当前目录是最佳匹配,它也能懂得你想跳转的心思。

    再我最初实现 z.lua 时,只有一个和 z.sh 类似的默认匹配算法,在网友的建议下,我陆续学习了来自 fasd / autojump 中的优秀理念,并加以完善改进,成为如今集三家之长的 “增强匹配算法” ,给它取个昵称,叫做 “更懂你的匹配算法”。

    更新数据库的时机

    何时更新数据呢?默认情况下,z.lua 会在每次显示命令提示符时记录当前路径(和 z.sh 一致),但是还提供了一个 $_ZL_ADD_ONCE 的环境变量选项,设置成 1 的话,只有当前路径改变,才会将新路径添加到数据库。

    除了设置环境变量外,不同的 shell 下还可以在初始化时增加 "once" 参数来达到相同目的,比如:

    eval "$(lua /path/to/z.lua --init bash once enhanced)"
    eval "$(lua /path/to/z.lua --init zsh once enhanced)"
    lua /path/to/z.lua --init fish once enhanced | source
    

    将会同时启用增强匹配算法和 once 机制,在一些比较慢的硬件下(路由器,cygwin,msys ),使用该机制将有效的提升性能。其实 autojump 在 zsh 下会使用类似 once 的机制,而 bash 下则和 z.sh 类似。

    从效果上来讲,z.sh 的模式(关闭 once )强调的是 “在某路径下工作的时间长短”,而 autojump 的模式(启用 once )则强调 “进入某路径的次数多少”。

    交互式选择模式

    使用 -i 参数进行跳转时, 如果有多个匹配结果,那么 z.lua 会给你显示一个列表:

    $ z -i soft
    3:  0.25        /home/data/software
    2:  3.75        /home/skywind/tmp/comma/software
    1:  21          /home/skywind/software
    > {光标位置}
    

    然后你按照最前面的序号输入你想要去的地方,比如输入 3 就会进入 /home/data/software。如果你不输入任何东西直接按回车,那么将会直接退出而不进行任何跳转。

    Tips

    推荐一些常用的命令别名:

    alias zc='z -c'      # 严格匹配当前路径的子路径
    alias zz='z -i'      # 使用交互式选择模式
    

    同时你可以定义一个名为 zf 的命令,搭配 fzf 进行历史路径模糊匹配:

    alias zf='cd "$(z -l -s | fzf --reverse --height 35%)"'
    

    性能评测

    最慢的部分当然是添加当前路径到数据库。该操作会在每次你按回车时执行,所以我在我的 Nas 上做了个对比:

    $ time autojump --add /tmp
    real    0m0.352s
    user    0m0.077s
    sys     0m0.185s
    
    $ time fasd -A /tmp
    real    0m0.618s
    user    0m0.076s
    sys     0m0.242s
    
    $ time _z --add /tmp
    real    0m0.194s
    user    0m0.046s
    sys     0m0.154s
    
    $ time _zlua --add /tmp
    real    0m0.052s
    user    0m0.015s
    sys     0m0.030s
    

    可以看出,z.lua 是消耗资源最少,并且最快的,可以更流畅的在性能不好的环境中使用。

    结论

    真的可以卸载 autojump / z / fasd 了。

    第 1 条附言  ·  2019-02-01 19:59:16 +08:00
    更新版本:43 解决随机数安全问题,以及 fish shell 下 interactive selection 无法工作的问题
    41 条回复    2020-12-19 00:34:00 +08:00
    Chingim
        1
    Chingim  
       2019-02-01 01:31:29 +08:00 via Android
    看起来不错,没想到我用的 fasd 这么不能打
    这个快是因为 lua 语言(fasd 应该是 shell script 实现)带来的吧?
    Chingim
        2
    Chingim  
       2019-02-01 01:35:39 +08:00 via Android
    我找到一个能打的 https://github.com/xen0n/autojump-rs/blob/develop/README.md

    能加入比较么
    zhs227
        3
    zhs227  
       2019-02-01 01:41:48 +08:00
    最近在尝试楼主的这个东西,用 luajit 加持一般进入目录的时间在个位数毫秒级别,非常好用
    yuikns
        4
    yuikns  
       2019-02-01 03:44:29 +08:00
    点开工程发现用户名有点熟悉。原来好久前就关注了。
    膜大佬
    ynyounuo
        5
    ynyounuo  
       2019-02-01 03:57:11 +08:00
    @Chingim fasd 基本上没有后续支持了,作者感觉忘记了这个项目
    skywind3000
        6
    skywind3000  
    OP
       2019-02-01 05:23:06 +08:00
    @Chingim z.lua 更快,切换路径只要 0.017 秒,你看他的评测他居然需要 0.050 秒。其实也不奇怪,lua 的可执行也才 200 多 KB,它可执行 5MB,strip 过后也有 1.5MB ,启动速度它就拜下来了,他还没启动完,z.lua 可能都运行完了。
    yuikns
        7
    yuikns  
       2019-02-01 05:30:19 +08:00
    好奇怪,我 mac 的 bash 使用了下, lua 5.3.5,随便 cd 几下后,在 /tmp 下产生了大量 lua_xxxxxx 空文件。

    data_save 那个 function 好像会删除文件,但是失败了?

    请问发 issue 还需要什么信息?
    wweir
        8
    wweir  
       2019-02-01 05:32:19 +08:00 via Android
    @skywind3000 可执行文件在文件系统是有缓存的,以这点文件大小来说事并不科学。
    如果想从文件大小来说明比 rust 的实现快,至少得冷热启动分别给一下数据
    wzw
        9
    wzw  
       2019-02-01 07:39:51 +08:00 via iPhone
    看了就想试试,稳定性如何
    skywind3000
        10
    skywind3000  
    OP
       2019-02-01 09:06:04 +08:00
    @wweir 不是说它要载入这个大文件费时间,而是文件那么大,初始化一定很多代码要跑。
    wweir
        11
    wweir  
       2019-02-01 09:24:53 +08:00 via Android
    @skywind3000 同样,进程自身在内核中也是有缓存的,重置一下上下文就好

    不想去扣这些底层,对普通人来说,就是玄学。要表述一个违反常规认知的观点,那就来点实际的吧,比如:bench 数据
    congeec
        12
    congeec  
       2019-02-01 09:28:21 +08:00
    为嘛用 lua 呢?为了性能?
    Yggdroot
        13
    Yggdroot  
       2019-02-01 09:29:51 +08:00 via Android
    正在使用,很好用。z.lua 还有个优点,就是有什么好的想法作者都会很快做出反馈,其它的工具就未必。
    guanhui07
        14
    guanhui07  
       2019-02-01 10:05:03 +08:00
    不错
    clown139880
        15
    clown139880  
       2019-02-01 10:48:44 +08:00
    mac 用 antigen 安装之后报错
    skywind3000/z.lua/z.lua:1414: attempt to concatenate a nil value
    屏蔽两行后使用 z 提示_zlua:33: permission denied
    是我 lua 的问题么,我用 brew install lua 安装的
    使用 z.sh 时没有出现过任何问题
    dltsgl
        16
    dltsgl  
       2019-02-01 11:19:30 +08:00
    centos6 报错:
    lua: /home/v33491/download/lua/z.lua:7: unexpected symbol near '<'

    lua version 是: Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio

    用的 bash

    不太懂,请教一下什么原因
    lihongjie0209
        17
    lihongjie0209  
       2019-02-01 11:23:52 +08:00   ❤️ 2
    这种交互式的工具性能倒是其次, 关键是易用性
    skywind3000
        18
    skywind3000  
    OP
       2019-02-01 13:27:11 +08:00
    @clown139880 重新按默认参数编译个 lua 吧,brew 的版本有问题。
    skywind3000
        19
    skywind3000  
    OP
       2019-02-01 13:30:45 +08:00
    @wweir 如你所愿,我做了个测试:

    原版本 autojump:

    skywind@weilin0:~/.vim$ time j vim
    /home/skywind/software/vim

    real 0m0.149s
    user 0m0.047s
    sys 0m0.063s

    autojump-rs:

    skywind@weilin0:~/software/vim$ time j vim
    /home/skywind/.vim

    real 0m0.075s
    user 0m0.000s
    sys 0m0.031s

    z.lua:

    skywind@weilin0:~/.vim$ time z vim

    real 0m0.019s
    user 0m0.016s
    sys 0m0.016s

    够明白了么?我说的启动时间不仅指操作系统层的启动时间,还指应用程序自身的初始化时间,程序越庞大越复杂,模块越多,自然涉及到越复杂的启动过程,举个例子,就像 C++ 全局对象多了以后,进入主程序之前,都有一大堆构造函数要调用。
    wweir
        20
    wweir  
       2019-02-01 14:36:06 +08:00
    @skywind3000 数据依然不够全面,benchmark test 报告,可不是这么简单就行的
    skywind3000
        21
    skywind3000  
    OP
       2019-02-01 17:49:00 +08:00
    @wweir 我已经给出支持我论据的初步测试了,你如果觉得不妥但,欢迎进一步测试来反驳。
    wweir
        22
    wweir  
       2019-02-01 18:43:13 +08:00 via Android
    我反驳啥?我只要提出质疑就行了。
    谁让你是想推广的那一方的,我们作为用户,就是有这个特权 😄
    Yggdroot
        23
    Yggdroot  
       2019-02-01 18:54:13 +08:00 via Android
    @wweir 质疑也要有根有据,而不是无脑喷。
    haozhang
        24
    haozhang  
       2019-02-01 18:56:04 +08:00 via Android
    @wweir 不要包括我,我没那么厚的脸,被打肿了还要充胖子。
    uyhyygyug1234
        25
    uyhyygyug1234  
       2019-02-01 20:15:34 +08:00
    感觉切换目录这个,fasd z.sh 性能已经挺好了。不过路由器上可能差点。

    我见到 https://www.v2ex.com/t/436485 这边有配合 fzf 做交互的的选择,更加直观。




    ps 楼主的文章介绍更好的 init.sh 中,提到的“有恒产才会有恒心”想法很好。
    skywind3000
        26
    skywind3000  
    OP
       2019-02-01 20:58:03 +08:00
    @yuikns 该问题已经修复,初始化随机数种子用了一个 os.tmpname() 结果没发现该函数会在 /tmp 建立临时文件。随机数种子已经改写为更好的方式了,不再调用 os.tmpname(),该问题修正。
    skywind3000
        27
    skywind3000  
    OP
       2019-02-01 21:02:35 +08:00
    @uyhyygyug1234 其实下面方法也可以代替:

    alias zf='cd "$(z -l -s | fzf --reverse --height 35%)"'

    达到你说的这个效果,把 z.sh/z.lua 的历史数据拿出来,放给 fzf 匹配。但是啊,z.lua 的匹配不当会考虑字符串,还会考虑 frecency 权重,不是简单 fzf 那种字符串排序。同时还有很多用于提升效率的匹配规则,也不是 fzf 那种纯字符串匹配。
    skywind3000
        28
    skywind3000  
    OP
       2019-02-01 21:15:17 +08:00
    @dltsgl 能不能自己编译个完整的 lua,有问题再给我反馈。
    有些包管理里面的 lua 编译参数都没给全,导致库函数缺胳膊少腿的。
    Kobayashi
        29
    Kobayashi  
       2019-02-01 21:28:19 +08:00 via Android
    据我所知,你测试中的 fasd 不准确,应该是 autojump 调用 Python 解释器最为耗时。我是从这里看到的:

    https://reddit.com/r/linux/comments/agkx07/zlua_a_better_method_to_change_directory/eecu8op?context=3

    作者有考虑像 fasd 一样从输入的命令中提取路径信息、文件信息吗?

    @uyhyygyug1234 autojump, z, fasd, z.lua, zsh-z 对比可以看看上边的链接。从结论上讲,fasd 因为抓取了输入的命令中的路径,考虑的要比其他几位多。
    但从速度上来看,z.lua 和 zsh-z 最快,前者支持 POSIX sh,但后者也说明其实慢的不是 shell 脚本,而是因为 z 中调用外部命令耗时。
    uyhyygyug1234
        30
    uyhyygyug1234  
       2019-02-01 21:34:19 +08:00
    uyhyygyug1234
        31
    uyhyygyug1234  
       2019-02-01 21:35:29 +08:00
    貌似 z[dot]sh 这个触发了外链 spam 规则。只能贴图了。
    GPU
        32
    GPU  
       2019-02-02 00:58:36 +08:00
    我用 oh my zsh 的话怎么安装这个插件

    我在 .zshrc 里面 加入 plugins=( skywind3000/z.lua ) 这样不生效 .

    还是这种方式只支持 antigen
    skywind3000
        33
    skywind3000  
    OP
       2019-02-02 01:05:37 +08:00
    @GPU 我不用 oh-my-zsh 啊,试试这个:

    git clone https://github.com/skywind3000/z.lua $ZSH_CUSTOM/plugins/zlua

    然后配置里:
    plugins=( ... zlua )
    Kobayashi
        34
    Kobayashi  
       2019-02-02 10:05:56 +08:00 via Android
    @uyhyygyug1234 关于 fasd 速度啥不是对你说的,是想问作者有没有支持 fasd 做法的计划。z.lua 替换 z 和 autojump 是可能,替换 fasd 就算了。z.lua 既没有 v 的功能,也没有从命令行提取路径的特性,比不上 fasd。

    fzf 我自己也在用,fzf 的 wiki 有列出你给出的脚本,不过还是谢谢了。好像我记得 fzf 加入--height 参数可以把 fzf 菜单移动到光标下,而不是全屏,我个人喜欢这么干。
    Kobayashi
        35
    Kobayashi  
       2019-02-02 10:12:26 +08:00 via Android
    @skywind3000 我猜作者以前没写过 ZSH 插件,也不用 ZSH 框架、插件管理器?插件脚本名应该与插件所在文件夹名、项目名一致。即把插件脚本改名为 z.lua.plugin.zsh ,这基本上已经成为 ZSH 插件的规范要求。现在的命名根本没办法被 oh-my-zsh 加载,大部分插件管理器也加不了。

    只要想问一下作者,有没有考虑给 z.lua 加入类似 fasd 的命令内容提取?
    skywind3000
        36
    skywind3000  
    OP
       2019-02-02 11:47:42 +08:00
    @Kobayashi 我一直用 antigen,只要后缀有 .plugin.zsh 它就识别了,没用 oh-my-zsh,已经改成 z.lua.plugin.zsh 了。关于 fasd,我感觉它这种疯狂收集所有路径的开销太大,我几台 nas / 路由 和 cygwin/wsl 根本吃不消,用不起。暂时不是 z.lua 的目标,未来我会花时间增加更多便利的跳转机制,以及 fzf 的集成。
    Kobayashi
        37
    Kobayashi  
       2019-02-02 14:35:42 +08:00 via Android
    @skywind3000 恩,我确实没有考虑到在路由上的消耗问题,谢谢解答。
    a132811
        38
    a132811  
       2019-02-05 09:27:38 +08:00
    lua ${HOME}/conf/z.lua --init zsh
    ZLUA_SCRIPT="usage: which [-as] program ..."

    报错了呢?
    skywind3000
        39
    skywind3000  
    OP
       2019-02-05 13:06:13 +08:00
    @a132811 你的 lua 有问题,是不是 brew 安装的啊? z.lua 需要取得 zlua 可执行的路径,结果取不出来。重新按默认参数编译一个 lua 解决。
    stdout
        40
    stdout  
       2019-11-08 12:21:13 +08:00
    果断换成 z.lua 了,fasd 太慢了。按这 enter 不动明显能感觉出来。
    flavoury
        41
    flavoury  
       2020-12-19 00:34:00 +08:00
    配置在 zsh 中,可以这么写:

    ```
    # z.lua, 启用
    eval "$(lua $(brew --prefix)/share/z.lua/z.lua --init zsh once enhanced)"
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3547 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 04:54 · PVG 12:54 · LAX 21:54 · JFK 00:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.