别名命令更改到最新目录

alias command to change to newest directory

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

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

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

我如何为这个命令设置别名以便在我每次 运行 时对其进行评估?

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

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

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

这里有一个安全的方法来执行你想要的操作:我所说的安全是指它会找到最近的 目录(而不是像你的情况那样只是文件,虽然这个可以修复)并且如果目录名称包含空格、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[=10=]' "$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[=11=]' "$mrd"

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

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

替换 mrd

循环后,如果 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 '...[=17=]' )

疯狂

如果您想要一个疯狂的功能:您可能注意到我提供的 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 < <(
        [[  = . ]] && 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[=22=]' "$mrd"
    ) && cd -- "$mrd"
}

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