CLI AI

sed -i 在 Mac 上用不了?BSD 与 GNU 的差异

2026-05-15

如果你看到 sed: 1: "s/foo/bar/g": invalid command code,其实什么都没坏。macOS 自带的是 BSD 用户态工具,而你的脚本是照着 GNU 写的。命令名一样,参数不一样。下面这六处才是真正会咬人的。

1. macOS 上的 sed -i 必须带参数

clai
$ clai 在 config.txt 里原地把 foo 替换成 bar,要能同时在 mac 和 linux 上跑→ sed -i.bak 's/foo/bar/g' config.txt   # 可移植写法:会生成 config.txt.bak

GNU 把备份后缀当作可选,BSD 则要求必须有。sed -i '' 's/…' 在 macOS 上能跑,在 Linux 上会失败。sed -i.bak 两边都能跑,代价只是多一个备份文件要清理。

2. 扩展正则:用 -E,永远别用 -r

clai
$ clai 用扩展正则把连续的数字替换成 N→ echo "foo123" | sed -E 's/[0-9]+/N/'fooN

-r 是 GNU 独有的。-E 两边都认——GNU 已经支持它很多年了。把 -E 变成默认习惯,一整类 bug 就消失了。

3. date:Linux 用 -d,macOS 用 -r

clai
$ clai 把 unix 时间戳 1735689600 转成 UTC 的可读日期→ date -u -d @1735689600 '+%Y-%m-%d %H:%M:%S UTC'    # GNU2025-01-01 00:00:00 UTC→ date -u -r 1735689600 '+%Y-%m-%d %H:%M:%S UTC'     # BSD / macOS

这里没有可移植写法。要么按 uname 分支,要么装上 GNU coreutils 用 gdate。

4. stat:Linux 用 -c,macOS 用 -f

clai
$ clai 输出这个文件的大小和名字→ stat -c '%s %n' t.txt      # GNU4 t.txt→ stat -f '%z %N' t.txt      # BSD / macOS

不仅参数不同,格式占位符也不同。如果脚本要用 stat 做点复杂的事,Linux 上改用 find -printf,需要可移植时用 wc -c。

5. macOS 上没有 grep -P

clai
$ clai 用可移植的方式从这个字符串里只取出数字→ echo "abc123" | grep -Eo '[0-9]+'123

grep -P(Perl 正则)是 GNU 的编译选项,macOS 不提供。-E 足以覆盖绝大多数真实场景;如果确实需要前瞻断言,你需要的是 perl 或 ripgrep,而不是 grep。

6. xargs -r 只有 GNU 有,而你需要它

clai
$ clai 找出超过 30 天的 .log 并删除,列表为空时不要执行 rm→ find . -name '*.log' -mtime +30 -print0 | xargs -0 -r rm

没有 -r 时,输入为空的 GNU xargs 会不带任何参数执行一次命令——空目录里的 xargs rm 因此报错,xargs docker rm 则会变成惊喜。BSD 的 xargs 本来就不会在空输入时执行,所以那边不需要 -r,老版本甚至不认这个参数。

坑

  • brew install coreutils 会装上带 g 前缀的孪生命令——gsed、gdate、gstat。把 gnubin 加进 PATH 会让 Mac 表现得像 Linux:修好了你的笔记本,也把 bug 对团队其他人藏了起来。
  • 不要假定有 readlink -f。 较老的 macOS 没有。有 realpath 就用它,否则用 greadlink -f。
  • base64 -d 与 -D。 GNU 用 -d 解码,macOS 历来要 -D。

相关问题

invalid command code 到底什么意思? BSD 的 sed 把下一个词当成备份后缀吞掉了,然后把你表达式的剩余部分当作它不认识的命令来读。这是参数解析的牺牲品,不是正则的问题。

macOS 属于 BSD 还是 Linux? 都不是。它的用户态源自 FreeBSD,所以工具名字和 Linux 一样,参数却不一样。

怎么写出两边都能跑的脚本? 坚持 POSIX 参数,优先用 -E,永远写 sed -i.bak,并且在进 CI 之前在两个系统上都测一遍。

另请参阅

CliAI 会识别你的操作系统和 shell,生成这台机器真正认得的参数。一行命令安装。