我经常克隆一个 git 存储库,然后进入其根目录。例如:
$ git clone https://github.com/hpjansson/chafa && cd chafa
为了使这更容易一些,我有一个 zsh 缩写 – cc
– 扩展为&& cd !#:2:t
:
typeset -Ag abbrev
abbrev=(
'G' '| grep -v grep | grep'
'L' '2>&1 | less'
'V' '2>&1 | vipe >/dev/null'
'ac' '| column -t'
'bl' '; tput bel'
'cc' '&& cd !#:2:t'
'fl' "| awk '{ print $"
'ne' '2>/dev/null'
'pf' "printf -- '"
'sl' '>/dev/null 2>&1'
'tl' '| tail -20'
)
__abbrev_expand() {
emulate -L zsh
setopt EXTENDED_GLOB
local MATCH
LBUFFER=${LBUFFER%%(#m)[a-zA-Z]#}
if [[ "${LBUFFER: -1}" == ' ' ]]; then
LBUFFER+=${abbrev[$MATCH]:-$MATCH}
if [[ $MATCH = 'fl' ]]; then
RBUFFER="}'"
elif [[ $MATCH = 'pf' ]]; then
RBUFFER="\n'"
fi
else
LBUFFER+=$MATCH
fi
zle self-insert
}
zle -N __abbrev_expand
bindkey ' ' __abbrev_expand
bindkey -M isearch ' ' self-insert
!#
指当前命令行,如man zshexpn
(section HISTORY EXPANSION
, subsection Event Designators
) 中所述:
!#
请参阅目前输入的当前命令行。该行被视为完整的,直到并包括带有引用的行之前的单词!#
。
:2
指的是命令行上的第二个单词(这是我输入命令时的url $ git clone
):
n
第 n 个参数。
并:t
指该词的尾部:
t
删除所有前导路径名组件,保留尾部。这就像basename
.
通常,我会git clone
在命令行中输入,然后复制粘贴项目的 url,然后插入一个空格,cc
然后插入另一个空格,这会给出所需的命令。
但是,有时,我复制粘贴的网址以扩展名结尾.git
。当发生这种情况时, 的扩展!#:2:t
包含不需要的.git
扩展:
$ git clone https://github.com/hpjansson/chafa.git cc
→
$ git clone https://github.com/hpjansson/chafa.git && cd !#:2:t
→
$ git clone https://github.com/hpjansson/chafa.git && cd chafa.git
cd: no such file or directory: chafa.git
一种解决方案是还使用:r
修饰符来删除.git
扩展名:
r
删除文件扩展名,保留根名称。没有文件扩展名的字符串不会被更改。文件扩展名是.
后跟任意数量的字符(包括零),这些字符既不是.
也不是/
,并且持续到字符串末尾。例如, 的扩展名foo.orig.c
是.c
,并且dir.c/foo
没有扩展名。
vv
$ git clone https://github.com/hpjansson/chafa.git && cd !#:2:t:r
→
$ git clone https://github.com/hpjansson/chafa.git && cd chafa
但是,如果路径不以.git
扩展名结尾,则扩展失败,并且git
命令和cd
命令都不会执行。
这是一个说明问题的最小示例:
$ echo /foo/bar.baz !#:1:t:r
/foo/bar.baz bar
$ echo /foo/bar !#:1:t:r
zsh: modifier failed: r
第一个命令成功,因为最后一个路径组件包含扩展名.baz
,但第二个命令失败,因为不再有扩展名。 OTOH,在 bash 中4.3.48
,这两个命令都按预期工作。
我不明白为什么:r
在没有扩展的情况下会失败,因为它的文档包含这句话:
没有文件扩展名的字符串不会被更改。
它并不是说使用:r
不包含扩展名的字符串是错误的。
我尝试了另一种方法;使用修饰符删除扩展名:s
。我认为这需要HIST_SUBST_PATTERN
设置选项:
setopt HIST_SUBST_PATTERN
使用此选项,可以编写:s/.*
删除扩展名:
vvvvv
$ echo /foo/bar.baz !#:1:t:s/.*
/foo/bar.baz bar
当有扩展名时它可以工作,但当没有扩展名时它会再次失败:
$ setopt HIST_SUBST_PATTERN
$ echo /foo/bar !#:1:t:s/.*
zsh: substitution failed
bar
是否存在可以同时引用in/foo/bar.baz
和 to bar
in的单个修饰符序列/foo/bar
?
我在用着zsh 5.7.1-dev-0 (x86_64-pc-linux-gnu)
。
答案1
我想不出通过历史扩展来做到这一点的方法。但我不认为历史扩展是最好的工具。这是非常有限的。来自一个零乐小部件,您可以访问命令行并使用任意代码对其进行操作。利用这一点。
一种方法是对缩写进行扩展:$abbrev[$MATCH]
使用代替${(e)abbrev[$MATCH]}
。显然,您需要更改缩写以适当地引用特殊字符。对于cc
,使用类似
&& cd ${${(z)BUFFER}[3]}
对于 的特定情况git clone
,更一般地对于在当前目录中创建目录的任何内容,您可以使用不同的缩写:切换到新创建的目录。它适用于git clone https://example.com/foo.git
、 forgit clone https://example.com/foo
和 for git clone https://example.com/foo.git bar
,以及 for tar xf foo.tar
(如果存档有一个顶级目录)和mv /some/directory .
.
&& cd *(/oc[1])