是否可以在 Git 中对 shell 的内置命令使用别名扩展?

Is it possible to use alias expansion with shell's builtin commands in Git?

我的 .gitconfig 中有以下别名:

freq = !history | cut -c 8- | grep ^git | sort | uniq -c | sort -n -r | head -n 5

它应该显示我最常用的 5 个 git 命令。取自这里:http://alblue.bandlem.com/2011/04/git-tip-of-week-aliases.html(我知道这篇文章是旧的)

当我运行命令时,它没有输出任何东西:

$ git freq
$

但是如果我运行history | cut -c 8- | grep ^git | sort | uniq -c | sort -n -r | head -n 5,给出的输出是:

$ history | cut -c 8- | grep ^git | sort | uniq -c | sort -n -r | head -n
   5 git log
   5 git status
   5 git current
   5 git co master
   4 git co other-branch

我猜这是因为 history 是一个 shell 内置命令,如果我更改 freq 别名,例如至:

freq = !history

我得到:

$ git freq
error: cannot run history: No such file or directory
fatal: While expanding alias 'freq': 'history': No such file or directory
$

有没有办法让它起作用?

原因

看完问题后,我也认为问题是由于 history 是一个 shell 内置的。

调试git

Git命令可以通过设置GIT_TRACE环境变量来调试 至 true(有关详细信息,请参阅 Scott Chacon 的 Git 书中关于 Git Internals - Environment Variables 的相关部分)。

$ GIT_TRACE=true git freq

10:14:11.901296 git.c:554               trace: exec: 'git-freq'
10:14:11.901296 run-command.c:341       trace: run_command: 'git-freq'
10:14:11.932496 run-command.c:341       trace: run_command: 'history | tail'
10:14:11.963697 run-command.c:192       trace: exec: '/bin/sh' '-c' 'history | tail' 'history | tail'

Checking the source 显示外部命令由调用 sane_execvpexecv_shell_cmd 函数处理,这是 execvp [=87 的一个更用户友好的前端=]standard library 函数——如果命令名称不包含斜杠——搜索 PATH 中的每个目录以查找与命令同名的可执行文件。由于 history 是一个 shell 内置的,它永远不会被发现,所以它 returns 是一个错误。参见


我不是一个听之任之的人,后来我使用其他 shell 内置插件进行了进一步的实验,这些实验成功了,所以我认为它一定是别的东西:

没有非交互式 shells

的历史记录

阅读 Bash 手册解释说 Bash History Facilities are only used in interactive shells 而 shell 由 exec 调用启动是非交互式的。这就是为什么 运行 宁 history 内置不打印任何输出。


解决方案

选项 1:在非交互式中打开历史功能 shell

感谢Gilles on Unix and Linux,原来非交互shell中的历史特征可以通过设置HISTFILE shell变量开启,然后运行ning set -o history 可以看出 运行ning:

bash -c "HISTFILE=~/.bash_history; set -o history; history"

.gitconfig 中设置以下别名将起作用:

freq = "!HISTFILE=$HOME/.bash_history; set -o history; history | cut -c 8- | grep ^git | sort | uniq -c | sort -n -r | head -n 5"

注意:如果您使用 shell 而不是 Bash,您需要指定 shell 保存其命令历史记录的文件的名称。如果您使用 Bash,可能 HISTFILE shell 变量设置为 $HOME/.bash_history.

以外的值

此外,shell 变量(例如 HISTFILE 不应作为环境变量导出(可用于 git),这就是我在这里没有使用它的原因。

选项 2:从历史文件中读取

一个更简单的解决方案是直接从 shell 历史文件中读取:

freq = !grep ^git $HOME/.bash_history | sort | uniq -c | sort -n -r | head -n 5

这将给出与 运行 在新的交互式 shell 中使用 history 命令相同的结果,因为当新的 shell 启动时,它没有命令它的历史不同于它从历史文件中读取的内容。

此外,直接从历史文件中读取时不需要cut命令。

选项 3:Bash别名

另一种选择是忘记使用 Git 别名,而只使用 Bash 别名:

alias git-freq='history | cut -c 8- | grep ^git | sort | uniq -c | sort -n -r | head -n

使 shell 历史文件保持最新

我想添加以下有用的辅助信息。请注意,它适用于交互式 Bash shell,它可能适用于也可能不适用于其他现代 shell。

我经常在同一台主机上同时 运行 多个 shell 并且我不希望在退出 shell 时覆盖保存的历史命令。这是我的 .bashrc 设置,可确保在每个命令后写入 shell 历史文件:

# Append history entries on logout - instead of over-writing the history file.
shopt -s histappend

# Don't store duplicate lines in the history.
# Ignore any commands that begin with a space.
HISTCONTROL=ignoredups:ignorespace

# Use a very large history file to store lots of commands.
# See http://mywiki.wooledge.org/BashFAQ/088
HISTFILESIZE=5000
# Keeping a large history requires system resources
HISTSIZE=3000

# Append to the history file after every command.
PROMPT_COMMAND="history -a"