bash sed

Created

2023-09-12 17:30:16

Updated

2024-10-28 12:38:03

env version
sed sed (GNU sed) 4.7

1 工作原理

sed = stream editor 可以对标准输出和文件进行逐行处理 ### 命令结构

Diagram

1.1 流程图

Diagram

1.2 –debug 分析流程

1.2.1 p 打印操作分析

hello cat
hello world
hot dog
# = 的行为是打印匹配的行的行号
sed '/hello/=;p' example.txt
执行结果
1
hello cat
hello cat
2
hello world
hello world
hot dog
hot dog
sed --debug '/hello/=;p' example.txt
执行结果
SED PROGRAM:
/hello/ =
p
INPUT:   'example.txt' line 1
PATTERN: hello cat
COMMAND: /hello/ =
1
COMMAND: p
hello cat
END-OF-CYCLE:
hello cat
INPUT:   'example.txt' line 2
PATTERN: hello world
COMMAND: /hello/ =
2
COMMAND: p
hello world
END-OF-CYCLE:
hello world
INPUT:   'example.txt' line 3
PATTERN: hot dog
COMMAND: /hello/ =
COMMAND: p
hot dog
END-OF-CYCLE:
hot dog
  1. 第5行, 显示 pattern space buffer的内容,就是行内容
  2. 显示结果里的第6行COMMAND: /hello/ = 判断pattern space是否包含hello,是的话就打印它的行号
  3. 第8行 COMMAND: p [addr]是空,就是直接匹配了,直接p操作打印pattern space的内容.
  4. 第11行是 不带-n的 默认行为 打印了pattern space的内容 hello cat

1.2.2 s 替换操作分析

sed  's/hello/xxxx/gp' example.txt
执行结果
xxxx cat
xxxx cat
xxxx world
xxxx world
hot dog
sed --debug 's/hello/xxxx/gp' example.txt
执行结果
SED PROGRAM:
s/hello/xxxx/gp
INPUT:   'example.txt' line 1
PATTERN: hello cat
COMMAND: s/hello/xxxx/gp
MATCHED REGEX REGISTERS
regex[0] = 0-5 'hello'
xxxx cat
PATTERN: xxxx cat
END-OF-CYCLE:
xxxx cat
INPUT:   'example.txt' line 2
PATTERN: hello world
COMMAND: s/hello/xxxx/gp
MATCHED REGEX REGISTERS
regex[0] = 0-5 'hello'
xxxx world
PATTERN: xxxx world
END-OF-CYCLE:
xxxx world
INPUT:   'example.txt' line 3
PATTERN: hot dog
COMMAND: s/hello/xxxx/gp
PATTERN: hot dog
END-OF-CYCLE:
hot dog
  1. 第8行 是 s/hello/xxxx/gp中p这个s的flag的行为(打印pattern space). 注意它其实并不是X,s是X,这个p只是它的flag,只不过行为与身为X的p操作一样.后续再说
  2. 第9行 显示变化后的 pattern space 的内容给我们看. 这个时候已经被替换了相关的字符串. 如果patttern space 内容有变化会在这里重新显示一次
  3. 不带-n的默认操作, 打印 pattern space

1.2.3 a 行后插入操作分析

sed -n '/world/a python' example.txt
执行结果
python

从执行结果看, a操作默认会打印你插入的内容

# 我们debug 一下不带 -n 参数的 流程.
sed --debug '/world/a python' example.txt
执行结果
SED PROGRAM:
/world/ a\python

INPUT:   'example.txt' line 1
PATTERN: hello cat
COMMAND: /world/ a\python

END-OF-CYCLE:
hello cat
INPUT:   'example.txt' line 2
PATTERN: hello world
COMMAND: /world/ a\python

END-OF-CYCLE:
hello world
python
INPUT:   'example.txt' line 3
PATTERN: hot dog
COMMAND: /world/ a\python

END-OF-CYCLE:
hot dog
  1. 从11-16行可以看到 Pattern space并没有发生变化. (有变化会重新显示一次)
  2. 15行是 没有-n 的默认行为, 打印Pattern space
  3. 16行是a 的行为. 它发生在 默认打印pattern space行为的后面
  4. a的行为: 不改变pattern space , 它会把在该命令后面的文本,在当前CYCLE结束或读取下一行时输出.
  5. 注意上面的空行3,7,13,20这几行,这是COMMAND本身的一部分. 就是说我们a python后面实际跟着一个换行符,要不然也不会是插入一行了. 所以SED PROGRAM:后面显示的是2行,包含了一个换行符

1.2.4 i 行前插入操作分析

sed '/world/i python' example.txt
执行结果
hello cat
python
hello world
hot dog
sed  --debug '/world/i python' example.txt
执行结果
SED PROGRAM:
/world/ i\python

INPUT:   'example.txt' line 1
PATTERN: hello cat
COMMAND: /world/ i\python

END-OF-CYCLE:
hello cat
INPUT:   'example.txt' line 2
PATTERN: hello world
COMMAND: /world/ i\python

python
END-OF-CYCLE:
hello world
INPUT:   'example.txt' line 3
PATTERN: hot dog
COMMAND: /world/ i\python

END-OF-CYCLE:
hot dog
  1. 第14行直接打印了, 可以与a操作 做个对比
  2. 官方原话: Immediately output the lines of text which follow this command

1.2.5 H和x操作 与hold space

官方例子, 我内容稍微变下

js
vue
react

python
php

java
golang
rust
sed '/./{H;$!d} ; x ; s/^/\nSTART-->/ ; s/$/\n<--END/' input.txt
# 上面的命令会在最开头增加一个空行. 这里这个将这个去掉了.
# sed '/./{H;$!d} ; x ; s/^/START-->/; $!s/$/\n<--END\n/;$s/$/\n<--END/' input.txt
执行结果
                    
START-->
js
vue
react
<--END

START-->
python
php
<--END

START-->
java
golang
rust
<--END
X Description
H1 添加一个换行符到hold space,然后将pattern space 的内容追加到hold space
x2 互相hold space和pattern space的内容
d 删除 pattern space的内容,立刻开始下一次cycle,后续的命令不再执行哦
sed --debug '/./{H;$!d} ; x ; s/^/\nSTART-->/ ; s/$/\n<--END/' input.txt
执行结果
SED PROGRAM:
/./ {
    H
    $! d
}
x
s/^/
START-->/
s/$/
<--END/
INPUT:   'input.txt' line 1
PATTERN: js
COMMAND: /./ {
COMMAND:   H
HOLD:    \njs
COMMAND:   $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 2
PATTERN: vue
COMMAND:   /./ {
COMMAND:     H
HOLD:    \njs\nvue
COMMAND:     $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 3
PATTERN: react
COMMAND:     /./ {
COMMAND:       H
HOLD:    \njs\nvue\nreact
COMMAND:       $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 4
PATTERN:
COMMAND:       /./ {
COMMAND:       }
COMMAND:       x
PATTERN: \njs\nvue\nreact
HOLD:
COMMAND:       s/^/
START-->/
MATCHED REGEX REGISTERS
regex[0] = 0-0 ''
PATTERN: \nSTART-->\njs\nvue\nreact
COMMAND:       s/$/
<--END/
MATCHED REGEX REGISTERS
regex[0] = 22-22 ''
PATTERN: \nSTART-->\njs\nvue\nreact\n<--END
END-OF-CYCLE:

START-->
js
vue
react
<--END
INPUT:   'input.txt' line 5
PATTERN: python
COMMAND:       /./ {
COMMAND:         H
HOLD:    \npython
COMMAND:         $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 6
PATTERN: php
COMMAND:         /./ {
COMMAND:           H
HOLD:    \npython\nphp
COMMAND:           $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 7
PATTERN:
COMMAND:           /./ {
COMMAND:           }
COMMAND:           x
PATTERN: \npython\nphp
HOLD:
COMMAND:           s/^/
START-->/
MATCHED REGEX REGISTERS
regex[0] = 0-0 ''
PATTERN: \nSTART-->\npython\nphp
COMMAND:           s/$/
<--END/
MATCHED REGEX REGISTERS
regex[0] = 20-20 ''
PATTERN: \nSTART-->\npython\nphp\n<--END
END-OF-CYCLE:

START-->
python
php
<--END
INPUT:   'input.txt' line 8
PATTERN: java
COMMAND:           /./ {
COMMAND:             H
HOLD:    \njava
COMMAND:             $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 9
PATTERN: golang
COMMAND:             /./ {
COMMAND:               H
HOLD:    \njava\ngolang
COMMAND:               $! d
END-OF-CYCLE:
INPUT:   'input.txt' line 10
PATTERN: rust
COMMAND:               /./ {
COMMAND:                 H
HOLD:    \njava\ngolang\nrust
COMMAND:                 $! d
COMMAND:               }
COMMAND:               x
PATTERN: \njava\ngolang\nrust
HOLD:    rust
COMMAND:               s/^/
START-->/
MATCHED REGEX REGISTERS
regex[0] = 0-0 ''
PATTERN: \nSTART-->\njava\ngolang\nrust
COMMAND:               s/$/
<--END/
MATCHED REGEX REGISTERS
regex[0] = 26-26 ''
PATTERN: \nSTART-->\njava\ngolang\nrust\n<--END
END-OF-CYCLE:

START-->
java
golang
rust
<--END
  1. 第13行, /./匹配非空行
  2. 第14行, H指令, 将换行符+pattern space追加到 hold space
  3. 第15行, 可以看到hold space的内容
  4. 第16行, d 操作 ,然后17行直接 去下一次 cycle,后续 命令不再执行.
  5. 第32行, input.txt 第4行是空行, 所以/./ 不匹配, 没有做{}里面的d操作, 则继续后面的指令 x,将hold space和pattern space 互换了.
  6. 后面是s 替换操作. 最后 不带-n的默认 打印pattern space 操作.

2 OPTION 参数

OPTION Description
-n 仅显示 script 处理后的结果
-e 以指定的 script 来处理输入的文本文件,默认就有这个参数
-f : 以指定的文件的内容来作为 script 来处理 input-file
-E/-r 支持扩展正则 (推荐用-E 可移植性)
带-E与不带-E的区别
1. 不带的时候 那么{}()|+? 没有特别含义,就是它本身,如果想要变成特殊含义则前面加上\
2. 带上-E与之相反
-i 直接修改 input-file 的内容
-s 放到input-file章节说明
--debug 显示 sed 的执行过程,低版本的sed可能没有
hello cat
hello world
hot dog

下面这个,匹配则打印处理后的行(/hello/p的行为),然后不管匹配与否再打印处理后的行(不带-n的默认行为).

sed '/hello/p' example.txt
执行结果
hello cat
hello cat
hello world
hello world
hot dog

只打印匹配的行

sed -n '/hello/p' example.txt
执行结果
hello cat
hello world

指定多个-e, 就是对读取的行,按顺序进行 script1,然后script2 操作

sed -n -e '/hello/p' -e '/world/p' example.txt
执行结果
hello cat
hello world
hello world

将 /hello/p 这样的 script 写到文件中去,执行如下命令

sed -n -f script.sed example.txt

|无特殊含义,就是它本身,所以没有找到匹配的行.

sed  -n '/hello|world/p' example.txt

这里的|表示特殊含义或 ,所以可以正确匹配.

sed  -n -E '/hello|world/p' example.txt
sed -n 's/cat/dog/g;p' example.txt
# 直接修改源文件了, 注意这里就不能再加上;p了. 否则你文件里就重复行了.
sed -i 's/cat/dog/g' example.txt
  • ==使用-i后面直接带上.bak 来修改文件的同时进行备份.==
# 会先将example.txt 做个备份, 文件名是
sed -i.bak 's/cat/dog/g' example.txt.bak
# 所以 写-i 和其他 -E这种参数时要注意, 不要放在i的后面 , 
# 写成 -Ei而不是-iE 后者是做了备份了.E成了备份文件的后缀名
关于-i的底层逻辑
  1. 实际是创建了一个临时文件, 将输出写入到该临时文件而不是终端.
  2. 等到文件读完,会将临时文件rename 为 我们 原本的文件名
# 我们使用strace来跟踪我们的sed操作里与文件有关的系统调用
strace -e trace=file sed -i 's/cat/dog/g' example.txt
执行结果
...
...
openat(AT_FDCWD, "example.txt", O_RDONLY)  = 3
openat(AT_FDCWD, "./sedE5aURA", O_RDWR|O_CREAT|O_EXCL, 0600) = 4
rename("./sedE5aURA", "example.txt")       = 0

3 input-file

tomcat
hello cat
google
sed  -n '/cat/{p;=}' example.txt text.txt
执行结果
hello cat
1
tomcat
4
hello cat
5
  1. 看行号,我们可以知道sed是将2个文件当成一个文件来处理
  2. 使用 -s 参数, 会将每个文件作为一个单独的来处理
sed  -ns '/cat/{p;=}' example.txt text.txt
执行结果
hello cat
1
tomcat
1
hello cat
2

4 script

4.1 [addr] 匹配行

hello one
hello two
tree
four
five
space
blank
world one
xxx two
yyy
world five
zzz
# 会打印所有行 这个时候 [addr] 可以说是空的.
sed -n "p" example.txt
sed -n "s/hello/world/g" example.txt
# 匹配到第二行
sed -n '2p' example.txt
执行结果
hello two
# 包含hello的行
sed -n '/hello/p' example.txt
# 大写的I 表示忽略大小写, 如果写成 i ,是 行前插入的操作. 是X类别
sed -n '/hello/Ip' example.txt
  • 行号指定区间,多行
# 匹配到第2到4行
sed -n '2,4p' example.txt
# 第2行, 再加2行, 也就是2-4行, 一共3行
sed -n '2,+2p' example.txt
执行结果
hello two
tree
four
  • 正则区间 /pattern1/,/pattern2/
  1. 匹配到pattern1的行开始到匹配到pattern2的行结束
  2. 如果开始的行没有匹配到,那么就没有匹配到行
  3. 而到哪一行为止呢? 稍微思考下, 可以得出这样的结论 :
    1. 如果到哪行为止没有匹配到, 就意味着一直匹配到最后一行
    2. 到哪行为止 有多行都能匹配到, 肯定是首先匹配的那一行.
# 含有two的行到  含有five的行
sed -n '/two/,/five/p' example.txt

多个符合的区间, 就会都匹配上的

执行结果
hello two
tree
four
five
xxx two
yyy
world five
# 第一个区间匹配到了. 第二个区间只匹配到开始的行. 那么第二个区间就是匹配行到最后一行
sed -n '/two/,/four/p' example.txt
执行结果
hello two
tree
four
xxx two
yyy
world five
zzz
  • 行号正则混合使用
#/four/ 如果没匹配到行,那么从第 1 行一直匹配到最后,会都打印.
sed -n '1,/four/p' example.txt
#如果匹配到行号大于 后面的具体行数, 那么就只会显示 /four/ 匹配到的行
sed -n '/four/,6p' example.txt
# 排除1-2 行,其他行进行s操作
sed -n '1,2!s/one/111/p' example.txt
# 打印不保护hello的行
sed -n '/hello/!p' example.txt
# 打印最后一行
sed -n '$p'  text.txt example.txt
# 打印各自文件的最后一行.
sed -ns '$p'  text.txt example.txt

4.2 X 指令

4.2.1 查 (p)

X Description
p 打印处理后的匹配行 (打印pattern space + 换行符)
= 打印匹配行的行号 + 换行符
  • p
seq 3|sed -n '2p'
  • =
# 显示匹配到的行的行号
sed -n '/hello/=' example.txt

seq -f "line%g" 1 3|sed '='
执行结果
1
line1
2
line2
3
line3

4.2.2 增 (a i r w)

X Description
a 行后追加
i 行前追加
r 外部文件读入, 行后追加
w 匹配行写入外部文件

# 在匹配到的hello 行下一行 ,添加 NIHAO 这样一行
# a后面 空格 再跟上你要添加的内容
sed -i '/hello/a NIHAO' example.txt
# 匹配到的每一个行 后都添加一行 内容是hi
sed -i '/hello/,/world/a hi'
# 插入多行.
sed -i '/hello/a\
hi\
how are your' example.txt

# 行前
sed -i '/hello/i NIHAO' example.txt
# 将new.txt 里的内容添加到 包含hello 的行后
sed -i '/hello/r new.txt' example.txt
sed -n '/hello/w new.txt' out.txt

4.2.3 删 (d)

X Description
d 删除 pattern space的内容,立刻开始下一次cycle,
后续的命令不再执行且默认不带-n时的打印处理后的行(pattern space+换行符) 的行为也不会执行.
如果这个默认行为没有取消,那么你不带-n时每次d,都会打印一行空行了
seq -f "line%g" 1 3 | sed '2d;='
执行结果
1
line1
3
line3
# 删除包含hello的行到包含world的行, 有多个就执行删除多个区间
sed '/hello/,/world/d' example.txt

4.2.4 改 (s)

Diagram
X Description
s : 替换操作
flags Description
i/I 匹配时忽略大小写
HEllo cat
xx hello yyy hello
hot dog HELLO
#每一行的第一个 hello 替换为 world
sed 's/hello/world/' example.txt
# 全部hello替换为world
sed 's/hello/world/g' example.txt 
#忽略大小写,全部替换 i/I 大写的I也是一个意思
sed 's/hello/world/ig' example.txt 
# 将行内第 2 个匹配的 hello 到后面所有匹配到的 hello 替换为 world
sed 's/hello/world/2g' example.txt
sed 's/hello/world/2g' example.txt 执行结果
HEllo cat
xx hello yyy world
hot dog HELLO
# \U 将匹配到的内容 转为大写 (\u 是第一个字符大写)
sed 's/hello/\U&/g' example.txt
# 将xx转为大写, yyy转为大写 \E表示停止前面\U 的行为,就是\E后面的不会继续转大写了.
sed -E 's/(xx)\shello\s(yyy)/\U\1\E hello \U\2/g' example.txt
# 同理 \L 是将匹配到的内容 转为小写, \l 是将第一个字符转小写
sed -E 's/hello/\L&/ig' example.txt
regex Description
& 正则表达式匹配到的所有内容
\n (n值为1-9) 正则表达式里\(\)里匹配到的内容
# 将两行的eat/EAT 改成==> don't eat/EAT
# & 表示/eat/正则匹配到的具体内容
sed "s/eat/don't &/i" <<EOF
cats eat fish
dogs EAT fish
EOF
执行结果
cats don't eat fish
dogs don't EAT fish
# \S 非空白字符
# 按顺序 第一个() 就是 \1...
sed -E "s/(\S+)\s(eat)\s(\S+)/\3 \2 \1/i" <<EOF
cats eat fish
dogs EAT fish
EOF
执行结果
fish eat cats
fish EAT dogs

4.2.5 n/N

X Description
n 如果没有使用参数-n, 则立刻打印处理后的行(pattern space+换行符),然后不管是否使用-n,都将pattern space 替换为下一行的内容,如果没有下一行了,则立刻结束,后续指令不操作.
N 追加换行符到pattern space,继续追加下一行的内容 ,如果没有下一行了,则立刻结束,后续指令不操作.
seq 1 3 | sed  'n;p'
执行结果
1
2
2
3
debug
SED PROGRAM:
n
p
INPUT:   'STDIN' line 1
PATTERN: 1
COMMAND: n
1
PATTERN: 2
COMMAND: p
2
END-OF-CYCLE:
2
INPUT:   'STDIN' line 3
PATTERN: 3
COMMAND: n
3
END-OF-CYCLE:
  1. 第6-7行,由于这里 不带-n,则立刻打印 pattern space,然后 将pattern space 替换为下一行
  2. 第8行显示为替换后的 pattern space
  3. 第9-10行 是p的操作, 打印了 pattern space
  4. 第13行说明直接读取了文件的第3行, 第15行的n 操作 发现没有下一行了.立刻结束了. 不做后续的p操作.
#  比如偶数行 做些操作
seq 6 | sed -n 'n;p'
# 匹配到的行后面第2行
seq 7 |sed -n '/3/{n;n;p}' # 显示5
# 将第3行和第3行 合成一行.
sed  '2N;s/\n/--/' example.txt
执行结果
HEllo cat
xx hello yyy hello--hot dog HELLO
debug
SED PROGRAM:
2 N
s/\n/--/
INPUT:   '2.txt' line 1
PATTERN: HEllo cat
COMMAND: 2 N
COMMAND: s/\n/--/
PATTERN: HEllo cat
END-OF-CYCLE:
HEllo cat
INPUT:   '2.txt' line 2
PATTERN: xx hello yyy hello
COMMAND: 2 N
PATTERN: xx hello yyy hello\nhot dog HELLO
COMMAND: s/\n/--/
MATCHED REGEX REGISTERS
regex[0] = 18-19 '
'
PATTERN: xx hello yyy hello--hot dog HELLO
END-OF-CYCLE:
xx hello yyy hello--hot dog HELLO

4.2.6 ; 和 {;}

;可以有多个

{}里还可以有{}

# = 表示打印匹配的行的行号
sed -n  '/hello/=;p' <<EOF
hello cat
hot dog
world hello
EOF
执行结果
1
hello cat
hot dog
3
world hello

/hello/=;p 对与匹配到hello的行执行=指令, p与前面的无关, 它会重新看该行是否匹配它自己的规则.

sed -n  '/hello/{=;p}' <<EOF
hello cat
hot dog
world hello
EOF
执行结果
1
hello cat
3
world hello

/hello/{=;p} 对于匹配到hello的行 执行 =指令,再执行p指令. {}里的指令是一个整体

# 多个操作 可以这样写 ,不用;号
sed  '/hello/{
/world/a  python
=
}' <<EOF
hello xx
hello world
golang
EOF

4.2.7 使用变量

pattern="he..o"
# 这样. pattern里的. 在下面还是表示 任意字符哦.
# 使用双引号
sed -n "s/$pattern/world/g" example.txt
#或者 单引号
sed -n 's/'$pattern'/world/g' example.txt
Back to top