如何在 zsh 命令中引用路径尾部的根?

如何在 zsh 命令中引用路径尾部的根?

我经常克隆一个 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 barin的单个修饰符序列/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])

相关内容