Rust 版 coreutils 要进你的服务器了,该查的是这些
Ubuntu 26.10 完成了用 Rust 版 uutils 替换 GNU coreutils 的迁移,包括 cp、mv 和 rm。它按可直接替换来设计。而「可直接替换」是对常见路径的承诺,你的脚本活在不常见的那条上。

Rust 版 coreutils 要进你的服务器了 🦀
Ubuntu 26.10 完成了两个版本前启动的迁移:GNU coreutils 被 uutils —— 一套 Rust 重写实现 —— 取代。最后三个钉子户 cp、mv、rm 在 26.04 LTS 里还留在 GNU 版本上,因为 uutils 那边有一批 TOCTOU 问题要先修。上游修完了,26.10 收尾。
如果你自己跑一台 Linux 服务器,这种变更很容易一挥而过。官方说法是对最终用户没有功能差异:uutils 以与 GNU 可直接替换为目标,任何偏差都按 bug 处理。这是个真实且值得肯定的承诺。
它同时也是一个关于常见路径的承诺。
「可直接替换」承诺了什么,没承诺什么
可直接替换意味着:你用的参数行为符合预期,输出长得一样,退出码对得上。对 ls -la、cat、chmod、du 来说,这几乎就是全部,你确实一辈子也不会察觉。
它没有承诺的是:某个脚本依赖的每一种行为都是被规定过的行为。Shell 脚本会不知不觉攒下一堆没人写进文档的依赖:
- stderr 的确切措辞。 任何做
2>&1 | grep 'No such file'的地方,匹配的都是一个从来不是接口的字符串。 - 碰巧成立的顺序。 管道里不带
--sort的ls,或者被当成稳定的通配展开顺序。 - 没人测过的参数组合。 在既不支持 reflink 也不支持稀疏文件的文件系统上跑
cp --reflink=auto --sparse=always。 - 某个并不存在的参数的行为。 脚本里用了 GNU 特有扩展,然后跑在实现不同的系统上。
这些都不是 uutils 的失败。它们是替换一个做了三十年参考实现的东西所要付的常规代价,而代价落在写脚本的人头上。
最要紧的那三个,正是被押后的那三个
cp、mv、rm 最后才切换,不是巧合。它们是会破坏性地动你数据的那几个,而它们被押后恰恰是因为 time-of-check-to-time-of-use 问题 —— 那类"路径校验完、动手之前中间变了"的 bug。
这既是当初值得等的理由,也是现在值得留意的理由。这三个同时还是你的备份脚本、部署脚本和日志轮转最依赖的。
十五分钟能测完的东西
别去审计所有脚本。把要紧的那几个拿新二进制跑一遍,比对结果。
# 我现在用的是哪个实现?
cp --version | head -1 # "uutils coreutils" 还是 "cp (GNU coreutils)"
# 脚本里那条确切的调用,行为还一样吗?
# 在临时目录上跑,不要在生产上。
mkdir -p /tmp/ct/{src,dst}
printf 'x' > /tmp/ct/src/file
ln -s file /tmp/ct/src/link
cp -a /tmp/ct/src/. /tmp/ct/dst/ && ls -la /tmp/ct/dst在一台你真的在运维的服务器上,有三件事特别值得查:
- 备份或同步那条路径。 任何用到
cp -a、--preserve、硬链接或稀疏文件的地方。确认权限、属主、符号链接和时间戳都活下来了 —— 而不只是"文件到了"。 - 任何解析输出的地方。 在脚本里 grep
cp、mv、rm、ls后面跟管道的位置。每一处都是你在依赖输出格式。优先用退出码和find -print0,别去解析。 - 你的错误分支。 故意让操作失败 —— 目标只读、源不存在、磁盘满 —— 确认脚本仍然能发现。退出码契约是最可能正确的部分,也是你的脚本最可能忽略、转而去匹配字符串的部分。
这件事奖励的升级纪律
Ubuntu 26.10 是过渡版本:支持九个月,不是五年。如果你拿它跑生产,你已经接受了更快的节奏。如果你跑 LTS,那你有到下一个 LTS 之前的时间去找出自己的坑 —— 而过渡版本正是找坑的地方,在一台不服务任何人的机器上。
这才是真正的建议。不是"审计你的脚本"(没人会做),而是"在 LTS 把它变成你的问题之前,在一台随手可扔的 26.10 虚拟机上,把部署和备份脚本各跑一遍"。
这次迁移的内存安全理由是站得住的,兼容性工作也明显做得认真。这两点都成立,同时你那个用了十五年的备份脚本仍然可能是例外。一台临时虚拟机、十五分钟,就能告诉你是哪种。
来源: OMG! Ubuntu · It's FOSS