从 .bashrc 或 .zshrc 修改现有函数

modify an existing function from .bashrc or .zshrc

/usr/share/zsh/functions/Completion/Unix/_git

中定义了一个函数
(( $+functions[_git-diff] )) ||
_git-diff () {
  local curcontext=$curcontext state line ret=1
  declare -A opt_args

  local -a diff_options diff_stage_options
  __git_setup_diff_options
  __git_setup_diff_stage_options

  _arguments -C -s $endopt \
    $* \
    $diff_options \
    '(--exit-code)--quiet[disable all output]' \
    $diff_stage_options \
    '(--cached --staged)--no-index[show diff between two paths on the filesystem]' \
    '(--cached --staged --no-index)'{--cached,--staged}'[show diff between index and named commit]' \
    '(-)--[start file arguments]' \
    '*:: :->from-to-file' && ret=0

  case $state in
    (from-to-file)
      # If "--" is part of $opt_args, this means it was specified before any
      # $words arguments. This means that no heads are specified in front, so
      # we need to complete *changed* files only.
      if [[ -n ${opt_args[(I)--]} ]]; then
        if [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
          __git_changed-in-index_files && ret=0
        else
          __git_changed-in-working-tree_files && ret=0
        fi
        return ret
      fi

      # If "--no-index" was given, only file paths need to be completed.
      if [[ -n ${opt_args[(I)--no-index]} ]]; then
        _alternative 'files::_files' && ret=0
        return ret
      fi

      # Otherwise, more complex conditions need to be checked.
      case $CURRENT in
        (1)
          local files_alt='files::__git_changed-in-working-tree_files'
          if [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
            files_alt='files::__git_changed-in-index_files'
          fi

          _alternative \
            'commit-ranges::__git_commit_ranges' \
            'blobs-and-trees-in-treeish::__git_blobs_and_trees_in_treeish' \
            $files_alt \
            'blobs::__git_blobs ' && ret=0
          ;;
        (2)
          # Check if first argument is something special. In case of committish ranges and committishs offer a full list compatible completions.
          if __git_is_committish_range $line[1]; then
            # Example: git diff branch1..branch2 <tab>
            __git_tree_files ${PREFIX:-.} $(__git_committish_range_last $line[1]) && ret=0
          elif __git_is_committish $line[1] || __git_is_treeish $line[1]; then
            # Example: git diff branch1 <tab>
            _alternative \
              'commits::__git_commits' \
              'blobs-and-trees-in-treeish::__git_blobs_and_trees_in_treeish' \
              'files::__git_tree_files ${PREFIX:-.} HEAD' && ret=0
          elif __git_is_blob $line[1]; then
            _alternative \
              'files::__git_cached_files' \
              'blobs::__git_blobs' && ret=0
          elif [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
            # Example: git diff --cached file1 <tab>
            __git_changed-in-index_files && ret=0
          else
            # Example: git diff file1 <tab>
            __git_changed-in-working-tree_files && ret=0
          fi
          ;;
        (*)
          if __git_is_committish_range $line[1]; then
            # Example: git diff branch1..branch2 file1 <tab>
            __git_tree_files ${PREFIX:-.} $(__git_committish_range_last $line[1]) && ret=0
          elif { __git_is_committish $line[1] && __git_is_committish $line[2] } ||
              __git_is_treeish $line[2]; then
            # Example: git diff branch1 branch2 <tab>
            __git_tree_files ${PREFIX:-.} $line[2] && ret=0
          elif __git_is_committish $line[1] || __git_is_treeish $line[1]; then
            # Example: git diff branch file1 <tab>
            # Example: git diff branch -- f<tab>
            __git_tree_files ${PREFIX:-.} HEAD && ret=0
          elif __git_is_blob $line[1] && __git_is_blob $line[2]; then
            _nothing
          elif [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
            # Example: git diff --cached file1 file2 <tab>
            __git_changed-in-index_files && ret=0
          else
            # Example: git diff file1 file2 <tab>
            __git_changed-in-working-tree_files && ret=0
          fi
          ;;
      esac
      ;;
  esac

  return ret
}

我只需要追加

'files::__git_changed_files ${PREFIX:-.} HEAD' \

以上

'files::__git_tree_files ${PREFIX:-.} HEAD' && ret=0

目前我已将新函数复制粘贴到我的 .zshrc 中,它正在运行。

但是,我认为有一种更简洁的方法可以使用 .bashrc.zshrc 覆盖现有函数,如下所示:

_git-diff 2>/dev/null 
functions[_git-diff-orig]=$functions[_git-diff]  
_git-diff() {
    _git-diff-orig "$@"
    ... 
} 

但我不知道该怎么做。谁能帮忙。明确一点,我想使用 .bashrc.zshrc 覆盖该函数,以便它更便携。

zsh中,functions关联数组中的值为规范化代码文本。这意味着您可以使用任何常用的文本操作方法来修改它:

foo() {
  echo before
  echo and after
}

foo
# => before
# => and after

functions[foo]=${functions[foo]/echo before/echo before;echo during}

foo
# => before
# => during
# => and after

functions foo
# => foo () {
# =>   echo before
# =>   echo during
# =>   echo and after
# => }

有趣的是,zsh 将解析、验证和规范化分配给 functions 数组的代码——这与正常声明函数本质上是相同的过程。这就是为什么 functions foo 的输出有一个换行符,即使字符串替换使用了分号。

将这些行添加到 ~/.zshrc 应该适用于您的示例:

current="'files::__git_tree_files ${PREFIX:-.} HEAD'"
replacement="'files::__git_changed_files ${PREFIX:-.} HEAD' $current"
functions[_git-diff]=${functions[_git-diff]/$current/$replacement}

修改函数的其他方法

有多种方法可以更改 shell 语言的文本,其中一些 对于更复杂的功能更改可能会更好。注意更多变化 会使过程更加脆弱,因为对基本代码的更新可能会失败 修补程序。

另请注意,更改已应用于存储在 functions数组;可能与原始源文件中的内容不匹配。

使用patch

显而易见的选择,因为我们正在修补代码。 patch 实用程序修改 基于 diff 输出的文本(通常是 diff -u)。该实用程序可以处理 一些空格差异,以及一些错误,例如不正确的行号:

plan() {
    forecast=
    if [[ $forecast == sun ]]; then
        print "take sunscreen"
        print "wear hat"
    elif [[ $forecast == rain ]]; then
        print "take umbrella"
        print "carry raincoat"
    else
        print "stay home"
    fi
}
plan sun
#=> take sunscreen
#=> wear hat

plan hail
#=> stay home


patchDiff='
--- plan1
+++ plan2
@@ -4,3 +4,5 @@
        print "take sunscreen"
+       print "apply sunscreen"
        print "wear hat"
+       print "use sunglasses"
    elif [[ $forecast == rain ]]
@@ -11,2 +13,3 @@
        print "stay home"
+       print "and relax"
    fi'
functions[plan]=$(print -- $patchDiff \
    | patch -ls -o >(cat) =(print -- $functions[plan]))

plan sun
#=> take sunscreen
#=> apply sunscreen
#=> wear hat
#=> use sunglasses

plan hail
#=> stay home
#=> and relax

使用sed

使用 sed 流编辑器,并在前面的示例的基础上构建:

sedscript='s/print *\(.*\)/print ${(C):-}/'
functions[plan]=$(print $functions[plan] | sed -e $sedscript)

functions plan
#=> plan () {
#=>     forecast= 
#=>     if [[ $forecast == sun ]]
#=>     then
#=>         print ${(C):-"take sunscreen"}
#=>         print ${(C):-"apply sunscreen"}
#=>         print ${(C):-"wear hat"}
#=>         print ${(C):-"use sunglasses"}
#=>     elif [[ $forecast == rain ]]
#=>     then
#=>         print ${(C):-"take umbrella"}
#=>         print ${(C):-"carry raincoat"}
#=>     else
#=>         print ${(C):-"stay home"}
#=>         print ${(C):-"and relax"}
#=>     fi
#=> }

plan rain
#=> Take Umbrella
#=> Carry Raincoat

按行

这不需要匹配函数中的任何特定内容。两者都是 好的和坏的 - 它很容易实现,但它可以打破甚至简单 基本功能的更改:

# split the code into an array of lines (f)
lines=("${(f)functions[plan]}")

# add code in the middle
newCode=(
    ${lines[1]}
    'if [[ $forecast == tornado ]]; then
        print "find shelter"
        print "go quickly"
        return
    fi'
    ${lines[2,-1]})

# rejoin array with newlines (F)
functions[plan]=${(F)newCode}

functions plan
#=> plan () {
#=>     forecast= 
#=>     if [[ $forecast == tornado ]]
#=>     then
#=>         print "find shelter"
#=>         print "go quickly"
#=>         return
#=>     fi
#=>     if [[ $forecast == sun ]]
#=>     then
#=>         print ${(C):-"take sunscreen"}
#=>         print ${(C):-"apply sunscreen"}
#=>         print ${(C):-"wear hat"}
#=>         print ${(C):-"use sunglasses"}
#=>     elif [[ $forecast == rain ]]
#=>     then
#=>         print ${(C):-"take umbrella"}
#=>         print ${(C):-"carry raincoat"}
#=>     else
#=>         print ${(C):-"stay home"}
#=>         print ${(C):-"and relax"}
#=>     fi
#=> }

plan tornado
#=> find shelter
#=> go quickly