你大概写过这样的命令:
sort data.txt | uniq > data.txt
然后发现文件空了。因为 shell 先打开 data.txt 准备写入,把它清空,然后才轮到 sort 去读它——读到的是空文件。
这是 Unix 管道的经典陷阱。三十年来,大家的解法是加临时文件:
sort data.txt | uniq > tmp && mv tmp data.txt
多丑。moreutils 的 sponge 一句话解决:
sort data.txt | uniq | sponge data.txt
TL;DR
- moreutils 是 Joey Hess 收集的 15 个 Unix 工具,补的是三十年来的设计空白
- 核心是
sponge:吸收所有 stdin 再一次性写入文件,解决"输出重定向到正在读取的文件"vipe让你在管道中间插入编辑器,vidir用编辑器编辑整个目录parallel并行跑任务,ts给每行输出加时间戳,chronic只在失败时输出- 实测:
parallel比xargs -P快但不如 GNU parallel;sponge无替代品,必装
moreutils 是什么?
Joey Hess(也是 Debian 开发者)收集的一套命令行工具,官方描述:
“moreutils is a growing collection of the unix tools that nobody thought to write long ago when unix was young.”
翻译过来:Unix 年轻时候没人想到要写,但你现在每天都需要的工具。
15 个工具列表:
| 工具 | 作用 | 一句话场景 |
|---|---|---|
chronic |
命令成功时静默,失败时才输出 | 定时任务日志降噪 |
combine |
两个文件做布尔运算(and/or/xor/not) | 对比两个用户列表 |
errno |
查询 errno 名称和描述 | 看报错码含义 |
ifdata |
获取网卡信息,不用解析 ifconfig | 脚本里取 IP |
ifne |
stdin 非空时才执行命令 | 避免空输入触发操作 |
isutf8 |
检查文件或 stdin 是否 UTF-8 | 处理乱码文件前检查 |
lckdo |
持锁执行程序(已弃用) | — |
mispipe |
管道两个命令,返回第一个的退出码 | 修 pipefail 问题 |
parallel |
并行跑多个任务 | 批量处理 |
pee |
把 stdin 同时 tee 到多个管道 | 多路输出 |
sponge |
吸收 stdin 再写入文件 | 本文主角 |
ts |
给每行 stdin 加时间戳 | 日志计时 |
vidir |
用文本编辑器编辑整个目录 | 批量重命名文件 |
vipe |
在管道中间插入编辑器 | 交互式编辑管道数据 |
zrun |
自动解压参数后传给命令 | 直接处理压缩包 |
sponge:Unix 管道的第一号痛点
为什么 shell 原生做不到?
Shell 的重定向是先打开文件,再执行命令。cmd > file 的执行顺序:
- Shell 打开
file(清空它) - 执行
cmd cmd的输出写入file
当 cmd 需要读取 file 时,它读到的是已被清空的版本。
sponge 的解法
sponge 先把所有 stdin 全部吸收到内存/临时文件,等上游命令结束后,再一次性写入目标文件。
# 经典场景:原地排序
sort data.txt | uniq | sponge data.txt
# 场景:原地去重
cat access.log | sort -u | sponge access.log
# 场景:原地过滤
grep -v '^#' config.conf | sponge config.conf
内存注意:sponge 会把整个输入读进内存。处理 GB 级文件时小心,它的 --sponge-buffer 选项可改用临时文件缓冲。
实测对比
| 方案 | 命令 | 缺点 |
|---|---|---|
| 临时文件 | sort f > t && mv t f |
需要额外空间、两步操作 |
| sponge | sort f | sponge f |
一行,但内存占用等于文件大小 |
| Python | lines = sorted(set(open(f).readlines())) |
需要写脚本 |
结论:日常小文件用 sponge,大文件(>1GB)用临时文件或流式处理。
vipe + vidir:编辑器是终极 Unix 工具
vipe:在管道里插入编辑器
# 场景:交互式编辑命令输出后再传给下一步
ls -la | vipe | grep '\.txt$'
vipe 会打开你的 $EDITOR(vim/nano/code),让你手动编辑管道中间的数据,保存后自动传给下游命令。
实用场景:
# 从一堆文件里挑几个要压缩的
find . -name '*.log' | vipe | xargs tar czf logs.tar.gz
# 手动筛选要删除的进程
ps aux | vipe | awk '{print $2}' | xargs kill
踩坑 1:vipe 需要 $EDITOR 已设置。没设置时它会报错或调用默认编辑器。在脚本里用要显式设置:
export EDITOR=vim
vidir:编辑整个目录
vidir 用你的编辑器打开一个目录清单(文件名列表),你改完保存后,它自动执行重命名/移动/删除操作。
# 打开当前目录的文件列表
vidir
# 批量重命名:把 .jpeg 改成 .jpg
# 在编辑器里把每行的 .jpeg 改成 .jpg,保存退出
vidir --editor=vim
对比传统方案:
| 方案 | 批量重命名体验 |
|---|---|
mv 循环 |
需要写 for 循环,容易出错 |
rename |
功能强大但语法反直觉(Perl 正则) |
vidir |
直接在编辑器里改文件名,所见即所得 |
踩坑 2:vidir 删除某行就是删除该文件,没有确认提示。改之前先备份,或者在编辑器里检查一遍。
parallel vs xargs vs GNU parallel
moreutils 的 parallel 用来并行跑任务:
# 并行转换 100 张图片
ls *.png | parallel -j 8 convert {} {.}.jpg
# 对比 xargs
ls *.png | xargs -P 8 -I {} convert {} {.}.jpg
实测对比
| 方案 | 语法 | 速度 | 功能 |
|---|---|---|---|
moreutils parallel |
parallel -j N cmd |
中 | 基础并行 |
xargs -P |
xargs -P N -I {} |
中 | 无进度条 |
| GNU parallel | parallel cmd |
快 | 进度条/断点续跑/远程执行 |
关键差异:
moreutils parallel和xargs -P性能接近,语法不同- GNU parallel 功能强得多(进度条、
--joblog、--resume),但依赖 Perl,在精简系统(Alpine、部分容器)里可能没装 - moreutils 的
parallel是纯 C,体积小,适合放进精简镜像
选型建议:
| 场景 | 推荐 |
|---|---|
| 精简系统/容器 | moreutils parallel |
| 有 Perl 环境 | GNU parallel |
| 一次性简单任务 | xargs -P |
ts:给日志加时间戳
# 给每行输出加时间戳
ping 8.8.8.8 | ts
# 指定格式
ping 8.8.8.8 | ts '%Y-%m-%d %H:%M:%S'
# 测量命令耗时
long_running_cmd | ts
踩坑 3:ts 是缓冲的,它会等上游有输出才加时间戳。如果你想测命令总耗时,用 time;想测每步间隔,用 ts。
chronic:定时任务日志降噪
# 成功时什么都不输出,失败时才输出
chronic rsync -av /data backup:/data
# 在 cron 里
0 3 * * * chronic /usr/local/bin/backup.sh
每天凌晨跑备份,成功时你不会收到邮件,失败时才报警。这是运维标配。
其他工具速查
errno:查错误码
$ errno 2
ENOENT 2 No such file or directory
$ errno EACCES
EACCES 13 Permission denied
combine:文件布尔运算
# 两个列表取交集
combine list1.txt and list2.txt
# 取并集
combine list1.txt or list2.txt
# 取差集
combine list1.txt not list2.txt
ifne:空输入不执行
# 只有找到文件才执行
find . -name '*.tmp' | ifne xargs rm -f
pee:多路输出
# 同时 tee 到两个管道
cat data.txt | pee 'grep foo' 'grep bar'
zrun:自动解压
# 直接处理压缩包里的文件
zrun cat logs.gz
安装方法
# Debian/Ubuntu
sudo apt install moreutils
# macOS (Homebrew)
brew install moreutils
# Fedora/RHEL
sudo dnf install moreutils
# Arch
sudo pacman -S moreutils
# 从源码
git clone https://github.com/pgdr/moreutils.git
cd moreutils && make && sudo make install
成本账本
| 项目 | 成本 |
|---|---|
| moreutils 本身 | 免费开源(GPL) |
| 安装体积 | ~1-2 MB(15 个静态二进制) |
| 依赖 | 无(纯 C,libc 即可) |
| 维护 | 极低,Joey Hess 长期维护 |
对比:GNU parallel 需要 Perl(~20MB),moreutils 是 15 个独立小工具,适合放进 Docker 镜像或精简系统。
常见问题
Q1: sponge 和 tee 有什么区别?
A: tee 是实时分流(一边写文件一边写 stdout),sponge 是先全部吸收再一次性写入。tee file 不能用于"读取并覆盖同一文件"的场景,sponge 可以。
Q2: moreutils 的 parallel 和 GNU parallel 冲突吗?
A: 会冲突——两者都叫 parallel。如果同时安装,后装的会覆盖 PATH 里的 parallel。建议只装一个,或者用别名区分。
Q3: vidir 删除了文件能恢复吗?
A: 不能,vidir 删行即删文件,无回收站。改前先 cp -r dir dir.bak 备份。
Q4: vipe 在脚本里能用吗?
A: 能,但需要设置 $EDITOR。脚本里建议 export EDITOR=$(command -v vim)。
Q5: 为什么不直接升级到新版本 Unix?
A: 这些工具的空白不是 Unix 版本问题,是设计哲学问题。Unix 哲学是"做好一件事",sort 就该只排序,不该管"原地覆盖"。moreutils 是补这个组合层的工具。
Q6: moreutils 会持续维护吗?
A: Joey Hess 从 2006 年维护至今,Debian 官方仓库收录。社区活跃,bug 修复及时。
Q7: Windows 能用吗?
A: 原生不能,需要 WSL 或 Cygwin。Windows 用户建议用 WSL2 + apt 安装。