AI Infra 训练营
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub

sed —— 流编辑器

一句话定义

sed(stream editor)读取输入(文件或 stdin),按你给的命令做替换/删除/插入,输出到 stdout(或 -i 原地改文件)。最常用的是 s/pattern/replace/(substitute),日常 99% 场景就这一个。

典型场景

训练营文档里出场 24 次。绝大多数都在做"原地改配置文件":

# 改 sshd 配置(出自 Day0 §2.3)
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config

# 改 sysctl 参数
sed -i '/^#net.ipv4.ip_forward/s/^#//' /etc/sysctl.conf

# 改 hosts 文件
sed -i '/old-host/d' /etc/hosts

sed 的本质:行流处理

sed 一行一行处理输入:

  1. 读入一行到"模式空间"(pattern space)
  2. 对这一行运行你给的命令
  3. 输出模式空间内容(默认)
  4. 读下一行

知道这个就够了——sed 不是"打开文件 → 找到 → 修改 → 保存",而是"一边读一边改一边输出"。

input.txt          sed 'command'              stdout
 line 1   ───────►  apply to "line 1"  ───►   modified line 1
 line 2   ───────►  apply to "line 2"  ───►   modified line 2
 line 3   ───────►  apply to "line 3"  ───►   modified line 3

最常用:s/pattern/replace/ 替换

echo "hello world" | sed 's/world/k8s/'
# hello k8s

echo "foo foo foo" | sed 's/foo/bar/'        # 只换每行第一个
# bar foo foo

echo "foo foo foo" | sed 's/foo/bar/g'       # g = global,全部换
# bar bar bar

echo "FOO foo" | sed 's/foo/bar/I'           # I = ignore case
# bar bar

分隔符可以换

s/pat/rep/ 默认用 / 作分隔符。如果 pattern 或 replace 里有 /,会很丑:

sed 's/\/usr\/local\/bin/\/opt\/bin/' file       # 太多反斜线
sed 's|/usr/local/bin|/opt/bin|' file            # 换用 | 作分隔符
sed 's,/usr/local/bin,/opt/bin,' file            # 用 ,

/、|、,、# 等都行(除了反斜线和换行)。改路径时记得换分隔符,代码可读性高十倍。

替换里引用匹配内容

引用含义
&整个匹配内容
\1 \2 ...第 N 个捕获组(\(...\) BRE 或 (...) -E ERE)
echo "kubelet started" | sed 's/kubelet/[&]/'
# [kubelet] started                                    ← & 引用整个匹配

echo "ssh root@10.0.24.28" | sed -E 's/root@([0-9.]+)/admin@\1/'
# ssh admin@10.0.24.28                                 ← \1 是捕获的 IP

-i 原地修改(写脚本必会)

sed -i 's/old/new/' file.txt                # 直接改文件,无输出
sed -i.bak 's/old/new/' file.txt            # 改之前备份成 file.txt.bak

-i.bak 是最安全的写法:跑完出问题能 rollback。生产环境第一次用 sed 改文件,永远先 -i.bak。

macOS / BSD 的 sed -i 行为不同:必须给后缀(哪怕空字符串):

sed -i '' 's/old/new/' file.txt   # macOS:'' 表示不备份
sed -i.bak 's/old/new/' file.txt  # macOS / Linux 都行

在跨平台脚本里统一写 sed -i.bak 然后 rm file.bak 是最稳的。


地址(按行号或模式选行)

sed 命令前可以指定地址——只对匹配的行执行命令。

地址语法含义
无所有行
5第 5 行
5,10第 5 到 10 行
$最后一行
5,$第 5 行到末尾
/pattern/匹配 pattern 的行
/start/,/end/start 行到 end 行(含两端)
5~3第 5 行开始每 3 行(5, 8, 11, ...)

例子

sed -n '1,5p' file              # 只打印第 1-5 行(-n 不默认输出,p 打印)
sed '5d' file                   # 删第 5 行
sed '/^#/d' file                # 删所有以 # 开头的行(注释)
sed '/^$/d' file                # 删空行
sed '/start/,/end/d' file       # 删 start 到 end 之间的所有行(含)

sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
# 删 sshd_config 里以 PasswordAuthentication 开头的行

sed '/error/s/$/ <-- check/' log
# 给含 error 的行末加注释

训练营里最实用的几个套路

套路 1:注释 / 取消注释某行

# 注释(在行首加 #)
sed -i '/^kubelet/s/^/#/' /etc/somefile

# 取消注释(去掉行首 #)
sed -i '/^#kubelet/s/^#//' /etc/somefile

# 通用"含某词的行取消注释"
sed -i 's/^#\(.*kubelet.*\)/\1/' /etc/somefile

套路 2:改配置参数(已存在)

sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config

^Pattern.* —— 行首 + 所有内容。这样不管原值是 yes / no / commented,都被替换成新值。

但如果原配置里没这一行呢?sed 改不出来。所以更稳的套路是:

套路 3:改或加(idempotent)

# 如果存在 → 改;不存在 → 加
if grep -q "^PasswordAuthentication" /etc/ssh/sshd_config; then
  sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config
else
  echo "PasswordAuthentication no" >> /etc/ssh/sshd_config
fi

或者更紧凑(grep 静默 + 短路):

grep -q "^PasswordAuthentication" /etc/ssh/sshd_config \
  && sed -i 's/^PasswordAuthentication.*/PasswordAuthentication no/' /etc/ssh/sshd_config \
  || echo "PasswordAuthentication no" >> /etc/ssh/sshd_config

套路 4:批量 ssh 改远端

Day0 改 5 个节点的 sshd_config:

for h in m1 m2 m3 m4 m5; do
  ssh "$h" "sed -i \
    -e 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' \
    -e 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' \
    /etc/ssh/sshd_config"
done

-e cmd 可以叠多个命令(见下面)。

套路 5:删除某段

# 删除 /etc/hosts 里关于 old-host 的所有行
sed -i '/old-host/d' /etc/hosts

# 删除两个标记之间的内容(含标记)
sed -i '/# BEGIN MARKER/,/# END MARKER/d' /etc/somefile

套路 6:在某行后插入

sed -i '/^Match Group admin/a\    PermitRootLogin yes' /etc/ssh/sshd_config
# 在 Match Group admin 那一行后插入 "    PermitRootLogin yes"

sed -i '/^# end of file/i\new line above' /etc/somefile
# 在 "# end of file" 那一行前插入

a\ = append (after),i\ = insert (before),c\ = change (replace whole line)。


多命令:-e 或 ; 或 {}

# 多个 -e
sed -e 's/foo/bar/' -e 's/baz/qux/' file

# 分号分隔
sed 's/foo/bar/; s/baz/qux/' file

# 一组命令对同一个地址生效
sed '/error/{ s/^/[ERR] /; s/$/!/ }' file

写在文件里

复杂的 sed 脚本写在文件里:

# script.sed
s/foo/bar/g
s/old/new/g
/^#/d
sed -f script.sed input.txt

-n + p 选择性输出

sed 默认每行都输出。-n 关闭默认输出,配合 p(print)只输出你要的:

sed -n '5p' file                          # 只输出第 5 行
sed -n '1,10p' file                       # 第 1-10 行(等价 head)
sed -n '/error/p' file                    # 含 "error" 的行(等价 grep)
sed -n '/start/,/end/p' file              # 两个标记之间的内容

实际上 grep 干这个比 sed -n /pat/p 方便。但 sed 能干 grep 干不了的"两个标记之间"。


高阶:y 字符转换、G/H/N 多行处理

日常用得少,知道有就行。

echo "hello" | sed 'y/abcdefghij/ABCDEFGHIJ/'        # 字符级映射(等价 tr)
# HEllo

sed 'N; s/\n/ /' file                                # 把每 2 行合成 1 行

多行处理在 sed 里很反人类,遇到要写就直接换 awk 或 perl。


常见踩坑

坑 1:BSD/macOS sed 和 GNU sed 不兼容

最大区别:

sed -i 's/x/y/' file        # ❌ macOS 报错:extra characters at the end of i command
sed -i '' 's/x/y/' file     # ✅ macOS:必须给后缀(空字符串表示不备份)

跨平台脚本永远写 sed -i.bak + 后续 rm *.bak。或者用更现代的 gsed(macOS 上 brew install gnu-sed)。

坑 2:替换串里的 & 被意外引用

echo "version 1.0" | sed 's/version/[&]/'
# [version] 1.0                                       ← & 被替换成 "version"

如果你不要 & 的引用:转义 \&。

坑 3:变量带 / 没换分隔符

DIR=/usr/local/bin
sed -i "s/PATH/$DIR/" /etc/profile
# ❌ 报错:unknown option to `s'                       ← $DIR 里的 / 把 sed 命令断了

修:换分隔符。

sed -i "s|PATH|$DIR|" /etc/profile

坑 4:-i 备份语法搞错

sed -i .bak 's/x/y/' file       # ❌ Linux 不认这个(".bak" 被当成文件名)
sed -i.bak 's/x/y/' file        # ✅ Linux 正确
sed -i '.bak' 's/x/y/' file     # ✅ macOS 正确

注意 .bak 和 -i 之间没空格(Linux),或者有空格但带引号(macOS)。

坑 5:变量里的反斜线 / 双引号被 shell 吃了

PATTERN='^kubelet\s+'
sed "/$PATTERN/d" file          # ❌ shell 先把 \s 处理掉
sed '/^kubelet\s+/d' file       # ✅ 单引号

或者把变量写更严格:PATTERN='^kubelet[[:space:]]\+'。

坑 6:错把 sed -i 当 sed -n 用

sed -i '/error/p' file          # ❌ 把所有行写回(因为没 -n),而且每行打 print 一次

正确:

sed -n '/error/p' file          # 只看不写

-i(in-place)和 -n(no default output)是两件事,经常被打错。

坑 7:sed 在管道里输出延迟

类似 grep。要实时输出:

sed -u 's/foo/bar/g'        # -u (unbuffered)
# 或 stdbuf -oL sed ...

坑 8:以为 sed 能跨行匹配

sed 's/foo\nbar/X/' file    # ❌ 默认每次只处理一行,找不到跨行 foo\nbar

sed 跨行处理很难写(要 N、P、hold space)。有跨行需求直接上 perl 或 awk:

perl -0777 -pe 's/foo\nbar/X/g' file        # -0777 把整个文件当一个字符串

关联命令

  • grep —— 找用 grep、改用 sed
  • awk —— 按字段 / 复杂逻辑用 awk
  • tr —— 字符级转换(比 sed y/... 简单)
  • perl -pe 's/.../.../g' —— sed 跨行 / PCRE 替代
  • tee —— sed 改完想同时输出到屏幕和文件
在 GitHub 上编辑此页