简体   繁体   English

别名命令更改为最新目录

[英]alias command to change to newest directory

I want to add an alias in my bashrc file to change to the newest subdirectory in the current directory: 我想在我的bashrc文件中添加一个别名,以更改为当前目录中的最新子目录:

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

The problem is, the command is only evaluated once, when I source the file. 问题是,当我获取文件时,该命令仅被评估一次。 If I use the new cdl command after changing directories, it still tries to change to the old directory, which may not be present in my new location, and isn't all that useful to me. 如果我在更改目录后使用新的cdl命令,它仍然会尝试更改为旧目录,这可能不在我的新位置,并且对我来说并不是那么有用。

How can I alias this command to evaluate every time I run it? 如何在每次运行时对此命令进行别名评估?

Here's a safe way to perform what you want: by safe I mean that it'll find the most recent directory (and not just file as in your case, though this could be fixed) and it'll work if directory name contains spaces, glob characters and newlines (yours could be fixed to work with spaces and glob characters by adding appropriate double quotes, but not for newlines—some will argue that dealing with newlines is unnecessary; but since it's possible, why not have a robust command?). 这是一种安全的方式来执行你想要的东西:安全我的意思是它会找到最新的目录 (而不仅仅是你的情况下的文件,虽然这可以修复)如果目录名包含空格,它将起作用, glob字符和换行符(可以通过添加适当的双引号来修复你的空格和glob字符,但不能用于换行符 - 有些人会认为处理换行符是不必要的;但是因为它可能,为什么不能有一个健壮的命令?) 。

We'll use a function instead of an alias! 我们将使用函数而不是别名!

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"
}

Walkthrough 演练

Let's first concentrate on the inner part: 让我们首先关注内部部分:

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

The nullglob shell option will make globs expand to nothing if there are no matches. 如果没有匹配项,则nullglob shell选项将使globs扩展为空。 A glob is used in the loop for i in */ , with a trailing slash, so this expands to all directories in current directory (or nothing if no directories are there, thanks to nullglob ). for i in */的循环中使用了一个glob,带有一个斜杠,所以这会扩展到当前目录中的所有目录(如果没有目录,则没有任何内容,这要归功于nullglob )。

We initialize the mrd variable to the empty string, and we loop through all directories, with loop variable i : If either mrd is empty or i expands to a directory newer than ( -nt ) mrd , then we replace mrd by i . 我们将mrd变量初始化为空字符串,然后循环遍历所有目录,循环变量i :如果mrd为空或者i扩展到比( -ntmrd更新的目录,那么我们用i替换mrd

After the loop, if mrd is still empty (this happens if no directories are found at all) we don't do anything; 在循环之后,如果mrd仍然为空(如果没有找到任何目录就会发生这种情况)我们什么都不做; otherwise we print that directory name with a trailing nullbyte. 否则我们使用尾随的nullbyte打印该目录名称。

Now, the outer part: 现在,外部:

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

This takes the output of the inner part discussed above (so, either nothing or the content of most recent directory, with a trailing nullbyte) and stores it in variable mrd . 这将获取上面讨论的内部部分的输出(因此,无论是什么或最近目录的内容,具有尾随的nullbyte)并将其存储在变量mrd If nothing is read, read fails, otherwise read succeeds. 如果没有读取任何内容,则read失败,否则read成功。 In case of success, we happily cd in the newest directory. 如果成功,我们很高兴在最新的目录中cd


Two points I'd like to mention: 我想提两点:

  • It's possible to write cdl as: 可以将cdl写成:

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

    As you can see, this doesn't set nullglob , which is mandatory here. 如您所见,这不会设置nullglob ,这是必需的。 But you don't want to set it globally. 但是你不想全局设置它。 So you need to save old nullglob : 所以你需要保存旧的nullglob

     local old_nullglob=$(shopt -p nullglob) 

    and reset it at the end of your function: 并在功能结束时重置它:

     eval "$old_nullglob" 

    While this is perfectly fine, I now try to avoid this, since if your function exits before completing (eg, if user breaks its execution), nullglob wouldn't be reset. 虽然这很好,但我现在试图避免这种情况,因为如果你的函数在完成之前退出(例如,如果用户中断它的执行),则nullglob将不会被重置。 That's why I chose to run the loop in a subshell! 这就是我选择在子shell中运行循环的原因!

  • At this point, you might think that the following would solve the problem: 此时,您可能会认为以下内容可以解决问题:

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

    Unfortunately, $(...) trims trailing newlines. 不幸的是, $(...)修剪了尾随的换行符。 So it's not 100% working, hence it's broken. 所以它不是100%工作,因此它被打破了。

It turns out that the method that seems the most robust to me is to use 事实证明,对我来说最健康的方法就是使用

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

Crazy

If you want an insane function: you probably observed that the cdl I gave doesn't deal with hidden directory. 如果你想要一个疯狂的功能:你可能发现我给的cdl没有处理隐藏目录。 So how about we allow a call like the following: 那么我们如何允许如下调用:

cdl .

that will switch on the search for hidden directories too? 那也将开启搜索隐藏目录? and wait, how about we allow arguments to the function, so that a call like 等等,我们如何允许函数参数,以便调用就好

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

will cd to the most recent subdirs of /path/to/dir1 , /path/to/dir2 , etc. (including hidden dirs)? cd到最新的/path/to/dir1/path/to/dir2等子目录(包括隐藏的dirs)? The switch for hidden dirs should be the first argument, so that 隐藏目录的开关应该是第一个参数,所以

cdl /path/to/dir1 .

will cd into the most recent non-hidden subdir of /path/to/dir1 and current directory, but cd进入/path/to/dir1和当前目录的最新非隐藏子目录,但是

cdl . /path/to/dir1 .

would also include hidden directories. 还包括隐藏目录。

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"
}

My next edit will include an update that will allow a call to cdl that also brews coffee while computing the last digit of π. 我的下一个编辑将包括一个更新,允许调用cdl ,同时在计算π的最后一位数时也会冲泡咖啡。

If you switch to single quotes it should work: 如果你切换到单引号它应该工作:

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

Please note that the ls in the command won't necessarily provide the newest directory , it might also yield a file, in which case the command won't work as you expect. 请注意,命令中的ls不一定提供最新的目录 ,它也可能产生一个文件,在这种情况下命令将无法按预期工作。 Adding the --group-directories-first option might help in that regard. 在这方面,添加--group-directories-first选项可能会有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM