摘要:

  本文主要介绍Shell中一个文本处理命令sed

  自己本身也有在学Shell编程,有些操作通过命令来进行却是比人为快得多,最近重写了自己博客下边的自动脚本文件,想到自己经常去重新初始化站点,重新安装主题,每一次也要敲不少的命令,也是比较繁琐,发现原来Shell中有这样一个可以处理文本的命令,但是却要比平时所用的命令复杂得多。

1.sed简介

  sed算是是一种在线编辑器,采用的是流编辑模式,它一次处理文件中一行的内容。

  进行文本处理时,会把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕,所以每处理一次就会有一次输出。接着处理下一行,这样不断重复,直到文件末尾。

【注意】sed 默认不会直接修改源文件数据,而是会将数据复制到缓冲区中,修改也仅限于缓冲区中的数据。

2.sed命令

sed命令,不管是否找到指定的模式,它的退出状态都是0。只有当命令存在语法错误时,sed的退出状态才不是0。语法格式如下:

1
sed [ option ] [ action ] file_name
  • option
option -n 该选项会屏蔽启动输出,仅显示script处理后的结果
-i 直接修改文件内容,而不是输出到终端。
-e [script] 以选项中指定的script来处理输入的文本文件,若之前已有命令,此时会将script脚本命令添加到已有的命令中。
-f [script文件] 以选项中指定的script文件来处理输入的文本文件,若之前已有命令,此时会将script文件中的脚本命令添加到已有的命令中。
【注意】`-i`选项要慎用,毕竟会直接修改原文件。
  • action
action a 新增, a 的后面可以接字符串,这些字串会在新的一行出现(当前行的下一行)。
【注意】若要插入多行,则行之间要加上 \ 。
c 替换整行,用此符号后的新文本替换当前行中的文本。
d 删除,通常不接任何东西
i 插入,后面可以跟字符串,在当前行的上一行插入文本
【注意】若要插入多行,则行之间要加上 \ 。
p 打印,通常与 -n 一起使用。
s 替换字符串,可以搭配正则表达式,用一个字符串替换另一个字符串
y 将字符替换为另一字符(不能对正则表达式使用y命令)
r 将一个独立文件的数据插入到当前数据流的指定位置

【注意】sed 后面接的动作,要以 ' ' 两个单引号括住。

  • 相关正则表达式
字符 说明 实例
^ 行首定位符 /^a/ 匹配所有以a开头的行。
$ 行尾定位符 /a$/ 匹配所有以 a 结尾的行。
. 匹配除换行符以外的单个字符 /a..b/ 匹配包含字母 a ,后跟两个任意字符,再跟字母 b 的行。
* 匹配除换行符以外的单个字符 /a..b/ 匹配包含字母 a ,后跟两个任意字符,再跟字母 b 的行
[] 匹配指定字符组内的任一字符 /[Aa]b/ 匹配包含 Ab 或 ab 的行。
[^] 匹配不在指定字符组内的任一字符 /[^Aa]b/ 匹配包含 b ,但 b 之前的那个字符不是 A 或 a 的行。
\(..\) 保存已匹配的字符 1,5s/\(you\)self/\1r/ 标记元字符之间的模式,并将其保存为标签 1 ,之后可以使用 \1 来引用它。最多可以定义 9 个标签,从左边开始编号,最左边的是第一个。此例中,对第 1 到第 5 行进行处理,you被保存为标签 1 ,如果发现 youself ,则替换为 your 。
& 保存查找的字符串以便在替换串中引用 s/ab/**&**/ 符号 & 代表查找的字符串,ab 将会被替换为**ab**。
\< 词首定位符 /\<ab/ 匹配包含以 ab 开头的单词的行。
\> 词尾定位符 /ab\>/ 匹配包含以 ab 结尾的单词的行。
a\{n\} 连续 n 个 a /a\{5\}/ 匹配包含连续 5 个 a 的行。
a\{n,\} 至少连续 n 个 a /a\{5,\}/ 匹配包含至少连续 5 个 a 的行。
a\{m,n\} 至少连续 m 个,但不超过连续 n 个 a /a\{5,7\}/ 匹配包含连续 5 到 7 个 a 的行。

3.sed使用实例

  只说参数并不能很好理解如何使用,这里将会有大量的实例来帮忙理解。先写一个测试用的文件,就以Hexo的站点配置文件为例,删除一些东西,留下一些用于测试使用。文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def

【说明】我这里用的是WindowsGitbash,与linux中应该是一样的。另外运行脚本中的$ ./mine.sh表示在命令行运行该脚本文件,下边的为输出结果

3.1a和i动作

  • 在第n行前后各插入一行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed ni\string file_name
sed 2i\这是第2行前新插入的行 ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
这是第2行前新插入的行
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
  • 在第n行后插入一行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed na\string file_name
sed 2a\这是第2行后新插入的行 ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
这是第2行后新插入的行
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def

3.2d动作

  • 通过行号指定删除行
1
2
3
4
#!/bin/sh
# sed [ option ] [ action ] file_name
# 格式:sed 'nd' filename
sed '2d' ./_config.yml # 删除第2行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
  • 通过区间删除行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed 'n1,n2d' filename
sed '2,4d' ./_config.yml # 删除2-4行,共删除3行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
  • 通过数据搜索删除行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed '/<string>/d' filename
sed '/theme/d' ./_config.yml # 删除含有theme的行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[11]Theme: def

3.3c动作

  • 通过行号指定替换行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed '2c <string>' filename
sed '2c 这是替换后的第2行' ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
这是替换后的第2行
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
  • 通过区间指定替换行
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed 'n1,n2c <string>' filename
sed '2,3c 2,3行已被替换,这是替换后的行' ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
2,3行已被替换,这是替换后的行
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def

3.4-n选项与p动作

  • 数据的搜索显示
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed '/<string>/p' filename
sed '/theme/p' ./_config.yml # 显示含有 theme的行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[7] theme: theme: landscape theme
[8] theme: aaa
[8] theme: aaa
[9] theme: landscape
[9] theme: landscape
[10]theme: landscape
[10]theme: landscape
[11]Theme: def

会发现,含有theme的行每行都被显示了两次,一次是处理的时候的输出,另一次是p动作的输出。那么如何只输出我所需要的行呢?

  • -n的使用
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed -n '/<string>/p' filename
sed -n '/theme/p' ./_config.yml # 显示含有 theme的行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape

3.5s动作

  s就是字符串替换的命令,使用格式如下:

1
s/original/target/flags
original 需要替换的字符串,也就是原来的字符串
target 替换后的字符串,也就是目标字符串
flags n 1 ~ 512 之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有 5 个 a,但用户只想替换第 3 个 a,就可以使用此标记。
g 对数据中所有匹配到的内容进行替换,如果没有 g,则只会在第 1 次匹配成功时做替换操作。
p 会打印与替换命令中指定的模式匹配的行。此标记通常与 -n 选项一起使用。
w file 将缓冲区中的内容写到指定的 file 文件中。
& 用正则表达式匹配的内容进行替换。
\n 匹配第 n 个子串,该子串之前在 original 中用 \(\) 指定。
\ 转义(转义替换部分包含:&、\ 等)。
  • 替换字符串的n与g标识
1
2
3
4
5
6
7
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed 's/original/target/g' filename
# sed 's/original/target/n' filename
# sed 's/theme/我是替换的字符串/g' ./_config.yml # 替换匹配到的所有 theme
sed 's/theme/我是替换的字符串/2' ./_config.yml # 替换匹配到的第2个 theme

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: 我是替换的字符串: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def

【说明】-g选项则是替换所有的匹配值,这里不再说明。

  • 显示替换行的p标识
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed -n 's/original/target/p' filename
sed -n 's/theme/我是替换的字符串/p' ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[7] 我是替换的字符串: theme: landscape theme
[8] 我是替换的字符串: aaa
[9] 我是替换的字符串: landscape
[10]我是替换的字符串: landscape

【说明】此时仅仅输出了替换的行,但是,只要含有theme的行,每行的第一个theme都会被替换。

  • 匹配结果进行保存的w file标识
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed 's/original/target/w test.txt' filename
sed -n 's/theme/我是替换的字符串/w test.txt' ./_config.yml

运行结果如下:

原文件 text.txt文件内容(屏幕不显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[7] 我是替换的字符串: theme: landscape theme
[8] 我是替换的字符串: aaa
[9] 我是替换的字符串: landscape
[10]我是替换的字符串: landscape
  • 转义标识\

主要是用于替换文件中的路径时用的较多。

1
2
3
4
#!/bin/sh
# sed [ option ] [ action ] file_name
# 格式:sed 's/original/target/g' filename
sed 's/\/bin\/bash/\/abs\/df/g' ./_config.yml

运行结果如下:

原文件(这里增加了第12行用于测试) 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[12]/bin/bash
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[12]/abs/df
- 数据搜索并显示并执行命令
1
2
3
4
5
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
# 格式:sed -n '/string/{s/original/target/;p;q}' filename
sed -n '/theme/{s/landscape/next/;p;q}' ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[7] theme: theme: next theme

【说明】该命令会找到string出现的第一行,再执行后面花括号中的一组命令,每个命令之间用分号分隔,这里把landscape替换为next , p 表示显示处理结果,q表示退出。

3.6y动作

  y 转换命令是唯一可以处理单个字符的 sed 脚本命令。

1
[address]y/inchars/outchars/

  转换命令会对 incharsoutchars 值进行一对一的映射,即 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符…,这个映射过程会一直持续到处理完指定字符。如果 incharsoutchars 的长度不同,则 sed 会产生一条错误消息。

1
2
3
4
5
6
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
sed '1,$y/theme:/THEME*/' datafile
# 将 1 到尾行内所有行,所有的 't' 'h' 'e' 'm' 'e' 5个字母分别转换成 'T' 'H' 'E' 'M' 'E' 5个字母,将 ':' 转换成 '*'
# 正则表达式元字符对y命令不起作用。与s命令的分隔符一样,斜线可以被替换成其它的字符。

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] TiTlE* HExo
[2] auTHor* JoHn DoE
[3] languagE* En
[4] url* HTTp*//ExaMplE.coM
[5] pErMalink* *yEar/*MonTH/*day/*TiTlE/
[6]
[7] THEME* THEME* landscapE THEME
[8] THEME* aaa
[9] THEME* landscapE
[10]THEME* landscapE
[11]THEME* dEf

3.7r动作

  创建一个test.txt的文件,里边写入数据theme: next

1
2
3
4
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
sed '3r test.txt' ./_config.yml # 将test.txt中的文件插入到_config.yml文件第三行后面一行

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
[3] language: en
theme: next
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def

3.8-e 选项

  • 多条命令连接
1
2
3
4
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件不变
sed -e '2,3d' -e 's/theme/string/' ./_config.yml

运行结果如下:

原文件 缓冲区输出(屏幕显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] string: theme: landscape theme
[8] string: aaa
[9] string: landscape
[10]string: landscape
[11]Theme: def

【说明】删除第2,3行,然后将文件中的themestring替换。

3.9-i 选项(慎用)

  • 直接修改文件
1
2
3
4
#!/bin/sh
# sed [ option ] [ action ] file_name
# 注意:原文件被修改
sed -i 's/landscape/next/' ./_config.yml

运行结果如下:

原文件 修改后的文件(屏幕无显示)
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: landscape theme
[8] theme: aaa
[9] theme: landscape
[10]theme: landscape
[11]Theme: def
[1] title: Hexo
[2] author: John Doe
[3] language: en
[4] url: http://example.com
[5] permalink: :year/:month/:day/:title/
[6]
[7] theme: theme: next theme
[8] theme: aaa
[9] theme: next
[10]theme: next
[11]Theme: def

【说明】可以看到,命令直接替换了所有的字符串。

3.10-f 选项

  • sed脚本

  sed脚本就是写在文件中的一列sed命令。脚本中,命令的末尾不能有任何多余的空格或文本。如果在一行中有多个命令,要用分号分隔。使用sed脚本时,不再用引号来确保sed命令不被shell解释

  执行脚本时,sed先将输入文件中第一行复制到模式缓冲区,然后对其执行脚本中所有的命令。每一行处理完毕后,sed再复制文件中下一行到模式缓冲区,对其执行脚本中所有命令。

【说明】文章到这里就暂时结束了,没有遇到过相应的使用方式,笔记先到这里,sed还有其他的用法,后续有用到再进行补充。