Jacky's blog
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • Android
  • Web
  • Server
  • Python
  • iOS
  • Java
  • Vue
  • 计算机基础
  • AI
  • 全部工具
  • CLI 工具
  • 编辑器
  • 开发工具
  • 构建工具
  • 版本控制
  • 调试工具
  • 网络工具
  • 个人成长
  • 博客
  • 分类
  • 标签
  • 归档
收藏
GitHub (opens new window)
  • 开发工具

  • 构建工具

  • 版本控制

  • 容器

  • 调试工具

  • 测试工具

  • CLI 工具

    • tmux
    • oh-my-zsh
    • ripgrep 高性能文本搜索工具使用指南
    • awk
    • sed
      • base
        • OPTIONS
        • mode
        • &
      • BSD sed vs GNU sed 兼容性
        • 为什么会有两个 sed?
        • 核心差异一览
        • 最容易踩的坑:非贪婪匹配
        • 其他常见不兼容语法
      • Xcode Run Script 环境的特殊坑
      • 最佳实践
      • 实战案例:Xcode 编译失败全过程
      • link
    • xargs
    • fzf
    • fd
    • raycast
    • VisiData 终极生存指南(vd)
    • yq
    • direnv
    • ln 软链接/硬链接使用指南
    • watch 周期监控使用指南
    • osascript
  • 网络工具

  • 编辑器

  • 设计工具

  • 多媒体

  • 包管理

  • 平台工具

  • tools
  • cli_tools
Jacky
2024-11-01
目录

sed

sed 是一个流编辑器(Stream EDitor),对输入流(文件或管道输入)进行基本的文本转换。

踩坑背景:一个 shell 脚本本地命令行跑了 N 次都没问题,放进 Xcode Run Script 就报 RE error: repetition-operator operand invalid——根因是 GNU sed 和 BSD sed 的语法差异,完整排查过程见文末实战案例。

# base

sed 命令的一般语法是: sed OPTIONS... [SCRIPT] [INPUTFILE...]. 其中 OPTIONS 是命令行选项, SCRIPT 是 sed 的编辑脚本, 可以是一个或多个命令, INPUTFILE 是要处理的输入文件

# OPTIONS

  • -i: 原地编辑文件,而非输出到控制台。平台差异:GNU 直接 sed -i 's/a/b/',macOS/BSD 必须带空串参数 sed -i '' 's/a/b/',跨平台写法见 其他常见不兼容语法
  • -n: 抑制自动输出

# mode

  • s: 替换, 规则 s/PATTERN/REPLACEMENT/FLAGS. FLAGS 是可选的标志, 例如 g 表示全局替换
    • sed -E 's/\[//g; s/\]//g': 多规则替换。这里表示替换 [ 和 ] 为空字符串
    • sed 's/foo/bar&/' text.txt: 在 foo 前面直接加 bar。 这里的 &: 表示当前匹配到的字符串
  • d: 删除模式
    • sed '/foo/d' text.txt: 删除文本中所有包含 "foo" 的行
  • i: 在匹配模式前插入一行文本
    • sed '/foo/i\bar' text.txt
  • a: 用于在匹配模式后追加一行文本
    • sed '/foo/a\bar' text.txt
  • p: 用于打印匹配模式的行
    • sed -n '1p' url.txt: 只打印第一行
    • sed -n '/foo/p' text.txt

# &

表示当前匹配到的字符串

# BSD sed vs GNU sed 兼容性

# 为什么会有两个 sed?

历史遗留问题(POSIX / GNU 的概念背景见 shell 入门指南):

版本 来源 默认在哪里 特点
BSD sed BSD Unix 原版 macOS 系统自带 /usr/bin/sed 严格 POSIX 标准,功能少
GNU sed GNU 项目 Linux 默认;Mac 上需 brew install gnu-sed,命令叫 gsed(默认不覆盖系统 sed) 扩展语法多,功能强大,但不兼容 BSD

关键问题:很多 Mac 开发者装了 gnu-sed,再按 brew 提示把它的 gnubin 目录(/opt/homebrew/opt/gnu-sed/libexec/gnubin,里面的 sed 即 GNU 版)顶到 PATH 前面,或 alias 成 sed——本地命令行和脚本里用的就是 GNU sed,写脚本时会习惯性地用 GNU 扩展语法。一旦脚本被放到 Xcode Run Script 等干净环境里执行——PATH 里只有系统目录,用的是 BSD sed——就炸了。

# 核心差异一览

特性 GNU sed BSD sed 兼容性
基本正则 (BRE) ✅ ✅ ⚠️ POSIX 子集兼容;GNU BRE 还有 \+、\?、\| 等扩展,BSD 不认
扩展正则 (ERE, -E) ✅ ✅ ⚠️ 部分扩展语法不兼容
非贪婪匹配 .+? ⚠️ 不报错,但按贪婪处理 ❌ 直接报错 ❌ 两家都不支持非贪婪
\s 空白字符 ✅ ❌ ❌ 不兼容
\d 数字 ❌ 也不支持 ❌ ❌ PCRE 特性,sed 里别用
\w 单词字符 ✅ ❌ ❌ 不兼容
-i 原地修改 sed -i 's/old/new/' sed -i '' 's/old/new/' ❌ 语法不同
\n 换行匹配 ✅ ⚠️ 部分支持 ⚠️ 有差异
分组捕获 \(...\) \(...\) ✅ 兼容
后向引用 \1 ✅ ✅ ✅ 兼容

# 最容易踩的坑:非贪婪匹配

正则默认是贪婪匹配(尽可能多匹配);非贪婪匹配(.+?,尽可能少匹配)是 Perl/PCRE 的特性,sed 两家都不支持(正则语法基础见 正则表达式完整指南)。.+? 里的 ? 在 sed 里只是"0 或 1 次"量词,两个量词连用属于 POSIX 未定义行为,两家各选了一种处理方式:

  • GNU sed:不报错,实际仍按贪婪匹配处理——坑更隐蔽,你以为在用非贪婪,其实没有。实测:
echo "<a><b>" | gsed -E 's/<(.+?)>/[\1]/'
# 输出 [a><b] —— 一直吃到最后的 >,并没有非贪婪
1
2
  • BSD sed:严格校验,直接报语法错误:
sed: RE error: repetition-operator operand invalid
1

解决:

# ❌ PCRE 习惯写法(GNU 不报错但按贪婪处理,BSD 直接报错)
echo "my-project" | sed -E 's/.+?-([a-z0-9]+)$/\1/'

# ✅ 方案一:用 [^-] 排除法,匹配"到分隔符为止"
echo "my-project" | sed -E 's/[^-]+-([a-z0-9]+)$/\1/'

# ✅ 方案二:本来就要匹配到最后一个分隔符,直接用 .*
echo "my-project" | sed -E 's/^.*-([a-z]+[0-9]*)$/\1/'

# ✅ 方案三:复杂场景干脆用 awk,跨平台一致性更好
echo "my-project" | awk -F'-' '{print $NF}'
1
2
3
4
5
6
7
8
9
10
11

# 其他常见不兼容语法

1. \s 匹配空白

# ❌ PCRE 习惯写法(BRE 下 + 是字面量,GNU sed 上也不生效)
echo "hello world" | sed 's/\s+/-/g'

# ❌ GNU 专属写法(\s 是 GNU 扩展,BSD 把它当字面量 s,静默得到错误结果)
echo "hello world" | sed -E 's/\s+/-/g'

# ✅ 兼容写法(POSIX 字符类,两端都能跑)
echo "hello world" | sed -E 's/[[:space:]]+/-/g'
1
2
3
4
5
6
7
8

2. \d 匹配数字(GNU sed 也不支持)

# ❌ PCRE 习惯写法(\d 是 PCRE 特性,GNU sed 也不支持,实测原样输出)
echo "abc123" | sed -E 's/\d+//g'

# ✅ 兼容写法(用 [0-9])
echo "abc123" | sed -E 's/[0-9]+//g'
1
2
3
4
5

3. \w 单词字符

# ❌ GNU 专属(\w 是 GNU 扩展,BSD 把它当字面量 w)
echo "hello world" | sed -E 's/\w+/(&)/g'    # GNU/gsed 输出 (hello) (world),BSD 输出 hello (w)orld

# ✅ 兼容写法(POSIX 字符类)
echo "hello world" | sed -E 's/[[:alnum:]_]+/(&)/g'
1
2
3
4
5

4. -i 原地修改参数

# ❌ GNU 写法
sed -i 's/old/new/' file.txt

# ✅ BSD 写法(需要空字符串参数)
sed -i '' 's/old/new/' file.txt

# ✅ 跨平台兼容写法(带后缀备份再删掉)
sed -i.bak 's/old/new/' file.txt && rm file.txt.bak
1
2
3
4
5
6
7
8

5. \n 匹配换行:GNU sed 支持在模式里直接用 \n 匹配换行,BSD sed 不行(替换串里的 \n 两家都可以)。多行文本处理建议换 awk 或 perl。

6. \b 单词边界:无统一写法——GNU 认 \b,BSD 认 [[:<:]]/[[:>:]],互不相认。跨平台请改写成显式字符类或换 awk/perl。

7. BRE 里没有 \+:\+ 是 GNU BRE 扩展,BSD 不认。POSIX BRE 表达"一个或多个"用重复写法,或改用 -E:

# ✅ POSIX BRE:[x][x]* 等价于 x+
echo "hello world" | sed 's/[a-zA-Z0-9][a-zA-Z0-9]*/(&)/g'

# ✅ 或直接用 -E(ERE 的 + 两家都支持)
echo "hello world" | sed -E 's/[a-zA-Z0-9]+/(&)/g'
1
2
3
4
5

# Xcode Run Script 环境的特殊坑

Xcode Run Script 的环境和本地终端完全不一样,这是很多人踩坑的根源:

1. PATH 很精简:默认只有 /usr/bin:/bin:/usr/sbin:/sbin,没有 Homebrew 路径(/opt/homebrew/bin、/usr/local/bin)。本地命令行用的 yq、node、python3、GNU sed 统统找不到,用的是系统自带的 BSD 工具。解决方法:脚本开头手动加 PATH:

#!/bin/sh
if ! command -v yq >/dev/null 2>&1; then
    for p in /opt/homebrew/bin /usr/local/bin; do
        [ -d "$p" ] && export PATH="$p:$PATH"
    done
fi
1
2
3
4
5
6

2. Shell 可能不是你想的那个:默认用 /bin/sh。如果写成 bash "${PROJECT_DIR}/scripts/xxx.sh",Xcode 会按 PATH 找 bash,ARM Mac 上可能命中 /usr/local/bin 的 Intel 版 bash,报 Bad CPU type in executable。解决:用绝对路径 /bin/bash,或脚本第一行写 #!/bin/bash 直接调用。

3. 环境变量不一样:有 PROJECT_DIR、SRCROOT、BUILT_PRODUCTS_DIR 等 Xcode 专用变量,但没有 ~/.zshrc、~/.bashrc 里的别名和 PATH。

# 最佳实践

1. 脚本开头统一设置 PATH:

#!/bin/sh
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
1
2

2. 正则只用 POSIX 标准语法:\s/\d/\w/.+?/\b 的替代写法见上文 其他常见不兼容语法。

3. 复杂正则用 awk/perl 代替:跨平台一致性比 sed 好很多。

4. 写完脚本在干净环境下测一遍:

# 模拟 Xcode Run Script 的干净环境
env -i HOME="$HOME" PATH="/usr/bin:/bin:/usr/sbin:/sbin" /bin/sh your_script.sh
1
2

# 实战案例:Xcode 编译失败全过程

问题现象:Xcode 里 Cmd+R 编译报 Command PhaseScriptExecution failed with a nonzero exit code,但命令行 xcodebuild 完全正常。

排查过程:

  1. 看错误日志:sed: 1: "s/^.+?-([a-z]+[0-9]*)$/ ...": RE error: repetition-operator operand invalid
  2. 定位到脚本 scripts/build/generate_build_info.sh 第 44 行
  3. 本地手动跑:bash scripts/build/generate_build_info.sh ✅ 完全正常,没报错
  4. 模拟干净环境:env -i HOME="$HOME" PATH="/usr/bin:/bin" /bin/sh scripts/build/generate_build_info.sh ❌ 复现同样的错误
  5. 根因:本地用的是 Homebrew 装的 GNU sed,遇到 .+? 不报错(静默按贪婪处理);Xcode 环境用的是系统自带的 BSD sed,+? 量词连用直接报语法错误。

修复:

# ❌ 修复前(PCRE 习惯写法,BSD 报错)
BUILT_BY=$(echo "${WORKTREE_NAME}" | sed -E 's/^.+?-([a-z]+[0-9]*)$/\1/')

# ✅ 修复后(POSIX 兼容)
BUILT_BY=$(echo "${WORKTREE_NAME}" | sed -E 's/^.*-([a-z]+[0-9]*)$/\1/')
1
2
3
4
5

💡 记住:本地能跑不代表 CI 能跑,更不代表 Xcode 能跑。写 shell 脚本时,永远假设你跑在最干净的最小环境里,只用最基础的语法。

# link

  • shell 入门指南 - POSIX / GNU 概念背景与 shell 基础
  • 正则表达式(Regex)完整指南 - 正则语法基础
  • sed 命令用法小结 (opens new window)
  • GNU sed 用户手册 (opens new window)
#Shell#sed#Linux#debug#best-practice
上次更新: 2026/10/18, 22:14:48
awk
xargs

← awk xargs→

最近更新
01
Codex 使用与会话管理指南
10-17
02
Tailscale 入门:让自己的设备跨网络互联
10-15
03
GitHub CLI(gh)工作流与 CI 排查
10-08
更多文章>
Theme by Vdoing | Copyright © 2019-2026 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式