【前情提要】 awk 练熟了,小林能从日志里按字段统计 Top IP、Top URL、状态码分布。老周说:“grep 筛,awk 算,但如果你想‘改’日志内容呢?比如脱敏 IP、提取时间段、删除空行。”小林打开 sed 文档,准备学第三把刀。但他不知道,s///、-i、-n、p、d,每一个都有讲究。

一、老周的第三把刀

早上到工位,老周丢过来一个任务:

“你博客的 access.log,里面有真实 IP。你把它脱敏,把 192.168.1.100 换成 ***.***.***.***。”

“用 awk 不行吗?”

“awk 能改,但改文本是 sed 的强项。”老周说,“grep 筛行,awk 算列,sed 改内容。这是日志分析的三把刀,各管一段。”

“那 sed 怎么用?”

“你先看它的基本语法。”

sed 's/旧内容/新内容/' 文件

“s 是 substitute,替换。/ 是分隔符。默认只替换每行第一个匹配。”

小林试:

echo "hello world" | sed 's/world/ubuntu/'

输出:

hello ubuntu

“成了。”小林说。

“但你注意,我让你脱敏 IP,不是只换一个。日志里每行都有 IP,你得全局替换。”

“加 g?”

“对。”

echo "192.168.1.100 192.168.1.100" | sed 's/192.168.1.100/***.***.***.***/g'

输出:

***.***.***.*** ***.***.***.***

“g 是 global,全局替换。不加 g,只替换每行第一个。”

小林记下:

  • sed 's/旧/新/':替换每行第一个
  • sed 's/旧/新/g':全局替换

二、实战一:脱敏 IP

“现在正式脱敏。”老周说,“先看日志。”

sudo tail -5 /var/log/nginx/access.log

输出:

192.168.1.100 - - [14/Sep/2026:10:00:01 +0800] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
192.168.1.101 - - [14/Sep/2026:10:00:02 +0800] "GET /posts/ HTTP/1.1" 200 2345 "-" "Mozilla/5.0"
192.168.1.100 - - [14/Sep/2026:10:00:03 +0800] "GET /api/health HTTP/1.1" 500 153 "-" "curl/7.81.0"

“你把所有 192.168.1.x 换成 ***.***.***.***。”

“我写 sed 's/192\.168\.1\.[0-9]*/***.***.***.***/g'?”

“为什么加点?”

“因为 . 在正则里是‘任意字符’,不转义的话,192x168x1x100 也会匹配。”小林说。

“对。”老周说,“sed 默认用基本正则,. 是元字符,要匹配真正的点,得写 \.。”

小林敲:

sudo sed 's/192\.168\.1\.[0-9]*/***.***.***.***/g' /var/log/nginx/access.log | head -5

输出:

***.***.***.*** - - [14/Sep/2026:10:00:01 +0800] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
***.***.***.*** - - [14/Sep/2026:10:00:02 +0800] "GET /posts/ HTTP/1.1" 200 2345 "-" "Mozilla/5.0"
***.***.***.*** - - [14/Sep/2026:10:00:03 +0800] "GET /api/health HTTP/1.1" 500 153 "-" "curl/7.81.0"

“IP 没了。”小林说。

“但注意,你只是输出到屏幕,没改文件。”老周说,“要改文件,加 -i。”

sudo sed -i 's/192\.168\.1\.[0-9]*/***.***.***.***/g' /var/log/nginx/access.log

“但你先别改原文件。”老周拦住他,“-i 直接改文件,改错了没法恢复。你先输出到新文件。”

sudo sed 's/192\.168\.1\.[0-9]*/***.***.***.***/g' /var/log/nginx/access.log > /tmp/access_masked.log

“这是安全做法。”老周说,“sed 默认输出到屏幕,要改文件用 -i,但建议先输出到新文件确认。”

小林记下:

  • sed 's/正则/替换/g' file > newfile:输出到新文件,安全
  • sed -i 's/正则/替换/g' file:直接改文件
  • 正则里 . 要写 \.

三、-n 和 p:只看你想看的行

“sed 还有两个常用参数:-n 和 p。”老周说。

“p 是 print?”

“对。默认 sed 会打印所有行。加 -n 取消默认打印,p 才会打印匹配的行。”

sudo sed -n '1,5p' /var/log/nginx/access.log

输出前 5 行。

“这跟 head -5 有啥区别?”

“head 只能看前几行,sed 能看任意范围。”

sudo sed -n '100,200p' /var/log/nginx/access.log

“第 100 到 200 行。”老周说,“你以后定位问题,先 grep -n 找到行号,再 sed -n '行号p' 看上下文。”

“那 -n 不加 p 呢?”

“-n 不加 p,什么都不输出。”老周说,“-n 是‘取消默认打印’,p 是‘打印’。两个一起用,才是‘只打印匹配行’。”

小林试:

sudo sed -n '1p' /var/log/nginx/access.log

输出第一行。

sudo sed -n '$p' /var/log/nginx/access.log

输出最后一行。

“$ 表示最后一行。”老周说,“sed -n '$p' 就是看最后一行。”

小林记下:

  • sed -n 'Np':打印第 N 行
  • sed -n 'M,Np':打印第 M 到 N 行
  • sed -n '$p':打印最后一行

四、实战二:提取时间段日志

“现在提取 10:00 到 10:05 的日志。”老周说。

“怎么写?”

“sed -n '/起始时间/,/结束时间/p'。”

sudo sed -n '/14\/Sep\/2026:10:00/,/14\/Sep\/2026:10:05/p' /var/log/nginx/access.log | head -20

“注意,日志里的 / 在 sed 里是分隔符,所以要转义成 \/。”老周说,“或者换个分隔符,比如 #。”

sudo sed -n '\#14/Sep/2026:10:00#,\#14/Sep/2026:10:05#p' /var/log/nginx/access.log | head -20

“用 # 做分隔符,就不用转义 / 了。”老周说,“sed 的分隔符可以是任意字符,选一个日志里不常见的,省得转义。”

“那如果我只想提取 10:00 这一分钟的日志呢?”

sudo sed -n '/14\/Sep\/2026:10:00/p' /var/log/nginx/access.log | head -20

“这是匹配包含 10:00 的所有行。”老周说,“但注意,10:00 也会匹配 10:00 到 10:00:59,不会匹配 10:01。sed 是按行匹配,不是按时间解析。”

小林记下:

  • sed -n '/起始/,/结束/p':提取范围
  • 日志里的 / 要转义成 \/,或用其他分隔符
  • sed -n '/时间/p':提取包含该时间的行
  • sed 不解析时间,只按文本匹配

五、实战三:删除空行和注释行

“你以后处理配置文件,经常要删空行和注释。”老周说。

“怎么删?”

“d 是 delete。”

sed '/^$/d' 文件

“^$ 表示空行,d 表示删除。”老周说,“你试试。”

小林创建了一个测试文件:

cat > /tmp/test.conf << 'EOF'
# 这是注释
server_name localhost;

# 这是另一条注释
listen 80;

root /var/www/blog;
EOF

删除空行:

sed '/^$/d' /tmp/test.conf

输出:

# 这是注释
server_name localhost;
# 这是另一条注释
listen 80;
root /var/www/blog;

删除注释行:

sed '/^#/d' /tmp/test.conf

输出:

server_name localhost;

listen 80;

root /var/www/blog;

“两个一起用。”

sed -e '/^$/d' -e '/^#/d' /tmp/test.conf

输出:

server_name localhost;
listen 80;
root /var/www/blog;

“-e 可以指定多个编辑命令。”老周说,“sed 的 d 是删除行,s 是替换,p 是打印。你以后处理配置,先删空行和注释,再看有效内容。”

小林记下:

  • sed '/^$/d':删除空行
  • sed '/^#/d':删除注释行
  • sed -e '/^$/d' -e '/^#/d':多个命令

六、sed -i:Mac 和 Ubuntu 的差异

“现在讲一个你之前踩过的坑。”老周说。

小林立刻想起来:“sed -i,Mac 要 -i '',Ubuntu 要 -i。”

“对。”老周说,“你在 Mac 上写脚本,如果用 sed -i,拿到 Ubuntu 跑,就会报错。Ubuntu 的 GNU sed,-i 后面直接跟命令;Mac 的 BSD sed,-i 后面必须跟一个备份后缀,空字符串也要写。”

“那我怎么写兼容脚本?”

“两种方式。”老周说。

方式一:判断系统。

if [[ "$(uname)" == "Darwin" ]]; then
    sed -i '' 's/old/new/g' file
else
    sed -i 's/old/new/g' file
fi

方式二:用 -i.bak,两边都认。

sed -i.bak 's/old/new/g' file

“-i.bak 表示备份原文件为 file.bak,两边行为一致。”老周说,“但这样会多出备份文件,你得手动删。”

“那推荐哪个?”

“你主线在 Ubuntu 上写脚本,用 -i 就行。”老周说,“Mac 只做编辑器,不在 Mac 上跑 sed -i。这是最省事的。”

小林记下:

  • Mac(BSD sed):sed -i '' 's/old/new/g' file
  • Ubuntu(GNU sed):sed -i 's/old/new/g' file
  • 兼容写法:sed -i.bak 's/old/new/g' file
  • 主线在 Ubuntu 写脚本,别在 Mac 跑 sed -i

七、排错挑战:sed -i 把文件改坏了

下午,小林想批量修改 Nginx 配置,把 listen 80; 改成 listen 8080;。

他写:

sudo sed -i 's/listen 80;/listen 8080;/g' /etc/nginx/sites-available/blog

然后检查:

cat /etc/nginx/sites-available/blog

输出:

server {
    listen 8080;
    server_name localhost;
    root /var/www/blog;
    index index.html;
    location / {
        try_files $uri $uri/ =404;
    }
}

“看起来没问题。”小林说。

然后他测试:

sudo nginx -t

输出:

nginx: configuration file /etc/nginx/nginx.conf test is successful

“通过了。”小林说。

“那你访问一下。”

小林在 Mac 浏览器访问 http://localhost:8080。

无法访问

“啥?”小林懵了,“配置没问题,Nginx 测试通过,为什么访问不了?”

他喊来老周。

老周看了一眼:“你改的是 listen 80 改成 listen 8080,但你的 Nginx 主配置里,有没有别的站点还在监听 80?如果有,两个站点冲突,Nginx 会拒绝启动或只加载一个。”

小林检查:

ls /etc/nginx/sites-enabled/

输出:

blog
default

“default 还在。”小林说,“它监听 80,blog 监听 8080,不冲突啊。”

“那 VMware Fusion 的端口转发呢?”老周问,“你之前配的是 Mac 8080 → Ubuntu 80。现在 Ubuntu 的 Nginx 监听 8080,你 Mac 访问 localhost:8080,转发到 Ubuntu 的 80,但 Ubuntu 的 80 没有服务在听。”

“那我得改端口转发?”

“对。”老周说,“要么把 Nginx 改回 80,要么把端口转发改成 Mac 8080 → Ubuntu 8080。你改了一个地方,得把整条链路都检查一遍。”

小林改回 80:

sudo sed -i 's/listen 8080;/listen 80;/g' /etc/nginx/sites-available/blog
sudo nginx -t
sudo systemctl reload nginx

再次访问 http://localhost:8080,博客回来了。

排错思路总结:

  1. sed -i 改配置,改完先 nginx -t 测试。
  2. 测试通过但访问不了,检查整条链路:Nginx 监听端口 → 防火墙 → 端口转发 → 浏览器访问地址。
  3. 改端口时,所有依赖这个端口的配置都要同步改。
  4. sites-enabled/ 下可能有多个站点,检查是否冲突。
  5. 最稳的做法:改配置前先备份,cp file file.bak。

“记住,”老周说,“sed -i 是直接改文件,改错了没法撤销。 你改配置前,先 cp 备份。cp /etc/nginx/sites-available/blog /etc/nginx/sites-available/blog.bak。改坏了,mv 回来就行。”

八、sed 常用命令速查

老周拿过小林的笔记本,写了一个表。

# 替换
sed 's/old/new/' file          # 替换每行第一个
sed 's/old/new/g' file         # 全局替换
sed -i 's/old/new/g' file      # 直接改文件(GNU)

# 打印
sed -n 'Np' file               # 打印第 N 行
sed -n 'M,Np' file             # 打印第 M 到 N 行
sed -n '$p' file               # 打印最后一行
sed -n '/pattern/p' file       # 打印匹配行

# 删除
sed '/pattern/d' file          # 删除匹配行
sed '/^$/d' file               # 删除空行
sed '/^#/d' file               # 删除注释行

# 多命令
sed -e 's/old/new/g' -e '/^$/d' file

# 范围提取
sed -n '/start/,/end/p' file

“这十几条,你背下来。”老周说,“以后处理日志和配置,80% 的场景都用这些。”

小林把速查表抄了一遍。

九、练习

  1. 练习一:用 sed 's/192\.168\.1\.[0-9]*/***.***.***.***/g' 脱敏 access.log,输出到 /tmp/access_masked.log。用 head -5 确认 IP 被替换。
  2. 练习二:用 sed -n '/14\/Sep\/2026:10:00/,/14\/Sep\/2026:10:05/p' 提取 10:00 到 10:05 的日志。用 wc -l 统计提取了多少行。然后用 sed -n '100,200p' 查看第 100 到 200 行。
  3. 练习三:创建一个测试配置文件,包含注释和空行。用 sed -e '/^$/d' -e '/^#/d' 删除空行和注释,输出到新文件。对比原文件和新文件的行数。

参考答案

练习一:

sudo sed 's/192\.168\.1\.[0-9]*/***.***.***.***/g' /var/log/nginx/access.log > /tmp/access_masked.log
head -5 /tmp/access_masked.log

练习二:

sudo sed -n '/14\/Sep\/2026:10:00/,/14\/Sep\/2026:10:05/p' /var/log/nginx/access.log > /tmp/time_range.log
wc -l /tmp/time_range.log
sudo sed -n '100,200p' /var/log/nginx/access.log | head -10

练习三:

cat > /tmp/test.conf << 'EOF'
# 这是注释
server_name localhost;

# 这是另一条注释
listen 80;

root /var/www/blog;
EOF

wc -l /tmp/test.conf
sed -e '/^$/d' -e '/^#/d' /tmp/test.conf > /tmp/test_clean.conf
wc -l /tmp/test_clean.conf
cat /tmp/test_clean.conf

十、下集预告

sed 练熟了,小林能脱敏 IP、提取时间段、删除空行和注释了。老周说:“grep 筛,awk 算,sed 改,三把刀你都会了。但真正分析日志,得把它们串起来。”小林打开终端,准备学组合拳。但他不知道,grep | awk | sort | uniq -c | sort -rn | head 这条管道,每一步都有讲究。

下一集:《组合拳:grep + awk + sed 三件套》