我想在我的bashrc文件中添加一个别名,以更改为当前目录中的最新子目录:
alias cdl="cd $(ls -t | head -n 1)"
问题是,当我获取文件时,命令仅被评估一次。如果我在更改目录后使用新的cdl
命令,它仍然会尝试更改为旧目录,这可能不会出现在我的新位置,并且对我来说并不是很有用。
如何在每次运行时对此命令进行别名评估?
答案 0 :(得分:4)
这是一种安全的方式来执行你想要的东西:安全我的意思是它会找到最新的目录(而不仅仅是你的情况下的文件,虽然这可以修复)和如果目录名包含空格,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选项会使globs扩展为空。在循环for i in */
中使用了一个带有尾部斜杠的glob,所以这会扩展到当前目录中的所有目录(如果没有目录,则没有任何内容,这要归功于nullglob
)。
我们将mrd
变量初始化为空字符串,然后循环遍历所有目录,循环变量为i
:如果mrd
为空或i
扩展为一个比(-nt
)mrd
更新的目录,然后我们将mrd
替换为i
。
在循环之后,如果mrd
仍为空(如果没有找到任何目录,则会发生这种情况),我们不做任何事情;否则我们使用尾随的nullbyte打印该目录名。
现在,外部部分:
IFS= read -r -d '' mrd < <( ... )
这将获取上面讨论的内部部分的输出(因此,无论是否为最新目录的内容,具有尾随的nullbyte)并将其存储在变量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 :(得分:2)
如果切换到单引号,它应该有效:
alias cdl='cd -- "$(ls -t | head -n 1)"'
请注意,命令中的ls
不一定会提供最新的目录,它也可能会产生一个文件,在这种情况下命令将无法正常工作。在这方面,添加--group-directories-first
选项可能会有所帮助。