很多时候运行完命令才忘了sudo,那么
- 方向上键到上一条命令,然后方向左键或home键到命令最左端,添加"sudo ",回车
- Ctrl-P回到上一条命令,Ctrl-A回到最左端,添加"sudo ",回车
- sudo !!,回车
很明显第三个是最简单的。
同样很多时候,一条命令的输出太长,需要pipe到less中或者Grep中,那么用alias或者函数会方便很多。可是"!!"没法写入到.zshrc中。
Google相关信息,出现频率最高的是这个命令:fc -nl -1
,可以直接在得到历史记录中的上一条命令。
不过有一个问题:当一个命令的前面出现若干空格时,这条命令不会出现在历史记录中
,那么这时候fc命令无法返回我们想获得的那条命令。
同时,当上条命令中出现任意alias时,通过fc命令返回上条命令会出现command not found
提示,貌似是因为alias默认无法在non-interactive shell中生效。
那么,fc -nl -1
有两个问题:
- 上个命令中有alias时无效
- 上个命令中最开始有若干空格时无效
先解决问题1
既然上个命令中有alias无法识别,那么将命令中的alias还原成原始命令,问题即可解决。
zsh中有个函数_expand_alias,其作用是当当前光标所在的word是一个alisa时,会自动将alisa还须成该alias的值,也就是原始的命令。
有函数了,那么还需要个条件来触发函数。
在ZSH中,每一个函数都可以当做一个Widget,通过Widget绑定到某个按键组合从而可以实现对Widget的触发。
那么就这样,
# 新建一个函数 expand_alias_space () { zle _expand_alias zle self-insert } # 新建一个Widget zle -N expand_alias_space # 绑定到空格键 bindkey " " expand_alias_space
效果图:
当命令中只有一个alias时,输入完alias后直接就回车,不输入空格就无法触发Widget,就无法expand alias了。
所以对于Enter
键也同样要绑定Widget,不过这个Widget会略微有所不同。
# 新建一个函数
expand_alias_enter () {
if [[ -z $BUFFER ]]
then
zle clear-screen
else
zle _expand_alias
zle accept-line
fi
}
# 新建一个Widget
zle -N expand_alias_enter
# 绑定到回车键
bindkey "^M" expand_alias_enter
expand_alias_enter
函数首先判断当前输入内容是否为空,如果为空,就帮个忙,把屏幕清一下,如果不为空,就expand alias,然后执行命令。
效果图:
OK,问题1解决。
然后就是问题2
解决了问题1那问题2就很简单了。
既然部分情况下命令不会被记录到历史中,那么咱来手动记录。
每次回车的时候把当前记录记录下来就可以了吧。
修改expand_alias_enter
函数为:
expand_alias_enter () { if [[ -z $BUFFER ]] then zle clear-screen else zle _expand_alias zle accept-line # 保存当前命令 LAST_COMMAND=$CURRENT_COMMAND CURRENT_COMMAND=$BUFFER fi }
这样上次执行的命令保存在$LAST_COMMAND
中,那么eval $LAST_COMMAND
就可以执行上次命令。
嗯,问题2解决。
任务完成
其实还是有问题的,如果两条相邻的命令都执行了eval $LAST_COMMAND
,当前命令执行eval $LASZT_COMMAND
,也就是上条命令,而上条命令也执行eval $LAST_COMMAND
,此时$LAST_COMMAND
中保存的还是上条命令,那继续执行上条命令,然后就死循环了:
zsh: job table full or recursion limit exceeded
这个问题解决起来就比较麻烦了。
思路
要消除循环,就要将$LAST_COMMAND
的值替换成具体的命令。
那么就需要在用到了$LAST_COMMAND
的函数中将当前函数中执行过的所有命令都顺序存放在某个buffer中,在执行完函数后,检测下buffer,如果不为空,那么就将其中的值作为本次执行过的命令内容,存放在$CURRENT_COMMAND
中。
这样虽然历史记录中显示的是函数名,但是$CURRENT_COMMAND
中记录的是这个函数对应的所有命令,不包含$LAST_COMMAND
,也就不会出现循环问题。
实现
函数
下面实现一个显示上次执行过的命令的函数:
<pre><code>fun() { # 将本函数要执行的命令赋给BUFFER_FOR_LAST_COMMAND BUFFER_FOR_LAST_COMMAND="echo \[`echo $LAST_COMMAND`\]" # 执行该BUFFER eval $BUFFER_FOR_LAST_COMMAND } </code></pre>
执行完函数后检测变量$BUFFER_FOR_LAST_COMMAND
,如果不为空,将其值赋给$CURRENT_COMMAND
,然后删除变量$BUFFER_FOR_LAST_COMMAND
。
这个过程要在执行完函数后触发,Widgetzle-line-init
可以满足要求。
function zle-line-init { if [ -n "$BUFFER_FOR_LAST_COMMAND" ] then # 如果BUFFER不为空,则将其值作为本次运行的命令 CURRENT_COMMAND=$BUFFER_FOR_LAST_COMMAND eval unset -v $BUFFER_FOR_LAST_COMMAND fi zle reset-prompt } zle -N zle-line-init
效果如图:
Alias
至于alias可以用函数实现,就这样。
任务完成
总感觉第二部分有问题,但又不知道到底什么地方有问题,先就这样,以后再改。
这是第一篇用Markdown写的博文,在马克飞象上完成。界面手感都不错,就是vim模式下切换到普通模式比较麻烦,需要将输入法切换到英文模式才能操作,不方便。用dillinger.io导出。导出后的样式和马克飞象中差太多。
有时间配置vim的Markdown插件,配合fcitx.vim至少可以少按几下Shift,虽然插件有很明示的延时。
感谢依云提供的Widget,当用来执行固定操作时这种方式更合适。
sudo-command-line() { # If current buffer is empth, get the last command [[ -z $BUFFER ]] && zle up-history # If the command not start with sudo [[ $BUFFER != sudo\ * ]] && { typeset -a bufs bufs=(${(z)BUFFER}) # If the first word in BUFFER is an alias, replace is with # it's value if (( $+aliases[$bufs[1]] )); then bufs[1]=$aliases[$bufs[1]] fi bufs=(sudo $bufs) BUFFER=$bufs } zle end-of-line } zle -N sudo-command-line bindkey "\e\e" sudo-command-line
本文链接地址: 由在ZSH运行上条命令展开
发表评论