立即注册找回密码

QQ登录

只需一步,快速开始

微信登录

微信扫一扫,快速登录

手机动态码快速登录

手机号快速注册登录

搜索

图文播报

查看: 2115|回复: 5

[分享] fish比zsh好用吗?

[复制链接]
发表于 2024-9-16 15:27 | 显示全部楼层 |阅读模式
回复

使用道具 举报

发表于 2024-9-16 15:28 | 显示全部楼层
好用。但是这种好用没啥用。
在生产力方面Linux shell对比GUI,不管整的再花哨其实比下限真没啥优势,重点是脚本化上限非常高。fish shell不是POSIX兼容,就只能那样了。
只能本机调调进程。生产力优势根本体现不出来。你要是玩的东西没啥规模可言,那肯定没问题,zsh fish就看你自己喜欢了。
一旦有开始考虑兼容性,须知Linux老管理手上的脚本多数连bash都不是,多是sh。
所以呢,fish是好用,但那种好用,用处并不大。好歹人家zsh还可以emulate sh。语法大部分也是兼容的。
<hr/>而我个人,还是用的bash(zsh fish都尝试过)。累手一点的东西都脚本或函数化了。那些花里胡哨的完全用不上。
回复 支持 反对

使用道具 举报

发表于 2024-9-16 15:29 | 显示全部楼层
用 zsh 有七八年了,开始用的是 oh my zsh,后来从 skywind3k 大佬的配置文件里抄来了 antigen,用起来还算不错。不过,也有一个问题困扰了好久,antigen 每加载一个插件,就会往 PATH 中塞一个路径,导致路径一大坨,需要改动的时候很难分辨。如下所示:
# 可以左右滑动看看有多长
-> % 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)(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/network/volume
不过即便如此,切换到 fish 还是值得的,一则简化并看懂了自己的配置,二则对于 shell 启动时间也是有优化的:
┬─[yifei@bogon:~]─[19:05:15]
╰─>$ 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

┬─[yifei@bogon:~]─[19:07:44]
╰─>$ 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
参考

回复 支持 反对

使用道具 举报

发表于 2024-9-16 15:29 | 显示全部楼层
对于一个没有学过并且不需要学习 bash(或者 Posix shell),而且不喜欢折腾的用户,我感觉 fish 是一个更好的选择。如果他喜欢 fish 的默认配置就更好了,基本可以达到开箱即用的程度。对 bash 语法了解比较少的用户,也可以安装一个尝试一下,如果能接受的话也是不错的。
但对于已经习惯 bash 语法的用户,fish 并不是一个很好的选择。虽然交互使用 fish,脚本使用 bash/zsh,可以解决一部分问题。但使用 fish 带来的问题还是要比它能解决的问题要多。zsh 最主要的问题是配置麻烦,如果使用 oh-my-zsh 的话,最主要的问题从配置麻烦变成了速度慢(自己写 .zshrc 的话,zsh 是绝对不会比 fish 慢的)。我自己用的是一个 200 多行的 .zshrc 脚本(这个脚本只包含 zsh 特有的东西,还有一个 .myshrc 是 bash 和 zsh 通用的东西,接近 500 行),这个脚本是我最初使用 zsh(估计 6 - 7 年前了吧)时在网上收集并且修改的,然后基本就很少再动了。我试过使用 oh-my-zsh 默认配置的启动时间,要比我 .zshrc + .myshrc 两倍还要多,而且功能还是不全的。
已经习惯 bash 语法的用户,我还是建议花一点时间,自己写 .zshrc(其实网上有很多例子,改一下就行了),而不是用 oh-my-zsh 或者其他类似的工具。或者说,如果你想用 oh-my-zsh,那么 fish 可能更适合你。网上可以看到不少人从 zsh 转到 fish,但基本上无一例外是 oh-my-zsh 的用户,而对于自己定制 .zshrc 的用户,fish 基本没有任何吸引力。
fish 相对 zsh 的优势主要就是非常友善的默认配置(但也仅仅是默认配置,如果不满意的话,想定制还是和 zsh 一样得到处搜)。而 zsh 相对 fish 的优势,主要是兼容 bash(其实并不是 100% 的兼容,但一般情况也不会遇到什么大问题)。在命令补全等方面,fish 和 zsh 相比并没有优势,配置好了后的易用性也是(有人说 fish 可以根据 man 补全,首先我不认为这是一个好主意。我不清楚它的具体机制,但基本上有两种方式,一种是每次安装带 man 文档的包后,执行一个后置脚本,这样会拖慢安装速度。另一种是 fish 自己携带 man 文档的补全信息,这样有更新不同步的问题。其次我还没遇到有什么命令需要借助 man 文档补全的,或者说,在用 zsh 的几年里,我基本上从没有过一次认为某个命令应该支持补全而 zsh 没有补全的情况。如果真的遇到了的话,我想我会自己写一个补全脚本然后提交到 zsh-completions)。
因为我并没有将 fish 作为自己默认的 shell 使用,对它的功能和灵活性不是特别了解。也不清楚有没有 zsh 强大和灵活,在这一点就不比较了。但兼容 bash 真的那么重要吗?其实这不只是语法本身的问题,bash 的语法确实有很多问题,最让我头疼的就是和处理带空格的字符串相关的各种问题,而且功能的缺失还是比较多的,从语法上说,我感觉 powershell 的语法更优,而且优势很大,在这一点上 fish 也逊色很多。但至少在目前,很多情况必须使用 bash 语法,因为这早已形成了一个生态环境。我可以假设每个 Linux 机器上都有 bash,可以随意在软件的发布包里使用 bash 脚本,别人也很容易看懂或者修改。但我不能期望别人专门装一个 fish 来执行我的 fish 脚本,当 bash 满足不了需求的时候,我宁愿使用 python,因为在多数机器上,python 也是安装了的。其他开发者也是这么想的,所以我们可以经常看到某个软件包里有 bash 脚本,但很少看到有 fish 脚本(连 zsh 脚本都很少,因为如果不需要使用 zsh 专有的特性,就会使用 bash 脚本,虽然多数情况用 zsh 也能运行)。如果不会 bash 的话,那么出了问题就很难定位和解决。如果我必须要学 bash,那么同时再学一个相差不多的 fish(即使它某些地方更好一些),就没什么必要了。
所以对于真正的重度终端用户,使用 fish 可能会节省一些最初的固定的配置时间,代价是要额外学一门 fish 语言,以及使用的时候面临两种相似的脚本语言的混淆问题,是得不偿失的。
fish 可以取代 zsh 吗?这么多年过去了,zsh 还没有取代 bash,那么 fish 可以取代 zsh 吗?可以这样说,fish 取代 zsh,要比 zsh 取代 bash 还有难很多倍。事实上,fish 目前是非常小众的,甚至和 bash 相比,zsh 都是相对小众的。网上是可以看到很多人用 zsh 或者 fish,但是一个人用 bash 会在网上专门发文章吗?很多人可能都不清楚自己在用的是 bash,还有很多人认为 shell 等同于 bash。
如果从发展上看,我是更看好 zsh 的,至少 zsh 还是有潜力取代 bash 的作为系统唯一的 shell 的(虽然可以还需要一些年),而 fish 基本上只能是一小部分用户使用的交互式 shell,也许不久之后,又会出现一个比 fish 更友好的 shell,而 fish 的名字也就逐渐被人们遗忘。
回复 支持 反对

使用道具 举报

发表于 2024-9-16 15:30 | 显示全部楼层
先占回自己的坑。
自己提的问题,自己在另一个问题当中回答了
https://www.zhihu.com/question/21418449/answer/95753913
回复 支持 反对

使用道具 举报

发表于 2024-9-16 15:30 | 显示全部楼层
fish的脚本功能是个半成品,虽然目的是比bash更规整,但因为不完整,某些功能暂时无法实现或比较繁琐。
回复 支持 反对

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册 微信登录 手机动态码快速登录

本版积分规则

关闭

官方推荐 上一条 /3 下一条

快速回复 返回列表 客服中心 搜索 官方QQ群 洽谈合作
快速回复返回顶部 返回列表