fish 的一些问题

2026-05-13

fish 号称是友好的交互式 Shell。它以开箱即用著称,上手的第一时间,原生的补全、高亮、提示令人惊喜,立即体验到 zsh 安装插件才具备的功能,尽管它不兼容 POSIX,但语法简单,性能不如 zsh 的问题也可以忽略,因此我几乎只花了十分钟就决定转向 fish 了。

然而使用几天后发现,它有一些令人难以忍受的问题。

奇葩的管道缓冲

先看这行脚本:

1
bash -c 'for i in 1 2 3; do echo $i; sleep 1; done' | string match -r '\d'

用 bash 模拟一个耗时命令(当然用 fish 也可以),每隔 1 秒输出一个数字,然后用内置的 string 查找。

原生的 Perl 正则,真是棒极了。没有阻塞,实时输出,一切都很美好。

那么这样呢?

1
bash -c 'for i in 1 2 3; do echo $i; sleep 1; done' | string match -r '\d' | string match -r '\d'

仅仅多加了一个 string,阻塞发生了:3 秒时没有任何输出,3 秒后才瞬间输出 3 个数字。

原因是,内置(builtin)、函数(function)均不支持并行1。第一个 string 等前面命令的结束才会刷新缓冲,然后一下子输出给第二个 string 使用。所以,管道要实现并行,只能出现最多一个内置或函数。

等等,这个问题看起来也不严重,我用 grep 不就行了吗?

好,假设我们这样二次过滤日志:

1
tail -f /path/to/log | grep ... | grep ...

好了,tail 永不关闭,现在什么输出也没有,因为 fish 默认把 grep 包装成了函数:

1
2
3
4
5
6
$ type grep
grep is a function with definition
# Defined in embedded:functions/grep.fish @ line 7
function grep
    command grep --color=auto $argv
end

不巧的是,我甚至把 tail 也包装成函数,让它在包含 -f/-F 选项并且输出端为 tty 时使用 bat 美化输出,于是,我连一次过滤也不能用了,除非使用 command 强制调用外部命令。

这还没完,fish 中的别名(alias),例如

1
2
3
4
5
6
7
$ alias cat='bat -p'
$ type cat
cat is a function with definition
# Defined via `source`
function cat --wraps='bat -p' --description 'alias cat=bat -p'
    bat -p $argv
end

本质上也是函数,因此也会阻塞。如果你想删除 alias,fish 并不支持类似 unalias 的功能,你需要使用 functions -e 删除函数。这也是 fish 总是推荐用 abbr 的原因:alias 有时并不好用。

这个问题从 2014 到 2026 都没有解决。友好的交互式 Shell,并没有看上去那么美好。

缺失的后台

这与管道缓冲的问题是类似的:

1
2
function f; sleep 1; echo done; end
f &

f & 并不能让函数在后台运行2,fish 也不支持 subshell。内置命令、代码块也是如此。从 2012 到 2026 都没有解决。

而在 bash/zsh 甚至 dash 中

1
2
f() { sleep 1; echo done; }
f &

都没有问题,没记错的话这应该是 POSIX 标准。

设想对 curl 做包装,添加了环境变量或者 --retry 参数之类的,然后使用 curl -o ... & 试图丢到后台下载大文件,它却强制停在前台。

这比管道缓冲的问题还奇葩,对管道只要 fish 本身的进程(如函数)不超过一个,或者强制使用外部命令即可,但要想后台运行函数,那就只能在 fish 中使用 fish -c '...' 这种奇葩操作了。

官方文档(截至目前是 4.7.1)的相关解释:

Fish does not currently have subshells.3

不支持 subshell。

Builtin: A command that is implemented by the shell. Builtins are so closely tied to the operation of the shell that it is impossible to implement them as external commands. In echo foo, the “echo” is a builtin.

Command: A program that the shell can run, or more specifically an external program that the shell runs in another process. External commands are provided on your system, as executable files. In echo foo the “echo” is a builtin command, in command echo foo the “echo” is an external command, provided by a file like /bin/echo.

Function: A block of commands that can be called as if they were a single command. By using functions, it is possible to string together multiple simple commands into one more advanced command.

Job: A running pipeline or command.

Pipeline: A set of commands strung together so that the output of one command is the input of the next command. echo foo | grep foo is a pipeline.4

不知道把这一坨术语丢给爱因斯坦,他能不能理解 builtin 到底是不是 command,包含 builtin/function 的一连串“commands”到底算不算 pipeline。


题外话,在稍微冷门或专业一点的领域,AI 的表现都是一本正经地胡说八道。

ChatGPT 总体比 DeepSeek 准确率高。但它很固执,即使我将手动测试结果、官方文档和 GitHub issue 链接丢给它,它也在找补。

而 DeepSeek 则总是讨好用户,说什么都是一通夸,据说其他国产 AI 也是如此。

没想到居然从 AI 上看到了民族性。

Shellfish

本作品根据 署名-非商业性使用-相同方式共享 4.0 国际许可 进行授权。

为邮件客户端添加 OAuth 2.0 代理