sed 有两个内置的存储空间:
命令 g(get)把保持空间的内容复制到模式空间。假定当前模式空间内容为“line 1”,保持空间内容为“line 2”,执行命令 g 之后,模式空间内容变为“line 2”,保持空间内容仍然为“line 2”。
示例文本 empnametitle.txt 的内容如下:
John Doe
CEO
Jason Smith
IT Manager
Raj Reddy
Sysadmin
Anand Ram
Developer
Jane Miller
Sales Manager
在这个文件中,每个员工的姓名和职位位于连续的两行内。下面的命令打印管理者的名称。
#sed -n -e '/Manager/!h' -e '/Manager/{g;p}' empnametitle.txt
Jason Smith
Jane Miller
#
本例中:
完整的执行流程如下表所示。
循环次数 | 模式空间 | 保持空间 | 操作 |
---|---|---|---|
1 | John Doe John Doe 空 | \n John Doe John Doe | h => |
2 | CEO CEO 空 | John Doe CEO CEO | h => |
3 | Jason Smith Jason Smith 空 | CEO Jason Smith Jason Smith | h => |
4 | IT Manager IT Manager Jason Smith\nIT Manager 空 | Jason Smith Jason Smith\nIT Manager IT Manager IT Manager | H => x => p Jason Smith\nIT Manager => |
5 | Raj Reddy Raj Reddy 空 | IT Manager Raj Reddy Raj Reddy | h => |
6 | Sysadmin Sysadmin 空 | Raj Reddy Sysadmin Sysadmin | h => |
7 | Anand Ram Anand Ram 空 | Sysadmin Anand Ram Anand Ram | h => |
8 | Developer Developer 空 | Anand Ram Developer Developer | h => |
9 | Jane Miller Jane Miller 空 | Developer Jane Miller Jane Miller | h => |
10 | Sales Manager Sales Manager Jane Miller\n Sales Manager | Jane Miller Jane Miller \nSales Manager Sales Manager | H => x => p Jane Miller\n Sales Manager |
也可以把命令保存到 sed 脚本中执行:
创建内容如下的脚本文件 g.sed
#!/bin/sed -nf
/Manager/!h
/Manager/{g;p}
修改脚本文件的模式为可执行
chmod u+x g.sed
执行脚本
#./g.sed empnametitle.txt
Jason Smith
Jane Miller
#
大写 G 命令把当前保持空间的内容作为新行追加到模式空间中。模式空间的内容不会被覆盖,该命令在模式空间后面加上换行符 \n,然后把保持空间内容追加进去。G 和 g 的用法类似于 H 和 h,小写命令替换原来的内容,大写命令追加原来的内容。
假定当前模式空间内容为“line 1”,保持空间内容为“line 2”,命令 G 执行后,模式空间内容变为“line 1\nline 2”,同时保持空间内容不变,仍然为“line 2”。
#echo -e "line1\nline2\nline3" | sed 'G'
line1
line2
line3
#
示例文本 empnametitle.txt 的内容如下:
John Doe
CEO
Jason Smith
IT Manager
Raj Reddy
Sysadmin
Anand Ram
Developer
Jane Miller
Sales Manager
在这个文件中,每个员工的姓名和职位位于连续的两行内。下面的命令在同一行上打印以冒号分割的管理者的名称和职位。
#sed -n -e '/Manager/!h' -e '/Manager/{x;G;s/\n/:/;p}' empnametitle.txt
Jason Smith:IT Manager
Jane Miller:Sales Manager
#
本例中:
完整的执行流程如下表所示。
循环次数 | 模式空间 | 保持空间 | 操作 |
---|---|---|---|
1 | John Doe John Doe 空 | \n John Doe John Doe | h => |
2 | CEO CEO 空 | John Doe CEO CEO | h => |
3 | Jason Smith Jason Smith 空 | CEO Jason Smith Jason Smith | h => |
4 | IT Manager Jason Smith Jason Smith\nIT Manager Jason Smith:IT Manager 空 | Jason Smith IT Manager IT Manager IT Manager IT Manager | x => G => s/\n/:/ => p Jason Smith:IT Manager => |
5 | Raj Reddy Raj Reddy 空 | IT Manager Raj Reddy Raj Reddy | h => |
6 | Sysadmin Sysadmin 空 | Raj Reddy Sysadmin Sysadmin | h => |
7 | Anand Ram Anand Ram 空 | Sysadmin Anand Ram Anand Ram | h => |
8 | Developer Developer 空 | Anand Ram Developer Developer | h => |
9 | Jane Miller Jane Miller 空 | Developer Jane Miller Jane Miller | h => |
10 | Sales Manager Jane Miller Jane Miller\nSales Manager Jane Miller:Sales Manager | Jane Miller Sales Manager Sales Manager Sales Manager | x => G => s/\n/:/ => p Jane Miller:Sales Manager |
注意:如果舍去命令 x,即使用 /Manager/{G;s/\n/:/;p},那么结果会由“雇员职位: 雇员名称”变成”雇员名称: 雇员职位”。
也可把上述命令写到 sed 脚本中然后执行:
创建内容如下的脚本文件 G-upper.sed
#!/bin/sed -nf
/Manager/!h
/Manager/{x;G;s/\n/:/;p}
修改脚本文件的模式为可执行
chmod u+x G-upper.sed
执行脚本
#./G-upper.sed empnametitle.txt
Jason Smith:IT Manager
Jane Miller:Sales Manager
#
cat -n 可以加行号,tac 可以按行反转输出,例如:
#echo -e "line1\nline2\nline3\nline4\nline5" | cat -n | tac
5 line5
4 line4
3 line3
2 line2
1 line1
#
用 sed 也可以达到相同的效果:
#echo -e "line1\nline2\nline3\nline4\nline5" | sed '=' | sed 'N;s/\n/ /g;s/^/ /g;' | sed -n '1!G;h;$p'
5 line5
4 line4
3 line3
2 line2
1 line1
#
第一个 sed 命令用于在每行前面加行号:
#echo -e "line1\nline2\nline3\nline4\nline5" | sed '='
1
line1
2
line2
3
line3
4
line4
5
line5
#
第二个 sed 命令将行号与正文拼成一行,并对标 cat -n 的输出添加相应的空格:
#echo -e "line1\nline2\nline3\nline4\nline5" | sed '=' | sed 'N;s/\n/ /g;s/^/ /g;'
1 line1
2 line2
3 line3
4 line4
5 line5
#
N 命令将下一行添加到模式空间中,结果是当前读入行和用 N 命令添加的下一行拼成“一行”:
1\nline1
2\nline2
3\nline3
4\nline4
5\nline5
s/\n/ /g;s/^/ /g; 命令将 \n 替换成两个空格,并在行头添加四个空格,为的是让输出和 cat -n 完全一样。
最后的 sed -n '1!G;h;$p' 命令模拟 tac 反转输出行,这个命令的工作原理是:
完整的执行流程如下表所示(为简化演示,没显示行头的空格)。
循环次数 | 模式空间 | 保持空间 | 操作 |
---|---|---|---|
1 | 1 line1 1 line1 空 | \n 1 line1 1 line1 | h => |
2 | 2 line2 2 line2\n1 line1 2 line2\n1 line1 空 | 1 line1 1 line1 2 line2\n1 line1 2 line2\n1 line1 | G => h => |
3 | 3 line3 3 line3\n2 line2\n1 line1 3 line3\n2 line2\n1 line1 空 | 2 line2\n1 line1 2 line2\n1 line1 3 line3\n2 line2\n1 line1 3 line3\n2 line2\n1 line1 | G => h => |
4 | 4 line4 4 line4\n3 line3\n2 line2\n1 line1 4 line4\n3 line3\n2 line2\n1 line1 空 | 3 line3\n2 line2\n1 line1 3 line3\n2 line2\n1 line1 4 line4\n3 line3\n2 line2\n1 line1 4 line4\n3 line3\n2 line2\n1 line1 | G => h => |
5 | 5 line5 5 line5\n4 line4\n3 line3\n2 line2\n1 line1 5 line5\n4 line4\n3 line3\n2 line2\n1 line1 | 4 line4\n3 line3\n2 line2\n1 line1 4 line4\n3 line3\n2 line2\n1 line1 5 line5\n4 line4\n3 line3\n2 line2\n1 line1 | G => h => p 5 line5\n4 line4\n3 line3\n2 line2\n1 line1 |
但是需要注意,这种方法实际上是在文件处理完成后才输出反转的内容,而不是在读取文件时逐行反转。对于真正的逐行反向输出,应该考虑使用 tac 或者编写一个小的脚本(如使用 awk、perl 或 bash)来实现。例如,使用 awk 实现逐行反向输出的脚本可能如下所示:
awk '{lines[NR] = $0} END {for (i=NR; i>0; i--) print lines[i]}' filename
这个 awk 脚本将文件的每一行存储在数组 lines 中,然后在文件处理完成后,从数组的末尾开始向前遍历并打印每一行,从而实现反向输出的效果。