【问题标题】:alias command to change to newest directory别名命令切换到最新目录
【发布时间】:2015-02-19 16:36:32
【问题描述】:

我想在我的 bashrc 文件中添加一个别名以更改为当前目录中的最新子目录:

alias cdl="cd $(ls -t | head -n 1)"

问题是,当我获取文件时,该命令只评估一次。如果我在更改目录后使用新的cdl 命令,它仍会尝试更改到旧目录,这可能不存在于我的新位置,对我来说并不是那么有用。

如何在每次运行时对这个命令进行别名评估?

【问题讨论】:

  • 使用单引号:alias cdl='cd $(ls -t | head -n 1)'。虽然你的命令被破坏了。

标签: bash unix alias


【解决方案1】:

这是一种执行您想要的安全的方法:安全是指它会找到最新的 目录 (而不仅仅是文件,就像您的情况一样,尽管这可以修复)和如果目录名称包含空格、glob 字符和换行符,它会起作用(您可以通过添加适当的双引号来固定使用空格和 glob 字符,但不适用于换行符 - 有些人会争辩说处理换行符是不必要的;但因为它是可能,为什么没有一个强大的命令?)。

我们将使用函数而不是别名!

cdl() {
    local mrd
    IFS= read -r -d '' mrd < <(
        shopt -s nullglob
        mrd=
        for i in */; do
            if [[ -z $mrd ]] || [[ $i -nt $mrd ]]; then
                mrd=$i
            fi
        done
        [[ $mrd ]] && printf '%s\0' "$mrd"
    ) && cd -- "$mrd"
}

演练

让我们首先关注内部:

shopt -s nullglob
mrd=
for i in */; do
    if [[ -z $mrd ]] || [[ $i -nt $mrd ]]; then
        mrd=$i
    fi
done
[[ $mrd ]] && printf '%s\0' "$mrd"

如果没有匹配项,nullglob shell 选项将使 glob 扩展为空。在循环 for i in */ 中使用了一个 glob,并带有一个斜杠,因此它会扩展到当前目录中的所有目录(如果没有目录,则不存在任何目录,感谢 nullglob)。

我们将mrd 变量初始化为空字符串,并循环遍历所有目录,循环变量i:如果mrd 为空或i 扩展为比(-nt) 更新的目录) mrd,然后我们将 mrd 替换为 i

在循环之后,如果mrd 仍然是空的(如果根本没有找到目录就会发生这种情况)我们什么也不做;否则,我们会打印带有尾随空字节的目录名称。

现在,外面的部分:

IFS= read -r -d '' mrd < <( ... )

这获取上面讨论的内部部分的输出(因此,没有任何内容或最近目录的内容,带有尾随空字节)并将其存储在变量mrd 中。如果什么都没读,read 失败,否则read 成功。如果成功,我们很高兴在最新目录中cd


我想提两点:

  • cdl 可以写成:

    cdl() {
        local mrd i
        for i in */; do
            if [[ -z $mrd ]] || [[ $i -nt $mrd ]]; then
                mrd=$i
            fi
        done
        [[ $mrd ]] && cd -- "$mrd"
    }
    

    如您所见,这并没有设置nullglob,这是强制性的。但是你不想全局设置它。所以你需要保存旧的nullglob

    local old_nullglob=$(shopt -p nullglob)
    

    并在函数结束时重置它:

    eval "$old_nullglob"
    

    虽然这很好,但我现在尽量避免这种情况,因为如果您的函数在完成之前退出(例如,如果用户中断执行),nullglob 将不会被重置。这就是为什么我选择在子shell中运行循环!

  • 此时,您可能认为以下方法可以解决问题:

    local mrd=$( ... loop that outputs most recent dir... ) && cd -- "$mrd"
    

    不幸的是,$(...) 修剪了尾随的换行符。所以它不是 100% 工作的,所以它坏了。

事实证明,对我来说似乎最健壮的方法是使用

IFS= read -r -d '' v < <( .... printf '...\0' )

疯狂

如果你想要一个疯狂的功能:你可能观察到我给出的cdl 不处理隐藏目录。那么我们如何允许如下调用:

cdl .

这也会开启搜索隐藏目录吗?等等,我们允许函数的参数怎么样,这样调用就像

cdl . /path/to/dir1 /path/to/dir2 ...

cd/path/to/dir1/path/to/dir2 等的最新子目录(包括隐藏目录)?隐藏目录的开关应该是第一个参数,这样

cdl /path/to/dir1 .

cd 放入/path/to/dir1 和当前目录的最新非隐藏子目录,但是

cdl . /path/to/dir1 .

还将包括隐藏目录。

cdl() {
    local mrd
    IFS= read -r -d '' mrd < <(
        [[ $1 = . ]] && shopt -s dotglob && shift
        (($#)) || set .
        shopt -s nullglob
        mrd=
        for d in "$@"; do
            if [[ ! -d $d ]]; then
                printf >&2 '%s is not a directory. Skipping.\n' "$d"
                continue
            fi
            [[ $d = / ]] && d=
            for i in "$d"/*/; do
                if [[ -z $mrd ]] || [[ $i -nt $mrd ]]; then
                    mrd=$i
                fi
            done
         done
        [[ $mrd ]] && printf '%s\0' "$mrd"
    ) && cd -- "$mrd"
}

我的下一个编辑将包括一个更新,该更新将允许调用 cdl,它还会在计算 π 的最后一位数字时冲泡咖啡。

【讨论】:

  • 它肯定会工作 +1,但 OP 会晕倒看到半线别名被转换成这个 :)
  • @anubhava 哈哈,很多。我不确定这里发生了什么,但它就像一个魅力。
  • @Mike 也许我的编辑会帮助你弄清楚发生了什么?
  • @gniourf_gniourf 非常感谢。如果可以的话,我会再次投票给你!
【解决方案2】:

如果你切换到单引号,它应该可以工作:

alias cdl='cd -- "$(ls -t | head -n 1)"'

请注意,命令中的ls 不一定会提供最新的目录,它也可能会生成一个文件,在这种情况下,该命令将无法按您预期的那样工作。在这方面添加 --group-directories-first 选项可能会有所帮助。

【讨论】:

  • 谢谢,你是对的。我将其更改为cd $(ls -td */ | head -n 1),以便它只列出目录。当然,如果没有这些,那就是另一个问题。我想我也应该为此添加一张支票。不过,这暂时有效。
  • 好吧,此时你最好写一个脚本而不是使用别名:)。
猜你喜欢
  • 1970-01-01
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 2022-08-12
  • 2022-12-05
  • 1970-01-01
  • 2014-10-08
  • 2016-01-13
相关资源
最近更新 更多