标签归档:zsh

zsh的字符串替换引起的卡顿

我的mac系统每次启动后第一次打开iterm2的时候,oh-my-zsh的启动总是明显的卡顿一下,而之后退出iterm2重启动则不会有这个卡顿,也就是只在第一次启动iterm2的时候发生。对启动的zsh增加了-xv参数后观察,发现这个卡顿发生在git_compare_version函数的第4行:

找到这个函数后,发现第4行的操作并不是git等网络操作,而是一个字符串替换的操作,它使用zsh内置的字符串替换功能:INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]})

非常的不符合直觉(直觉上以为卡顿是因为网络阻塞引起的),模拟一下,在一个脚本里使用这个字符串替换操作,看看具体的耗时情况:

$ cat zsh-test.sh
#!/usr/bin/env zsh -xv
export PS4=$'%D{%M%S%.} %N:%i> '

INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null));
INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]});
echo "$INSTALLED_GIT_VERSION"

然后再启动时调用这个zsh脚本:

$ cat run.sh
#!/usr/bin/env zsh -xv
export PS4=$'%D{%M%S%.} %N:%i> '
./zsh-test.sh

重启系统,启动后在bash下执行run.sh脚本:

INSTALLED_GIT_VERSION=($(command git --version 2>/dev/null));
5649865 ./zsh-test.sh:4> INSTALLED_GIT_VERSION=5649867 ./zsh-test.sh:4> git --version
5649865 ./zsh-test.sh:4> INSTALLED_GIT_VERSION=( git version 2.8.4 '(Apple' 'Git-73)' ) 
INSTALLED_GIT_VERSION=(${(s/./)INSTALLED_GIT_VERSION[3]});
5650999 ./zsh-test.sh:5> INSTALLED_GIT_VERSION=( 2 8 4 )

看到zsh-test.sh里的第5行字符串替换的操作耗时用了1秒多时间,如果再次执行的话会降到几个毫秒。这真是个蹊跷的问题,发邮件给 zsh-works@zsh.org 好几周也没有人回复,先在博客里记录一下这个问题,以后再追踪。zsh版本是:5.2 (x86_64-apple-darwin16.0.0)。

shell前边的连字符含义

在一个脚本里,要获取其父shell时,使用了下面的方式:

#!/bin/bash 
ps -ocomm= -p $(ps -oppid= $$)

它在某些环境下,父shell会显示为 “/usr/local/bin/zsh” 或者 “bash” ,而某些环境下却会显示为”-bash”或”-zsh”;这个开头多出来的连字符是怎么回事?查了一下,原来是表示的是”login shell”。

linux下可以在”/etc/passwd”里看到用户的login shell,而在mac下要确认当前用户的login shell,要通过下面的命令:

$ dscl . read /users/$USER UserShell
UserShell: /bin/bash

在mac下,当打开终端程序(Terminal.app)时,终端shell是login进程的子进程(不管你配置那种command):

$ pstree
 ...
 |-+= 01272 hongjiang /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal
 | \-+= 01275 root login -pf hongjiang
 |   \-+= 01276 hongjiang -bash
 ...

$ ps -ef | grep login
0  1275  1272   0  2:05PM ttys000    0:00.05 login -pf hongjiang 

# 把Terminal的启动命令修改为zsh也一样

 |-+= 01988 hongjiang /Applications/Utilities/Terminal.app/Contents/MacOS/Terminal
     | \-+= 01991 root login -pf hongjiang /bin/zsh  |   \-+= 01992 hongjiang -zsh

而在mac的 iTerm.app 下,不管如果你配置的command是”Login shell”还是修改为其他shell,启动后shell没有再挂在login进程下:

$ pstree
 |-+= 00574 hongjiang /Volumes/Data/program/iTerm.app/Contents/MacOS/iTerm
 | \-+= 02177 hongjiang /usr/local/bin/zsh  

但在iTerm下如果使用的是“Login shell”显示的名称前边却是有连字符的,而其他shell则没有

$ ps $$
  PID   TT  STAT      TIME COMMAND
 2220 s001  Ss     0:00.01 -/bin/bash

# 修改启动shell为zsh

➜  ps $$
  PID   TT  STAT      TIME COMMAND
 2524 s000  Ss     0:00.16 /usr/local/bin/zsh

要想在iTerm下保持跟Terminal一致也用login来启动,应该在配置里修改启动命令为:

login -pf $username /usr/local/bin/zsh   

在linux下,从tty登录shell也是一样由login进程启动的

$ ps -ef | grep bash
 hongjia+  2241   467  0 14:09 tty1     00:00:00 -bash

$ pstree 
systemd─┬─NetworkManager─┬─2*[dhclient]
    │                └─3*[{NetworkManager}]
    ├─...
    ├─login───bash
    ...   

$ ps -ef | grep login
root       467  0.0  0.2  84584  2328 ?        Ss   14:08   0:00 login -- hongjiang    

使用su命令切换到一个用户shell下,默认情况这个shell并不是”login shell”不会去执行/etc/profile和home目录下相关配置:

$ sudo su hongjiang

$ ps -ef | grep $$
hongjia+  2796  2795  0 14:57 pts/0    00:00:00 bash

要以”login shell”方式启动,需要对su指定一个参数“-“

$ sudo su - hongjiang

$ ps -ef | grep $$
hongjia+  3188  3187  0 15:35 pts/0    00:00:00 -bash

从bash文档里可以看到,要以”login shell”方式启动一个shell,要么第一个参数给一个特定的连字符“-”,要么显式的对bash设定”–login”参数。

命令输入完,发现需要先执行另一条命令

比如, cd /tmp/dd,刚输入完,发现需要先创建这个目录。以往的做法是 Ctrl+a 移到这一行开头,
然后输入 mkdir /tmp/dd && cd /tmp/dd

还有个简单的方式是,Ctrl+u 删除此行命令,然后执行其他命令。之后,再 Ctrl+y 粘贴之前的命令

这里Ctrl+u 相当于 Ctrl+a , Ctrl+k

在zsh下,还可以省一步,用 alt+q 可以替代上面的两步 ctrl+u , ctrl+y

注: Esc-qAlt-q 清除当前命令,执行另一个命令结束后,再插入此命令。

搜索历史命令

很久之前,我是这样对历史搜索增强的,参考我的.zshrc文件

bash 中绑定 up 和 down 在匹配的条件中选择

bind '"\e[A": history-search-backward'
bind '"\e[B": history-search-forward'

zsh中的绑定:

bindkey "^[[A" history-search-backward
bindkey "^[[B" history-search-forward

这样 ctrl-r 后搜索 mvn 然后可以用 up/down 选择所有mvn的历史命令。
不过这样如果你刚好使用过“history | grep mvn” 这样的命令时,再通过ctrl-r搜索mvn会把这条也匹配到,并且再继续用方向键时,列出的都是“history | grep”开头的命令;这点有些不爽。

还好有一种稍微好点的方式:敲入一个命令时,通过快捷键列出所有这个命令开头的,并给出序号来选择。

把下面几行放到 .zshrc文件里:

autoload -Uz history-beginning-search-menu
zle -N history-beginning-search-menu
bindkey '^X^X' history-beginning-search-menu

然后在终端,当我敲入 mvn 后,按ctrl-x两次会列出所有mvn开头的命令,然后可以通过输入序号来执行那一次的命令了。

Enter digits:
01 mvn                                                     13 mvn dependency:sources
02 mvn assembly                                            14 mvn dependency:soureces
03 mvn assembly:assembly                                   15 mvn dependency:tree | tee /data/tmp/hsf-dep-tree
04 mvn assembly:assembly -Dmaven.test.skip                 16 mvn eclipse:eclipse
05 mvn clean                                               17 mvn hi /data
06 mvn clean compile                                       18 mvn install
07 mvn clean install -Dmaven.test.skip                     19 mvn package
08 mvn clean package assembly:assembly -Dmaven.test.skip   20 mvn package assembly:assembly
09 mvn compile                                             21 mvn package assembly:assembly -Dmaven.test.skip
10 mvn compile:compile                                     22 mvn3
11 mvn copile                                              23 mvn3 compile
12 mvn dependency:sources                                  24 mvn3 eclipse:eclipse

不过这招仍有些问题,当历史命令里带有管道时,输入序号不起作用了,比如:

hongjiang@whj-mbp ~ % ps #两次ctrl-x
Enter digits:
01 ps -aux | head -2                           12 ps -ef | grep mplayer 
02 ps -aux | head 2                            13 ps -ef | grep fmd 
03 ps -ef                                      14 ps -ef | grep java                            

这时候输入12并不能自动补齐这条历史命令。先配合着与ctr-r一同来用吧。

zsh配置

当前的zsh配置文件(mac系统下),我没有用 oh-my-zsh 觉得里面很多自己用不上,还是根据自己的习惯配置的。
推荐一下 zsh-syntax-highlighting 这个插件,当命令输入不正确时颜色为红色,拼写正确为绿色。

autoload -Uz promptinit
promptinit
prompt adam1

setopt histignorealldups sharehistory
# Use emacs keybindings even if our EDITOR is set to vi
bindkey -e

source ~/.zsh_plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh
source ~/.zsh_plugins/zsh-history-substring-search.zsh
source ~/.zprompt

bindkey '^r' history-incremental-pattern-search-backward
#bindkey '^f' history-incremental-pattern-search-forward

bindkey "^[[A" history-search-backward
bindkey "^[[B" history-search-forward
bindkey '^p' history-search-backward
bindkey '^n' history-search-forward

# Keep 1000 lines of history within the shell and save it to ~/.zsh_history:
HISTSIZE=2000
SAVEHIST=2000
HISTFILE=~/.zsh_history

# Use modern completion system
autoload -Uz compinit
compinit

zstyle ':completion:*' auto-description 'specify: %d'
zstyle ':completion:*' completer _expand _complete _correct _approximate
zstyle ':completion:*' format 'Completing %d'
zstyle ':completion:*' group-name ''
zstyle ':completion:*' menu select=2
#eval "$(dircolors -b)"
zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' list-colors ''
zstyle ':completion:*' list-prompt %SAt %p: Hit TAB for more, or the character to insert%s
zstyle ':completion:*' matcher-list '' 'm:{a-z}={A-Z}' 'm:{a-zA-Z}={A-Za-z}' 'r:|[._-]=* r:|=* l:|=*'
zstyle ':completion:*' menu select=long
zstyle ':completion:*' select-prompt %SScrolling active: current selection at %p%s
zstyle ':completion:*' use-compctl false
zstyle ':completion:*' verbose true

zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#)*=0=01;31'
zstyle ':completion:*:kill:*' command 'ps -u $USER -o pid,%cpu,tty,cputime,cmd'

zstyle ':completion:*:manuals'    separate-sections true
zstyle ':completion:*:manuals.*'  insert-sections   true
zstyle ':completion:*:man:*'      menu yes select

#fpath=(~/.zsh/completion $fpath)

# alias
alias -g ...='../..'
alias -g ....='../../..'
alias '?'='history | grep -i '
alias 'p?'='ps aux | grep -i '

alias which='which -a'
alias sed='/usr/local/bin/gsed'
alias grep='grep --color=auto'
alias fgrep='fgrep --color=auto'
alias egrep='egrep --color=auto'
alias screen='screen -s zsh'
#alias ss='xfce4-screenshooter -r'
alias xclip='xclip -selection clipboard'
alias history='history -1000'
#alias man='TERMINFO=~/.terminfo/ LESS=C TERM=mostlike PAGER=less man'
alias man='TERMINFO=~/.terminfo/ LESS=C PAGER=less man'
alias ls='ls -G'
alias ll='ls -l'
alias la='ls -A'
alias l='ls -CF'
alias less='/usr/share/vim/vimcurrent/macros/less.sh'
alias find='find . '
alias emacs='emacs -q'
alias scala='EMACS="" && scala -deprecation '
alias polipo-proxy='polipo socksParentProxy=localhost:1984 proxyPort=4891'
alias ecl='open -n /data/tools/eclipse/Eclipse.app'
#alias mvn='mvn3 -s /data/tools/maven/settings-ali.xml'
alias mvn='mvn2' 
alias clhsdb='java -classpath .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB'
alias hsdb='java -classpath .:$JAVA_HOME/lib/sa-jdi.jar sun.jvm.hotspot.HSDB'

export EDITOR=vim
#export LANG=en_US.UTF-8

hash -d alibaba="/data/work/alibaba"
hash -d tb="/data/work/taobao"
hash -d bbl="/data/work/alibaba/bbl"
hash -d opensources="/data/work/opensources"
hash -d downloads="/data/downloads"
hash -d music="/data/music"
hash -d jdkopt="/System/Library/Frameworks/JavaVM.framework/Versions"

#screen integration to set caption bar dynamically
function title {
if [[ $TERM == "screen" || $TERM == "screen.linux" ]]; then
    # Use these two for GNU Screen:
    print -nR $'\033k'$1$'\033'\\\

    print -nR $'\033]0;'$2$'\a'
elif [[ $TERM == "xterm" || $TERM == "urxvt" ]]; then
    # Use this one instead for XTerms:
    print -nR $'\033]0;'$*$'\a'
    #trap 'echo -ne "\e]0;$USER@$HOSTNAME: $BASH_COMMAND\007"' DEBUG
fi
}

function calc() { echo "$*" | bc -l; }
function upto() { cd "${PWD/\/$@\/*//$@}" }
function lower() { echo ${@,,};}
function upper() { echo ${@^^};}

#path
PATH=`echo $PATH | sed -e 's#/usr/local/bin:##' -e 's#^#/usr/local/bin:#'`
PATH="/usr/local/sbin:$PATH"
if [ -d "$HOME/bin" ]; then
  PATH="$HOME/bin:$PATH"
fi
export SCALA_HOME="/data/tools/scala"
export JAVA_HOME="/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home"
export PATH=$JAVA_HOME/bin:$SCALA_HOME/bin:$PATH

#for homebrew
export CPPFLAGS=-I/opt/X11/include
export CFLAGS=-I/opt/X11/include

#autojump
if [ -f `brew --prefix`/etc/autojump.zsh ]; then
  . `brew --prefix`/etc/autojump.zsh
fi
export AUTOJUMP_IGNORE_CASE=1
export AUTOJUMP_KEEP_SYMLINKS=1

#fasd
eval "$(fasd --init posix-alias zsh-hook zsh-ccomp zsh-ccomp-install zsh-wcomp zsh-wcomp-install)"
alias c='fasd_cd -di'

#for iterm
function setTitle() { printf "\033]0;%s@%s:%s\007" "${USER}" "${HOSTNAME%%.*}" "${PWD/#$HOME/~}" }

改进zsh的prompt,进程exit code不为0时提示

如果执行的一个命令,exit code不为0,则在prompt中给出提示,用醒目的颜色。这个还是蛮有用的。
因为有时执行的脚本,意外中断,却不知。用echo $?去手动检查又麻烦。

从这里学到的:http://www.miek.nl/blog/archives/2008/02/20/my_zsh_prompt_setup/index.html

效果如下: