Posted on:
Last modified:
用 zsh 有七八年了,开始用的是 oh my zsh,后来从 skywind3k 大佬的配置文件里抄来了 antigen,
用起来还算不错。不过,也有一个问题困扰了好久,antigen 每加载一个插件,就会往 PATH
中塞
一个路径,导致路径一大坨,需要改动的时候很难分辨。如下所示:
ubuntu@VM-0-11-ubuntu [09:37:09 PM] [~]
-> % echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/lib:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/tmux:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/ansible:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/git:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/pip:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/colored-man-pages:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/django:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/docker:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/fzf:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/history:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/kubectl:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/colorize:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/github:/home/ubuntu/.antigen/bundles/robbyrussell/oh-my-zsh/plugins/python:/home/ubuntu/.antigen/bundles/zsh-users/zsh-completions:/home/ubuntu/.antigen/bundles/zsh-users/zsh-autosuggestions:/home/ubuntu/.antigen/bundles/Vifon/deer:/home/ubuntu/.antigen/bundles/nojhan/liquidprompt:/home/ubuntu/.antigen/bundles/willghatch/zsh-cdr:/home/ubuntu/.antigen/bundles/zsh-users/zaw:/home/ubuntu/.antigen/bundles/zsh-users/zaw/functions:/home/ubuntu/.antigen/bundles/wfxr/forgit:/home/ubuntu/.antigen/bundles/zsh-users/zsh-syntax-highlighting:/home/ubuntu/.fzf/bin:/home/ubuntu/.local/bin:/home/ubuntu/.dotfiles/bin:/home/ubuntu/repos/lib/go:/home/ubuntu/repos/unity/go/bin
不知道是不是因为换了 M1 芯片的 Mac 有什么不兼容的地方(并不是),最近 zsh 的加载速度
从 1s 涨到了 4s,以前的速度还勉强可以忍,现在想敲个命令都得等 shell 加载半天,实在受不了
了。一开始是打算精简下自己的 zshrc 文件,后来看了看发现完全看不懂这个文件,瞎改一通高亮
补全都不见了,要啥啥没有,于是索性换个 shell 了。
第一次用 fish 还是刚去头条实习的时候。记得当时看到 fish 眼前一亮,但是因为 bash 用得都不
熟练,而 fish 又和 bash 不兼容就暂时放弃了。当时的 fish 为了特立独行,有些 bash 中受欢迎
的地方也删掉了,比如说 &&
和 ||
条件执行两个命令,直到最近才补上。没想到和 fish 一别
就是六年,今天再次 brew install fish
还真有些一见如故的感觉。
安装好之后还要 sudo echo $(which fish) >> /etc/shells
(zsh),然后就可以 chsh $(which fish)
(in zsh),把 fish 扶到正宫位置了~
虽然 chsh
把 bash/zsh 打入冷宫了,但并不是彻底把它们卸载了。当你需要执行一些 bash 脚本
的时候,直接 bash my_script.sh
就好了。
这是原来的 zsh,自动补全和高亮一应俱全,就是启动慢了点。
这是新安装的 fish,自动补全和高亮开箱即用,没有装任何插件,只换了一个原生主题,启动飞快。
更妙的两点是,fish 的自动补全是基于上下文的,在不同的位置会有不同的补全;而且 fish 还会 自动解析 man page,智能补全命令。
前面提到 fish 和 bash 是不兼容的,更准确得说,fish 不是一个 posix compatible 的 shell, 虽然有些和以前的习惯用法不一样的地方,但是这也意味着 fish 可以摆脱一些历史上的设计错误, 从而拨乱反正。
比如说,fish 中不使用 $(cmd)
或者 `cmd`
来执行命令替换,直接使用 (cmd)
。
fish 中的 for 循环也更像现代编程语言 (Ruby):
for i in *.pdf
echo $i
end
而在 bash/zsh 中需要:
for i in *.pdf; do
echo $i;
done
至少对我而言,for 后面的那个分号是特别容易忘记的。
fish 中不需要 heredoc,因为字符串直接可以跨行。当然也可以在每行结尾处加上 \
转义换行。
echo "some string
some more string"
相当于 bash 中的:
cat <<EOF
some string
some more string
EOF
如果你装的新版本的 fish,那么是支持浮点数的,以前在命令行做个简单运算还得打开 python, 现在直接:
math 2/5 # 0.4
就可以了。不过老版本的 fish 貌似并不支持浮点数,至少 fish 2.7 是这样的。
bash 中最混乱的部分要数字符串了,比如说
${foo%bar}
(从后向前删除)${foo#bar}
(从前向后删除)${foo/bar/baz}
(正则替换)这个 %
和 #
我从来都不知道是干吗的。在 fish 中全部都替换成了内置命令 string 的方法,
和其他语言比较接近,不再是加密代码了。
# 替换字符串 ${var/pattern/replacement}
bash -c 'export name=Apple; echo ${name/pp/qq}'
name=Apple string replace pp qq $name
# ${foo#bar}
bash -c 'export name=Apple; echo ${name#App}'
name=Apple string replace App '' $name
# ${foo%bar}
bash -c 'export name=Apple; echo ${name%le}'
name=Apple string replace --regex 'le$' '' $name
还有,bash 中的特殊变量也挺难记的,fish 中也都改成了单词:
$*, $@, $1 ...: $argv # 函数或者脚本的参数
$0: status filename # 函数或者脚本的名字
$#: 使用 $argv 的长度
$?: $status # 上一个命令的返回值
$$: $fish_pid # shell 的 pid
$!: $last_pid # 上一个命令的 pid
$-: 大多数使用是 status is-interactive 和 status is-login
fish 中还有一个比较花哨的地方,可以打开浏览器选择主题和各种配置。直接执行 fish_config
就打开了。
另一个需要配置的一些地方就是我自己常用的两个函数了:
proxy
, 打开关闭命令行代理auto_venv
,自动激活和关闭 Python 虚拟环境以 proxy 来看一下,fish 的函数还是很直观的。
function proxy
if test "$argv[1]" = "on"
if test "$argv[2]" = ""
echo "No port provided"
return 2
end
# proxy offered by local shadowsocks
export http_proxy="http://127.0.0.1:$argv[2]"
export https_proxy="http://127.0.0.1:$argv[2]"
else if test "$argv[1]" = "off"
set -e http_proxy # set --erase
set -e https_proxy
else if test "$argv[1]" != ""
echo "Usage:
proxy - view current proxy
proxy on PORT - turn on proxy at localhost:PORT
proxy off - turn off proxy"
return 1
end
echo "Current: http_proxy=$http_proxy https_proxy=$https_proxy"
end
最后,还可以把 zsh 的 history 迁移到 fish 中来。令人没想到的是,这都有现成的脚本:
pip install zsh-history-to-fish
zsh-history-to-fish -n # -n 不要转化 && 和 ||
fish 需要一段时间才会重新读取 history 文件。至此,迁移完毕啦。
在切换到 fish 之后我还是对 zsh 为什么这么慢念念不忘,在对 .zshrc
做了一番 profile 和二分
查找之后终于找到了罪魁祸首,和新电脑的硬件并没有什么关系,而是这样一行:
[ -d /home/linuxbrew/.linuxbrew/bin ] && path+=(/home/linuxbrew/.linuxbrew/bin)
这行人畜无害的命令意思是:如果机器上有 linuxbrew 就把它添加到路径里。而在我的 M1 MacBook,
不知道为啥 /home
被挂载到了一个网络目录,所以每次打开一个 shell 的时候都会执行一个网络
操作,而且这个服务器还可能在美国,能不慢么……删除了这行后,zsh 启动又恢复到正常水平。
┬─[yifei@bogon:~]─[21:45:06]
╰─>$ ll /home
lrwxr-xr-x 1 root wheel 25B Dec 4 14:46 /home -> /some/netword/drive
不过即便如此,切换到 fish 还是值得的,一则简化并看懂了自己的配置,二则对于 shell 启动时间 也是有优化的:
╰─>$ time zsh -i -c exit
________________________________________________________
Executed in 254.61 millis fish external
usr time 148.16 millis 0.07 millis 148.10 millis
sys time 98.99 millis 1.59 millis 97.40 millis
╰─>$ time fish -i -c exit
________________________________________________________
Executed in 44.84 millis fish external
usr time 16.57 millis 0.08 millis 16.49 millis
sys time 21.33 millis 1.67 millis 19.66 millis
可以看到在我的配置下,fish 比 zsh 还是快多了。
最后贴上自己的配置文件供参考:
###### .dotfiles/fishrc ######
# vi:ft=fish
set DISABLE_FZF_AUTO_COMPLETION true
export TERM="xterm-256color"
export EDITOR="vi"
# PATH settings
set PATH $HOME/.local/bin $HOME/.cargo/bin $HOME/.dotfiles/bin $PATH
# Load HomeBrew
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
test -f /opt/homebrew/bin/brew && eval (/opt/homebrew/bin/brew shellenv)
test -f /usr/local/bin/brew && eval (/usr/local/bin/brew shellenv)
if uname | grep Linux
set PATH /home/linuxbrew/.linuxbrew/bin $PATH
end
# Aliases
alias dc=docker-compose
alias pc=podman-compose
alias t='tmux -2'
alias tmux='tmux -2'
alias cd..='cd ..'
alias py=python
alias ipy='python -m IPython'
alias g='git'
alias ll='ls -alh'
alias :q='exit'
alias :wq='exit'
alias mkdirp='mkdir -p'
alias shn='sudo shutdown -h now'
alias mirror='wget -E -H -k -K -p'
alias sudo='sudo ' # magic trick to bring aliases to sudo
alias px="proxychains4"
alias lcurl='curl --noproxy localhost'
alias save-last-command='history | tail -n 2 | head -n 1 >> ~/.dotfiles/useful_commands'
alias topcpu='ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head'
alias topmem='ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head'
# Venv auto actiavation
function __auto_source_venv --on-variable PWD --description "Activate/Deactivate virtualenv on directory change"
status --is-command-substitution; and return
# Check if we are inside a git directory
if git rev-parse --show-toplevel &>/dev/null
set gitdir (realpath (git rev-parse --show-toplevel))
else
set gitdir ""
end
# If venv is not activated or a different venv is activated and venv exist.
if test "$VIRTUAL_ENV" != "$gitdir/.venv" -a -e "$gitdir/.venv/bin/activate.fish"
source $gitdir/.venv/bin/activate.fish
# If venv activated but the current (git) dir has no venv.
else if not test -z "$VIRTUAL_ENV" -o -e "$gitdir/.venv"
deactivate
end
end
# Proxy switcher
function proxy
if test "$argv[1]" = "on"
if test "$argv[2]" = ""
echo "No port provided"
return 2
end
# proxy offered by local shadowsocks
export http_proxy="http://127.0.0.1:$argv[2]"
export https_proxy="http://127.0.0.1:$argv[2]"
else if test "$argv[1]" = "off"
set -e http_proxy
set -e https_proxy
else if test "$argv[1]" != ""
echo "Usage:
proxy - view current proxy
proxy on PORT - turn on proxy at localhost:PORT
proxy off - turn off proxy"
return 1
end
echo "Current: http_proxy=$http_proxy https_proxy=$https_proxy"
end
# Load fzf config
test -f ~/.dotfiles/fzf.fish && source ~/.dotfiles/fzf.fish
###### .config/fish/config.fish ######
if status is-interactive
# Commands to run in interactive sessions can go here
end
test -f ~/.dotfiles/fishrc && source ~/.dotfiles/fishrc
###### .dotfiles/fzf.fish ######
# vi:syntax=sh
export FZF_DEFAULT_COMMAND='fd --type f'
export FZF_CTRL_T_COMMAND='fd --type f'
export FZF_ALT_C_COMMAND='fd --type d'
export FZF_COMPLETION_TRIGGER=''
export FZF_DEFAULT_OPTS="--height 40% --reverse --border --prompt '>>> ' \
--bind 'alt-j:preview-down,alt-k:preview-up,alt-v:execute(vi {})+abort,ctrl-y:execute-silent(cat {} | pbcopy)+abort,?:toggle-preview' \
--header 'A-j/k: preview down/up, A-v: open in vim, C-y: copy, ?: toggle preview, C-x: split, C-v: vsplit, C-t: tabopen' \
--preview 'test (du -k {} | cut -f1) -gt 1024 && echo too big || highlight -O ansi -l {} 2> /dev/null || cat {} || tree -C {} 2> /dev/null'"
export FZF_CTRL_T_OPTS=$FZF_DEFAULT_OPTS
export FZF_CTRL_R_OPTS="--preview 'echo {}' --preview-window hidden:wrap --bind '?:toggle-preview'"
export FZF_ALT_C_OPTS="--height 40% --reverse --border --prompt '>>> ' \
--bind 'alt-j:preview-down,alt-k:preview-up,?:toggle-preview' \
--header 'A-j/k: preview down/up, ?: toggle preview' \
--preview 'tree -C {}'"
bind \cr 'commandline --replace -- (history | fzf) || commandline --function repaint'
附:Ubuntu 上安装 fish3
sudo apt-add-repository ppa:fish-shell/release-3
sudo apt update
sudo apt install fish
Fish 会对 PATH 做一些优化,确保没有重复,这反倒导致了 PATH 数组会发生“随机”顺序更换。而
HomeBrew 的目录/opt/homebrew
不在最前面的话,就会回退到系统在 /usr/local
的老命令。
以前用 zsh 的时候似乎不存在这个问题,也不知道是 zsh 实现比较稳妥,还是因为以前 HomeBrew
也装在 /usr/local
。不过好在 fish 提供了 fish_add_path
这个函数,使用它可以确保自定义
路径在最前边。另外还有一个坑,python-dotenv 这个包似乎不兼容 fish,只好用 direnv
了,
似乎还更好用一些。配置文件更改如下:
diff --git a/fishrc b/fishrc
index 1faafb5..a1187c1 100644
--- a/fishrc
+++ b/fishrc
@@ -4,12 +4,19 @@ export TERM="xterm-256color"
export EDITOR="vi"
# PATH settings
-set PATH $HOME/.local/bin $HOME/.cargo/bin $HOME/.dotfiles/bin $PATH
+fish_add_path $HOME/.local/bin $HOME/.cargo/bin $HOME/.dotfiles/bin
# Load HomeBrew
export HOMEBREW_NO_AUTO_UPDATE=1
export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
-test -f /opt/homebrew/bin/brew && eval (/opt/homebrew/bin/brew shellenv)
+if test -f /opt/homebrew/bin/brew
+ set -gx HOMEBREW_PREFIX "/opt/homebrew";
+ set -gx HOMEBREW_CELLAR "/opt/homebrew/Cellar";
+ set -gx HOMEBREW_REPOSITORY "/opt/homebrew";
+ set -gx HOMEBREW_SHELLENV_PREFIX "/opt/homebrew";
+ fish_add_path "/opt/homebrew/bin" "/opt/homebrew/sbin";
+end
+
test -f /usr/local/bin/brew && eval (/usr/local/bin/brew shellenv)
if uname | grep Linux
@@ -84,3 +91,6 @@ end
# Load fzf config
test -f ~/.dotfiles/fzf.fish && source ~/.dotfiles/fzf.fish
+
+# Enable direnv
+type -q direnv && direnv hook fish | source
© 2016-2022 Yifei Kong. Powered by ynotes
All contents are under the CC-BY-NC-SA license, if not otherwise specified.
Opinions expressed here are solely my own and do not express the views or opinions of my employer.
友情链接: MySQL 教程站