跳转至

sed 流编辑器

sed (stream editor) 即流编辑器,它读取输入流 (文件或来自管道的输入),并依照一系列命令对内容进行编辑修改,最后将结果写入标准输出。

sed 是很老牌的文本处理工具,它逐渐被其它工具所取代,例如 awk。但由于 sed 的几个命令非常简洁易用,比如替换、插入和删除,所以它仍然广受喜爱。

一个简单的文本替换案例:

$  # 使用 sed
$  echo "A beautiful girl" | sed 's/girl/woman/'
A beautiful woman
$  
$  # 使用 awk
$  echo "A beautiful girl" | awk '{gsub(/girl/,"woman"); print $0}'
A beautiful woman

sed 常用选项

  • -E, --extended-regexp, 使用扩展正则表达式 (ERE)
  • -e script, --expression=script, 添加 sed 执行的命令
  • -i[SUFFIX], --in-place[=SUFFIX], 直接修改文件,如果提供了 SUFFIX 将会备份

sed 对文本进行修改的命令,默认只会将输出打印出来,加上 -i 直接对文件进行修改,例如:

# 将替换后的文本打印出来
sed 's/foo/bar/' file

# 对文件进行文本替换
sed -i 's/foo/bar/' file

# 对文件进行文本替换,将会创建一个备份文件 file.sed
sed -i.sed 's/foo/bar/' file

-i 与其它选项一起使用时应分开写,例如 -i -E 不能写成 -iE,请看下面的例子:

# 下面两条命令是等价的
sed -Ei '...' file
sed -E -i '...' file

# -iE 等价于 --in-place=E,会创建一个备份文件 fileE
sed -iE '...' file

正则表达式

GNU sed -E 使用 ERE 语法,与 grep ERE 语法 一致。

替换命令语法

替换命令的语法是 s/regexp/replacement/,例如:

$  seq 31 36 | sed 's/33/replace/'
31
32
replace
34
35
36
$  # 替换行尾的数字 3 和 5
$  seq 31 36 | sed 's/[35]$/replace/'
31
32
3replace
34
3replace
36

自定义分割符

替换命令中的分割符 / 也可以用其他符号代替,例如:

$  # 常规书写方式
$  echo "/bin/sh test.sh" | sed 's/\/bin\/sh/\/bin\/bash/'
/bin/bash test.sh
$  
$  # 自定义分割符
$  echo "/bin/sh test.sh" | sed 's%/bin/sh%/bin/bash%'
/bin/bash test.sh
$  echo "/bin/sh test.sh" | sed 's!/bin/sh!/bin/bash!'
/bin/bash test.sh

上面这个例子需要在正则表达式中多次书写 / 符号,如果是 s/regexp/replacement/ 这样的写法,正则表达式里面好几个 / 需要加转义字符,写出来非常难看。改成 s%regexp%replacement% 这样的写法会简洁很多。

可以使用任意单个符号替代 /,恢复该符号的字面量需要加转义字符。

行内定位

sed 默认对每行第 1 次匹配进行替换,也可以全部替换或者选择性替换,例如:

$  # 替换第 1 次匹配
$  echo "a dog and a cat" | sed 's/a/A/'
A dog and a cat
$  
$  # 替换第 3 次匹配
$  echo "a dog and a cat" | sed 's/a/A/3'
a dog and A cat
$  
$  # 替换所有匹配
$  echo "a dog and a cat" | sed 's/a/A/g'
A dog And A cAt

行定位

可以明确指定行数或行区间,例如:

$  # 替换第三行
$  seq 6 | sed '3s/$/foo/'
1
2
3foo
4
5
6
$  # 从第三行到第五行
$  seq 6 | sed '3,5s/$/foo/'
1
2
3foo
4foo
5foo
6
$  # 从第三行到最后一行
$  seq 6 | sed '3,$s/^/foo/'
1
2
foo3
foo4
foo5
foo6

上例中,正则表达式 /$/ 匹配行尾,此时替换命令可以在行尾添加文本。同样 /^/ 可以实现行首添加文本。行区间 3,$ 代表从第三行到最后一行。

删除行

命令 d 用于删除行,例如:

$  seq 6 | sed '3d'
1
2
4
5
6

修改行

命令 c 用于修改行,例如:

$  seq 6 | sed '3c\change'
1
2
change
4
5
6
$  echo -e "line 1\nline 2\nend"
line 1
line 2
end
$  echo -e "line 1\nline 2\nend" | sed '/line/c\change'
change
change
end

修改命令 c 与替换命令 s 不同,它相当于删除旧行再插入新行。

插入和附加新行

命令 i 在匹配的行前面插入新行。命令 a 在匹配的行后面附加新行。请看下面的例子:

$  seq 6 | sed '6i\insert'
1
2
3
4
5
insert
6
$  seq 6 | sed '6a\append'
1
2
3
4
5
6
append

一次执行多条命令

-e 选项可以执行多条命令,例如:

$  seq 6 | sed  -e '2d' -e '5d'
1
3
4
6
$  seq 6 | sed  -e '2d' -e '3i\insert' -e '5d'
1
insert
3
4
6

; 分割多条命令,例如:

$  seq 6 | sed  -e '2d; 5d'
1
3
4
6

sed 实践

修改 apt 源

$  # 源文件
$  cat /etc/apt/sources.list 
deb http://deb.debian.org/debian buster main
deb-src http://deb.debian.org/debian buster main

deb http://deb.debian.org/debian-security/ buster/updates main
deb-src http://deb.debian.org/debian-security/ buster/updates main

deb http://deb.debian.org/debian buster-updates main
deb-src http://deb.debian.org/debian buster-updates main
$ 
$  # 修改文件
$  sudo sed -E -i.sed '/^deb/s%(https?|ftp)://[^/]+/%http://mirrors.aliyun.com/%' /etc/apt/sources.list
$  
$  # 修改后的文件
$  cat /etc/apt/sources.list
deb http://mirrors.aliyun.com/debian buster main
deb-src http://mirrors.aliyun.com/debian buster main

deb http://mirrors.aliyun.com/debian-security/ buster/updates main
deb-src http://mirrors.aliyun.com/debian-security/ buster/updates main

deb http://mirrors.aliyun.com/debian buster-updates main
deb-src http://mirrors.aliyun.com/debian buster-updates main

修改 profile 文件

$  # 写入样例数据
$  sed -i '$a\export EDITOR=nano' ~/.profile
$  sed -i '$a\export EDITOR=vim' ~/.profile
$  grep 'export EDITOR=' ~/.profile
export EDITOR=nano
export EDITOR=vim
$  
$  # 删除配置项
$  sed -i '/^export EDITOR\s*=/d' ~/.profile
$  grep 'export EDITOR=' ~/.profile
$  
$  # 添加配置项
$  sed -i '$a\export EDITOR=/usr/bin/vim' ~/.profile
$  grep 'export EDITOR=' ~/.profile
export EDITOR=/usr/bin/vim

上面这个例子,配置文件有多行重复的配置项,可以先删除再添加。