如何在Fish Shell中将标准输出重定向到多个命令?
Fish与Bash进程替换差异导致命令挂起的原因及解决方案
问题背景
在Bash环境中,我们可以通过tee配合进程替换>(...),将同一份标准输出分发到多个命令,实现从JSON中提取不同字段并保存到不同文件的需求,示例如下:
echo '{"foo": "bar", "baz": "bam"}' | tee >(jq -r '.foo' > foo.txt) >(jq -r '.baz' > baz.txt)
执行后foo.txt会写入bar,baz.txt会写入bam,同时原JSON内容也会输出到终端,全程正常运行。
但在Fish中执行类似命令时,会出现命令挂起、卡住不动的情况:
echo '{"foo": "bar", "baz": "bam"}' | tee >(jq '.foo' > foo.txt) >(jq '.bar' > bar.txt)
核心差异:进程替换的实现逻辑
Fish与Bash的进程替换机制存在本质区别,这是导致命令挂起的根本原因:
- Bash:
>(...)创建的是匿名管道,tee完成所有管道的数据写入后就直接退出,不需要等待管道另一端的命令执行完毕。即使后续命令仍在处理数据,tee也不会被卡住。 - Fish:
>(...)本质是创建命名管道(FIFO),tee会等待所有FIFO的读取操作完全完成后才会退出。如果某个进程替换中的命令没有彻底读取FIFO中的数据,或者多FIFO场景下同步等待逻辑触发,整个命令就会一直处于挂起状态。
简言之:Bash是异步分发数据,Fish是同步等待所有接收方处理完成才结束流程。
解决办法
针对Fish的特性,有两种实用的处理方式:
1. 给进程替换内的命令添加后台运行符&
让每个进程替换中的命令后台执行,这样tee无需等待它们完成即可正常退出:
echo '{"foo": "bar", "baz": "bam"}' | tee >(jq -r '.foo' > foo.txt &) >(jq -r '.baz' > baz.txt &)
2. 用jq一次性提取所有字段,再拆分输出
避免使用多个进程替换,先用jq按顺序输出需要的字段,再通过head/tail拆分到不同文件:
echo '{"foo": "bar", "baz": "bam"}' | jq -r '.foo, .baz' | tee >(head -n1 > foo.txt) >(tail -n1 > baz.txt)
另外注意:示例中存在字段名错误(.bar应为.baz),虽然这不是挂起的直接原因,但正确的字段名能避免不必要的输出异常。
内容的提问来源于stack exchange,提问作者Kurt Peek
相关产品推荐
相关产品推荐

