sed处理字符串

记录sed里面的常见用法

sed

sed介绍

sed是一个gnu项目,a stream editor, 官网http://www.gnu.org/software/sed/manual/sed.html sed是一个非常强大的文本处理工具,是linuxer经常用到的工具。

sed使用

使用方式

1
sed SCRIPT INPUTFILE

工作方式

可以看官方文档http://www.gnu.org/software/sed/manual/sed.html#Execution-Cycle sed在开始执行前维护了模式空间(pattern space)和辅助空间(auxiliary hold space) sed是行编辑器,所以对每一行执行script,执行之前通过address确认是否执行 执行完如果不加-n参数会把模式空间的结果输出到output stream

开始使用

命令CLI
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  -n | --quiet | --silent 安静模式,不打印模式空间,否则相同行会出现2次

  --debug debug模式

  -e | --expresion  script 比较常用,类似sed -i -e 'xxx' -e 'yyy' file, 注意这里也可以通过sed 'xxx ; yyy' file实现多个script

  -f | --file script_file

  -i 实际修改文件
  这里官方聚了个例子
  liuliancao@liuliancao:~$ cat a
  this is a test a.
  liuliancao@liuliancao:~$ sed -ni 's/a/b/' a
  liuliancao@liuliancao:~$ cat a
  为什么会空?因为n为静默,意思模式不输出,而-i则默认会创建一个临时文件最终回写到文件,由于输出为空,所以就是空了
  所以ni不要一起用,且操作文件前请优先备份,如果觉得备份麻烦可以这样做
  liuliancao@liuliancao:~$ sed -i.bak 's/a/b/' a
  这个时候当前目录下会自动生成一个a.bak文件

  -E | -r | --regexp-extended 使用[[http://www.gnu.org/software/sed/manual/sed.html#ERE-syntax][扩展表达式]]
  大致意思是扩展表达需要转移?(){}|等
  举个例子
  liuliancao@liuliancao:~$ echo '(a,b)' | sed  's/\(/"/g'
  sed: -e expression #1, char 8: Unmatched ( or \(
  liuliancao@liuliancao:~$ echo '(a,b)' | sed  's/(/"/g'
  "a,b)
  liuliancao@liuliancao:~$ echo '(a,b)' | sed  -r 's/\(/"/g'
  "a,b)
  liuliancao@liuliancao:~$ echo '(a,b)' | sed  -r 's/(/"/g'
  sed: -e expression #1, char 7: Unmatched ( or \(

  这里可以发现,当使用扩展正则的时候,需要通过\转义,否则转移还会带来错误,这里可以觉得扩展是更严格的表达式,会为()等赋予更多的含义
  所以必须转义,而普通表达式则并不需要
sed的scripts

sed的命令通常是这样, sed [addr]X[options],

这里的X通常就是一个单字母 addr是匹配的行地址

追加
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
liuliancao@liuliancao:~$ cat a
this is line 1 hello, a.
this is line b hello, b.
# a 行下增加,和i相对
liuliancao@liuliancao:~$ sed 'a This is tips below.' a
this is line 1 hello, a.
This is tips below.
this is line b hello, b.
This is tips below.
# i行上插入, 和a相对
liuliancao@liuliancao:~$ sed '1i This is tips above.' a
This is tips above.
this is line 1 hello, a.
this is line b hello, b.
替换
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
  # c 行内替换, 替换指定行
  liuliancao@liuliancao:~$ sed 'c This is tips below.' a
  This is tips below.
  This is tips below.
  liuliancao@liuliancao:~$ sed '1c This is tips below.' a
  This is tips below.
  this is line b hello, b.
  # d删除指定行
  liuliancao@liuliancao:~$ sed '1d' a
  this is line b hello, b.

  # s/regexp/replacement/[flags] 模式替换
  liuliancao@liuliancao:~$ sed 's/a/liuliancao/g' a
  this is line 1 hello, liuliancao.
  this is line b hello, b.
  # 模式替换可以在后向引用的时候对字符或者匹配内容进行大小写转换,其中
  # lowercase \l表示第一个字符,\L 表示所有字符,upppercase \u表示首个字符大写, \U全部大写, \E表示停止转换
  liuliancao@liuliancao:~$ sed -r 's/.*hello, (\w)\./\1/g' a
  a
  b
  liuliancao@liuliancao:~$ sed -r 's/.*hello, (\w)\./\U\1/g' a
  A
  B
  # 全局替换g,这个大家一般都知道,简单举个例子
  liuliancao@liuliancao:~$ echo 'hello, hello boy!' | sed 's/hello/你好/'
  你好, hello boy!
  liuliancao@liuliancao:~$ echo 'hello, hello boy!' | sed 's/hello/你好/g'
  你好, 你好 boy!
  # 指定第几个替换
  liuliancao@liuliancao:~$ echo 'hello, hello boy!' | sed 's/hello/你好/2'
  hello, 你好 boy!
  # n模式替换,n表示打印模式输出
  liuliancao@liuliancao:~$ echo -e '1\n2\n3\n4\n5\n6' | sed  -r  'n;s/.*/double/'
  1
  double
  3
  double
  5
  double
  # 利用n可以灵活打印第2n-1行的数据, 注意两个命令可以用;隔开,整个命令可以用{}包起来
  liuliancao@liuliancao:~$ echo -e '1\n2\n3\n4\n5\n6' | sed  -r  -e 'n;s/.*//' -e '/\s*/d'
  1
  3
  5
  liuliancao@liuliancao:~$ echo -e '1\n2\n3\n4\n5\n6' | sed  -r  -e 'n;s/.*// ; /\s*/d'
  1
  3
  5
退出

这里其实相当于head

1
2
liuliancao@liuliancao:~$ echo -e 'hello\nhello boy!' | sed '1q'
hello
翻译

类似tr

1
2
liuliancao@liuliancao:~$ echo '27890' | sed -r 'y/1234567890/我你他好朋友真的可爱/'
你真的可爱
读文件
1
2
3
4
5
6
# 第一行执行完以后读a文件并输出到第二行
liuliancao@liuliancao:~$ echo -e '123\n456' | sed '1ra'
123
this is line 1 hello, a.
this is line b hello, b.
456
address

如何选择位置,想下vim,目测和vim类似

行匹配

n指定行 a,b执行a到b行, a,+1p a,-1pa和a后面的一行 类似grep -A1和grep -B1

1
2
liuliancao@liuliancao:~$ echo -e '123\n456' | sed -n '1p'
123
不写,就是全局匹配
正则匹配
1
2
liuliancao@liuliancao:~$ echo -e '123\n456' | sed -n '/^4/p'
456
反条件匹配
1
2
liuliancao@liuliancao:~$ echo -e '123\n456' | sed -n '/^4/!p'
123
regular expressions 正则表达式

无论任何编辑器,熟练使用正则表达式才能更加快速准确匹配自己想要的结果,不要急,因为正则非常方便在linux上测试 sed官方文档也有相关说明http://www.gnu.org/software/sed/manual/sed.html#Regular-Expressions-Overview 英文感冒的可以看看菜鸟教程https://www.runoob.com/regexp/regexp-tutorial.html 这里把里面的内容简要说下 正则通过一些字符来匹配想要的内容

  • char

就是一个字典字符串

表示前面匹配的0个或者多个次

1
2
liuliancao@liuliancao:~$ [[ aaa =~ a* ]] && echo "matched" || echo "not matched"
matched
  • .

匹配任意单个字母

  • ^

以跟着后面的开头

1
2
3
# 匹配若干个a的,并且是a开头的
liuliancao@liuliancao:~$ [[ aaa =~ ^a+ ]] && echo "matched" || echo "not matched"
matched
  • $

和^相对,标识结尾

1
2
liuliancao@liuliancao:~$ [[ aaa =~ a+$ ]] && echo "matched" || echo "not matched"
matched
  • [list] [^list]

标识一组字符串,在或者不在

1
2
3
4
5
6
7
8
# match word only characters a,b,c
liuliancao@liuliancao:~$ [[ abbd =~ ^[abc]+$ ]] && echo "matched" || echo "not matched"
not matched
liuliancao@liuliancao:~$ [[ abbc =~ ^[abc]+$ ]] && echo "matched" || echo "not matched"
matched
# match word except characters a,b,c
liuliancao@liuliancao:~$ [[ fg =~ ^[^abc]+$ ]] && echo "matched" || echo "not matched"
matched
  • \+

一个以上

1
2
3
4
liuliancao@liuliancao:~$ [[ b =~ a+ ]] && echo "matched" || echo "not matched"
not matched
liuliancao@liuliancao:~$ [[ ba =~ a+ ]] && echo "matched" || echo "not matched"
matched
  • \?

可有可无的

1
2
3
4
5
# match contains cab or cb
liuliancao@liuliancao:~$ [[ cb =~ ca?b$ ]] && echo "matched" || echo "not matched"
matched
liuliancao@liuliancao:~$ [[ cab =~ ca?b$ ]] && echo "matched" || echo "not matched"
matched
  • \{a,b\}

匹配a到b次,所以?是\{0,1},\+是\{1,}, *是\{0,\}

  • \(regexp\)

捕获,捕获的用途主要如下

  1. 把捕获的东西当成一个整体
  2. 反向引用back references
  • regexp1 | regexp2

一些例子
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
‘abcdef’
Matches ‘abcdef’.

‘a*b’
若干个a(也可以0个)紧接着b,比如b,aab,ab等

‘a\?b’
匹配b或者ab

‘a\+b\+匹配至少一个a接至少一个b,比如abb,ab,aabb等
‘.*’
匹配所有
‘.\+至少一个字符包括空字符

‘^main.*(.*)以main开头,中间是任意字符串,()这里是捕获

‘^#’
以#开头的,比如过滤除去注释的正则^\s*#

\\$’
\字符结尾的

\$以$字符结尾的

[a-zA-Z0-9]匹配字母数字

[^ TAB]\+T、A、B、space组成的包含至少一个的

‘^\(.*\)\n\1$’
This matches a string consisting of two equal substrings separated by a newline.
匹配上下两行完全一样的内容,这里没有找到很好的例子,后补
‘.\{9\}A$’
9个字符接A结尾

‘^.\{15\}A’
This matches the start of a string that contains 16 characters, the last of which is an ‘A’.
15个字符串接A结尾
双中括号标记字符串组合
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[:alnum:]Alphanumeric characters: ‘[:alpha:]’ and ‘[:digit:]; in the ‘C’ locale and ASCII character encoding, this is the same as ‘[0-9A-Za-z]’.
字母和数字

[:alpha:]Alphabetic characters: ‘[:lower:]’ and ‘[:upper:]; in the ‘C’ locale and ASCII character encoding, this is the same as ‘[A-Za-z]’.
字母

[:blank:]Blank characters: space and tab.
空格,或者制表符


[:digit:]Digits: 0 1 2 3 4 5 6 7 8 9.
数字


[:lower:]Lower-case letters; in the ‘C’ locale and ASCII character encoding, this is a b c d e f g h i j k l m n o p q r s t u v w x y z.
小写字母

[:punct:]Punctuation characters; in the ‘C’ locale and ASCII character encoding, this is ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~.

‘[:space:]’
Space characters: in the ‘C’ locale, this is tab, newline, vertical tab, form feed, carriage return, and space.

‘[:upper:]’
Upper-case letters: in the ‘C’ locale and ASCII character encoding, this is A B C D E F G H I J K L M N O P Q R S T U V W X Y Z.

‘[:xdigit:]’
Hexadecimal digits: 0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f.
正则表达式扩展regular expression extensions

\w 单词 \W 非单词 \b 单词边界 \< 单词左边 \>单词右边

1
2
3
4
liuliancao@liuliancao:~$ echo 'hello, boy!' | sed 's/\b/X/g'
XhelloX, XboyX!
liuliancao@liuliancao:~$ echo 'hello, boy!' | sed 's/\</X/g ; s/\>/Y/g'
XhelloY, XboyY!

\` 模式空间开头 \'模式空间结尾 \s 匹配空白 § 匹配非空白

多行匹配

多行匹配是sed比较重要的内容,通过D, G, H, N, P来控制多行匹配,他们分别是什么意思呢 了解之前还是需要明白,sed保留有两个空间,一个模式空间pattern space, 一个辅助的保留空间hold space, 这两个空间默认都是空的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
D
deletes line from the pattern space until the first newline, and restarts the cycle.
模式空间的内容全部删掉,保留最后一行,跳过其他script,并重新执行
G
appends line from the hold space to the pattern space, with a newline before it.

H
appends line from the pattern space to the hold space, with a newline before it.

N
appends line from the input file to the pattern space.

P
prints line from the pattern space until the first newline.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
liuliancao@liuliancao:~$ echo -e 'hello\ni\nam\na\nboy' | sed -n 'N; l; D'
hello\ni$
i\nam$
am\na$
a\nboy$
# step1. sed read first line hello into pattern space
# step2. N appends the next line \n and i into pattern space.
# step3. l prints the pattern space
# step4. D remove the content of pattern space, leave the last line i
# step5. Next N appends the next line \n and am into pattern space
# step6. l prints the pattern space
# step7. D remove the content of pattern space, levave the last line name
# ...
# 可以看出,我们除了针对面向行的操作,也可以在相邻行进行操作

官方同样列出针对段落的匹配方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
sed '/./{H;$!d} ; x ; s/REGEXP/REPLACEMENT/'
d的含义
delete (clears) the current pattern space, and restart the program cycle without processing the rest of the commands and without printing the pattern space.

# /./{H;$!d} 一个一个读
# 整个分成3段,后两段其实是一段, 这个理解是很重要的
# 第一段是遍历匹配非空行,接下来做三件事情,
# 放到hold space(H)
# 清空模式空间, 段落最后一行不d($!d)
# 重新执行sed script,后面的x和s替换都不会执行如果是非空行
# 如果进入空行,执行x,(因为没有d,不会重启cycle) ,把hold空间的内容取出到模式空间
# 打印的是整个段落哦
# 并执行替换(这个时候替换的都是空白地方)
# 总结, 类似吃鸡蛋,饿的时候一直吃鸡蛋,早中晚,从冰箱(file)取出鸡蛋(characters),吃了很多蛋都放进碗里(hold space), 鸡蛋经过舌头(pattern space)吃到肚子里(stdout)
# 不饿(/./)之前的一个蛋($!)吃完后,把蛋每个都标记记录下,都过一下舌头,挨个贴下标签(s/regexp/replacement/), 最终消化各个时间点的蛋
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 贴下官方的例子
$ cat input.txt
a a a aa aaa
aaaa aaaa aa
aaaa aaa aaa

bbbb bbb bbb
bb bb bbb bb
bbbbbbbb bbb

ccc ccc cccc
cccc ccccc c
cc cc cc cc

$ sed '/./{H;$!d} ; x ; s/^/\nSTART-->/ ; s/$/\n<--END/' input.txt

START-->
a a a aa aaa
aaaa aaaa aa
aaaa aaa aaa
<--END

START-->
bbbb bbb bbb
bb bb bbb bb
bbbbbbbb bbb
<--END

START-->
ccc ccc cccc
cccc ccccc c
cc cc cc cc
<--END

看到这里知道为啥sed可以出一个orelly的书了吧。

分支控制
  • d

删掉模式空间内容,然后直接继续cycle,不执行其他命令,也不打印

  • D

删除所有,只保留最后一行,然后继续cycle,不执行其他命令,也不打印

  • [addr]X

最常见的

  • [addr]{ X ; X ; X}
  • [regexp] { X ; X ; X}
  • b

branch unconditionally (that is: always jump to a label, skipping or repeating other commands, without restarting a new cycle). Combined with an address, the branch can be conditionally executed on matched lines.

  • t

branch conditionally (that is: jump to a label) only if a s/// command has succeeded since the last input line was read or another conditional branch was taken.

  • T

similar but opposite to the t command: branch only if there has been no successful substitutions since the last input line was read.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
$ printf '%s\n' a1 a2 a3 | sed -E '/1/bx ; s/a/z/ ; :x ; y/123/456/'
a4
z5
z6

$ printf '%s\n' a1 a2 a3 | sed -E '/1/!s/a/z/ ; y/123/456/'
a4
z5
z6
# 可以看到当匹配1的时候,只执行x片段,否则执行所有片段 这样个人认为会一定程度增加代码复杂性
# 第二种方式是非匹配/1/!做什么, 否则做什么
liuliancao@liuliancao:~$ echo -e 'hello\ni\nam\na\nboy' | sed --debug -r '/h/bx ; s/a/z/ ; :x ; y/hello/12335/'
SED PROGRAM:
  /h/ b x
  s/a/z/
  :x
  y/hello/12335/
INPUT:   'STDIN' line 1
PATTERN: hello
COMMAND: /h/ b x
COMMAND: :x
COMMAND: y/hello/12335/
PATTERN: 12335
END-OF-CYCLE:
12335
INPUT:   'STDIN' line 2
PATTERN: i
COMMAND: /h/ b x
COMMAND: s/a/z/
PATTERN: i
COMMAND: :x
COMMAND: y/hello/12335/
PATTERN: i
END-OF-CYCLE:
i
INPUT:   'STDIN' line 3
PATTERN: am
COMMAND: /h/ b x
COMMAND: s/a/z/
MATCHED REGEX REGISTERS
  regex[0] = 0-1 'a'
PATTERN: zm
COMMAND: :x
COMMAND: y/hello/12335/
PATTERN: zm
END-OF-CYCLE:
zm
INPUT:   'STDIN' line 4
PATTERN: a
COMMAND: /h/ b x
COMMAND: s/a/z/
MATCHED REGEX REGISTERS
  regex[0] = 0-1 'a'
PATTERN: z
COMMAND: :x
COMMAND: y/hello/12335/
PATTERN: z
END-OF-CYCLE:
z
INPUT:   'STDIN' line 5
PATTERN: boy
COMMAND: /h/ b x
COMMAND: s/a/z/
PATTERN: boy
COMMAND: :x
COMMAND: y/hello/12335/
PATTERN: b5y
END-OF-CYCLE:
b5y
# 这里其实可以看到和上面的一样的结果, 通过--debug我们可以很清晰看出实际的执行情况

继续看官方的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
liuliancao@liuliancao:~$ seq 3 | sed --debug ':x ; n ; bx'
SED PROGRAM:
  :x
  n
  b x
INPUT:   'STDIN' line 1 # 数据读入1
PATTERN: 1 # 模式空间存1
COMMAND: :x # 进入条件x
COMMAND: n # 执行指令n,n和N的意思都是next的意思,在操作下一行之前,n打印当前的模式空间,而N则是追加到模式空间,
1 # 虽然是第一次,仍然会打印当前模式空间内容,并且清空内容
PATTERN: 2 # 此时命名空间设置为下一个行的值为2
COMMAND: b x # 进入bx分支
COMMAND: :x # 进入x
COMMAND: n # 第二次执行n,打印2并置空
2
PATTERN: 3 # 同时把模式空间的值设置为下一行的值
COMMAND: b x
COMMAND: :x
COMMAND: n # 第三次执行n,打印3并置空
3
END-OF-CYCLE:

liuliancao@liuliancao:~$ seq 3 | sed --debug ':x ; N ; bx'
SED PROGRAM:
  :x
  N
  b x
INPUT:   'STDIN' line 1 # 首行读入
PATTERN: 1 # 模式空间入1
COMMAND: :x
COMMAND: N # 执行N,追加下一行到模式空间包括换行
PATTERN: 1\n2
COMMAND: b x
COMMAND: :x
COMMAND: N
PATTERN: 1\n2\n3 # 继续追加
COMMAND: b x
COMMAND: :x
COMMAND: N # 没有下一行了,无需追加
END-OF-CYCLE:
1
2
3
printf '%s\n' aa bb cc dd | sed ':x ; n ; = ; bx' # 这里=目测是行号
aa
2
bb
3
cc
4
dd
printf '%s\n' aa bb cc dd | sed ':x ; N ; = ; bx'
2
3
4
aa
bb
cc
dd
printf '%s\n' aa bb cc dd | sed ':x ; n ; s/\n/***/ ; bx'
aa
bb
cc
dd
printf '%s\n' aa bb cc dd | sed ':x ; N ; s/\n/***/ ; bx'
aa***bb***cc***dd
$ cat jaques.txt
All the wor=
ld's a stag=
e,
And all the=
 men and wo=
men merely =
players:
They have t=
heir exits =
and their e=
ntrances;
And one man=
 in his tim=
e plays man=
y parts.
$ sed ':x ; /=$/ { N ; s/=\n//g ; bx }' jaques.txt
All the world's a stage,
And all the men and women merely players:
They have their exits and their entrances;
And one man in his time plays many parts.
# 这个例子是=结尾的并行,注意对应的语法,并行后,注意是对所有内容进行替换

总结下,n和N

  • n和N都是对下一行处理的多行控制单元
  • n是把下一行打印并清空模式空间,所以例子是一次打印下一,下二,下三,模式空间没有\n
  • N是把下一行追加到当前模式空间,但不打印,最后由于没有下一行,最终打印了, N可以用于两行匹配替换的情况, 模式空间会有并行的\n

总结

  • sed对于我们来说,优先掌握正则表达式和常用的替换s,打印等
  • 了解sed的工作方式,通过地址+script加两个空间构成
  • 了解sed从–debug开始
  • 对于多行处理,比如对并行(N)和段落'/./{H ; $!d} ; x ; s/regexp/replacement'、分支b等类似也建议了解下,多行处理的时候比较方便,目前看仍然需要进一步学习下
  • 再看一眼sed
1
2
3
4
5
liuliancao@liuliancao:~$ echo -e 'this is line 1 hello, a.\nthis is line 1 hello, b.'|sed '/./{H ; $!d} ; x ; s/^/Begin/g ; s/$/\nEnd/g'
Begin
this is line 1 hello, a.
this is line 1 hello, b.
End