将parse_git_branch函数从bash翻译成zsh(用于提示符)

7

我正在Bash中使用这个函数

function parse_git_branch {
  git_status="$(git status 2> /dev/null)"
  pattern="^# On branch ([^${IFS}]*)"
  if [[ ! ${git_status}} =~ "working directory clean" ]]; then
    state="*"
  fi
  # add an else if or two here if you want to get more specific

  if [[ ${git_status} =~ ${pattern} ]]; then
    branch=${BASH_REMATCH[1]}
    echo "(${branch}${state})"
  fi
}

但我决定使用zsh。虽然我可以完美地将其用作shell脚本(甚至没有shebang),但在我的.zshrc中,这个错误是在这一行if [[ ! ${git_status}}上的解析错误...

我需要做什么才能让它准备好用于zshell?

编辑:我收到的“实际错误”是" parse error near },它指的是具有奇怪双重}}的行,该行在Bash上有效。

编辑:以下是最终代码,仅供娱乐:

parse_git_branch() {
    git_status="$(git status 2> /dev/null)"
pattern="^# On branch ([^[:space:]]*)"
    if [[ ! ${git_status} =~ "working directory clean" ]]; then
        state="*"
    fi
    if [[ ${git_status} =~ ${pattern} ]]; then
      branch=${match[1]}
      echo "(${branch}${state})"
    fi
}

setopt PROMPT_SUBST
PROMPT='$PR_GREEN%n@$PR_GREEN%m%u$PR_NO_COLOR:$PR_BLUE%2c$PR_NO_COLOR%(!.#.$)'
RPROMPT='$PR_GREEN$(parse_git_branch)$PR_NO_COLOR'

感谢大家的耐心和帮助。
编辑:最佳答案给我们上了一堂课:git status是瓷器(用户界面)。良好的脚本编写应该遵循GIT管道。以下是最终函数:
# The latest version of Chris' function below

PROMPT='$PR_GREEN%n@$PR_GREEN%m%u$PR_NO_COLOR:$PR_BLUE%2c$PR_NO_COLOR%(!.#.$)'
RPROMPT='$PR_GREEN$(parse_git_branch)$PR_NO_COLOR'

请注意,仅提示是zsh特有的。在Bash中,它将是您的提示加上"\$(parse_git_branch)"
这可能会更慢(调用GIT次数更多,但这是一个实证问题),但它不会被GIT更改破坏(它们不会更改管道)。这对于良好的脚本向前移动非常重要。

在我看来,像这样使用IFS是没有意义的。IFS用于解析shell输入。从git status输出的结果永远不会依赖于IFS。您应该使用您期望git status输出的内容,可能是[^[:space:]]。但实际上,解析git status是错误的做法(它是瓷器而不是管道)。 - Chris Johnsen
@Chris Johnsen,我的理解是git实际上是检查目录以查看是否有来自索引的更改。如果不使用GIT或重新发明它,该怎么做呢? - Dan Rosenstark
@Chris Johnsen,修正了模式(本来就要做的 :) - Dan Rosenstark
不需要重新发明 Git 或避免使用它,只需使用其适当的脚本接口——管道命令。请参见我的答案。 - Chris Johnsen
@Chris Johnsen,好的,我会去看一下。没注意到你的答案,这可能会改变一下我的小parse_git_branch。 - Dan Rosenstark
7个回答

6
你应该使用Git的“plumbing”命令来提取所需信息。 “porcelain”命令(例如git status)的输出可能会随时间而变化,但“plumbing”命令的行为更加稳定。
使用外壳接口也可以完成此操作,而无需“bashisms”或“zshisms”(即=〜匹配运算符)。
parse_git_branch() {
    in_wd="$(git rev-parse --is-inside-work-tree 2>/dev/null)" || return
    test "$in_wd" = true || return
    state=''
    git update-index --refresh -q >/dev/null # avoid false positives with diff-index
    if git rev-parse --verify HEAD >/dev/null 2>&1; then
        git diff-index HEAD --quiet 2>/dev/null || state='*'
    else
        state='#'
    fi
    (
        d="$(git rev-parse --show-cdup)" &&
        cd "$d" &&
        test -z "$(git ls-files --others --exclude-standard .)"
    ) >/dev/null 2>&1 || state="${state}+"
    branch="$(git symbolic-ref HEAD 2>/dev/null)"
    test -z "$branch" && branch='<detached-HEAD>'
    echo "${branch#refs/heads/}${state}"
}

将输出集成到提示符中仍然是与shell相关的(即转义或引用$(对于bashzsh都是如此),并设置PROMPT_SUBST(对于zsh)。)。


看起来不错,谢谢。有什么最好的方法可以测试非Git目录(而且不输出任何内容)? - Dan Rosenstark
@jamessan,好的,我已经重新修改了并将其放在上面的问题中。不确定我们是否朝着正确的方向前进,三个git命令对比一个命令。另一方面,没有正则表达式...也许速度已经足够快了。 - Dan Rosenstark
@yar:如果你绝对负担不起额外的进程,那么你可能需要使用内置解析git status。问题在于,解析瓷器输出(例如git status)会使你容易受到行为/输出更改的影响(较新/较旧版本的Git的瓷器命令可能没有相同的行为)。Git开发人员非常小心地不更改现有管道命令/选项的行为。因此,它们构成了推荐的“脚本”接口。请参见git manpage中的“低级命令(管道)”。 - Chris Johnsen
@Chris Johnsen,我已经使用了几天,必须说:你是对的,使用管道而不是瓷器。但是,接下来你会遇到另一个问题:现在你正在编写自己的瓷器,这是很多工作量的。diff-index HEAD并不总是与针对“工作目录干净”的状态检查相同(例如,如果有新文件)。 - Dan Rosenstark
@yar,我已更新脚本以指示未跟踪的文件(显示为“+”)。 - Chris Johnsen
显示剩余8条评论

2

去掉多余的}符号?${git_status}} 应该改为 ${git_status}


一旦去掉额外的},我唯一看到的潜在问题就是使用${BASH_REMATCH[1]}。你可以在zsh中使用它,但需要启用相应的选项。如zsh文档中关于条件表达式所示,你需要使用类似以下语法:

if [[ ${git_status} =~ ${pattern} ]]; then
  branch=${match[1]}
  echo "(${branch}${state})"
fi

已经尝试过了,然后在同一行出现了不同的错误。基本上这将需要一个懂 zsh 的人,我想。谢谢。 - Dan Rosenstark
Bash将${git_status}}视为"whatever_git_status_expands_to}",而Zsh将${git_status}}视为解析错误,因为你有一个杂项}。如果您希望它们相同,则将${git_status}}放在引号中--"${git_status}}" - jamessan
做到了。现在出现了错误:“parse_git_branch:8: failed to compile regex: illegal byte sequence”……感谢@jamessan的帮助。如果我们能够将其作为内存函数运行,那就太棒了。 - Dan Rosenstark
我在我的回答中建议的更改对我来说完全有效,所以我建议您更新您的问题并提供您当前的函数。 - jamessan
1
那我很好奇你实际上将$IFS设置为了什么。默认值\x20\t\n\x00对我来说很有效。或者,你可以使用[^[:space:]]代替[^${IFS}] - jamessan
显示剩余7条评论

2
你可以使用数组match代替$BASH_REMATCH,也可以转义多余的闭合花括号。

未经测试:

function parse_git_branch {
  git_status="$(git status 2> /dev/null)"
  pattern="^# On branch ([^${IFS}]*)"
  if [[ ! ${git_status}\} =~ "working directory clean" ]]; then
    state="*"
  fi
  # add an else if or two here if you want to get more specific

  if [[ ${git_status} =~ ${pattern} ]]; then
    branch=${match[1]}
    echo "(${branch}${state})"
  fi
}

尝试一下,看看是否有帮助。


谢谢Dennis,目前一切都很好,匹配工作正如Jamessan在他的修订中建议的那样。 - Dan Rosenstark

1

重点是它必须是一个内联函数。正如我在问题中所述,作为一个 sh 脚本它运行得很好。虽然我可以再用 Ruby 实现一遍,但这并不会使其加载更快,这才是关键。 - Dan Rosenstark
虽然我看过zsh-git-prompt项目,但似乎它并不会在每次按下回车键时都解析...? - Dan Rosenstark
是的,每次按回车键时它都会解析。 - Olivier Verdier
好的,我会检查一下,它比现有的脚本(上面)有明显的优势。 - Dan Rosenstark

1
如果出现错误 failed to compile regex: illegal byte sequence,则从IFS中删除NULL(将${IFS}替换为${IFS//$'\0'/})。

我正在使用的zsh 4.3.10上,带有\0$IFS能够正常工作。 - jamessan
虽然你的回答基本上解决了我的问题,但是jamessan一整天都在和我一起努力解决这个问题 :) 非常感谢你们两位的帮助。 - Dan Rosenstark
1
Zsh 4.3.4 还添加了 [:IFSSPACE:] - jamessan
很酷,代码里可能会更好看。我稍后会检查一下(这有点分散了我真正的工作)。再次感谢你的耐心和帮助。 - Dan Rosenstark

0

如果有人对 git-prompt 功能的替代解决方案感兴趣,我已经在 github.com 上发布了这个脚本,但为了方便起见,您可以从这里获取。

它对我来说非常好用,希望对你也一样。

## Setting Prompt Colour(s):
invColor="$(tput rev)";   ## tput rev - Inverse
resColor="$(tput sgr0)";  ## tput sgr0 - Reset

## Custom Prompt Colour(s):
_BlackBG="$(tput setab 0)";
bGreenFG="$(tput bold; tput setaf 2)";
bMagentaFG="$(tout bold; tput setaf 5)";
bRedFG="$(tput bold; tput setaf 1)";
bBlueFG="$(tput bold; tput setaf 4)";
bCyanFG="$(tput bold; tput setaf 6)";
bWhiteFG="$(tput bold; tput setaf 7)";

## Define Enclosing-character(s):
_Bracket="${resColor}${_BlackBG}${bWhiteFG}";
oBracket="${_Bracket}["; cBracket="${_Bracket}]${resColor}";

## Bold-Foreground Color(s):
## tput bold - Bold

function git_branch () {
# git_branch() { git name-rev HEAD 2> /dev/null | sed 's#HEAD\ \(.*\)#git::\1#'; }
    git branch --no-color 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/git::\1/';
    return 0;
  }

## Version Control System - Active Working Copy:
function get_branch () {
    xBranch="`git_branch`";
    if [[ "${xBranch}" =~ "git::master" ]]; then
        xBranch="git::${bWhiteFG}master";
      else xBranch="`echo ${xBranch}|sed -e 's/git:://g'`";
        xBranch="branch::${bGreenFG}${xBranch}";
    fi
    if git rev-parse --git-dir >/dev/null 2>&1; then
        _pVCS="";
        if git diff --quiet 2>/dev/null >&2; then
          if [[ "${xBranch}" =~ "git::" ]]; then
              _pVCS="${bGreenFG}${xBranch}";
            else _pVCS="${bMagentaFG}${xBranch}";
          fi
          else _pVCS="${bRedFG}${xBranch}";
        fi
      else return 0;
    fi
    if [[ "${_pVCS}" =~ "no branch" ]]; then
      xTAG="`git tags|awk '{print $3}'|sed -e 's/[,;)]//g'`";
      _pVCS="${bBlueFG}tag::${bCyanFG}${xTAG}";
    fi
## Output Git Active-Repo ID:
   if [[ "${_pVCS}" =~ [::] ]]; then
      echo -ne "${oBracket}${_BlackBG}${_pVCS}${cBracket}";
   fi
#    return 0;
  }

使用方法:

declare PS1='\[\033[01;36m\]\u\[\033[01;31m\]@\[\033[01;32m\]\h\[\033[00m\]:\[\033[01;33m\]\w $(get_branch) \[\033[01;31m\]${XPT}\[\033[00m\] '

0

感谢 VonC 的一如既往。如果可能的话,我有点想寻找简单的方法,但让我们看看这个问题如何解决吧。 - Dan Rosenstark

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接