别名命令更改到最新目录
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
,在计算 π 的最后一位数字时也可以冲泡咖啡。
我想在我的 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
,在计算 π 的最后一位数字时也可以冲泡咖啡。