31、sed 模拟其它命令

本章节应该是 sed 实质性内容的最终章了。

sed是一个非常神奇的程序,或者说一门非常神奇的语言。能够以多种方式解决问题。

这种简而美的小程序解决问题的方式,是 Unix 哲学的一部分。也是我们日常开发中应该学习的地方。

除了sed 外,GNU Linux 还提供了其它非常多的使用小程序来解决各种问题,比如 cat 用于显示文件内容,比如 head 用于显示文件头部的内容,比如 tail 用于显示文件结尾的内容。

这些功能,如果使用 sed 重写或者模仿,那也是相对很简单的。

当然了,我们并不是为了解决什么重写什么,只是当作练习而已,练习如何使用 sed

sed 模拟 cat 命令

cat 命令用于显示文件的内容,它的使用语法如下

cat [-benstuv] [file ...]

我们在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

然后使用 cat 命令显示文件 data.txt 的内容

[www.ddkk.com]$ cat data.txt
1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

如果要使用 sed 来模拟它,非常简单,只要

[www.ddkk.com]$ sed '' data.txt

即可

输出结果如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

sed 删除空行

如果要删除空行,可以使用 删除命令(d)

/^$/d

例如下面的范例

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站\n\n\nDDKK.COM 弟弟快看,程序员编程资料站" | sed '/^$/d'

运行结果如下

DDKK.COM 弟弟快看,程序员编程资料站
DDKK.COM 弟弟快看,程序员编程资料站

从结果中可以看出,所有的空行都已经被删除了。

sed 过滤空行

如果要过滤空行,直接将空行不输出即可,命令如下

/^$/!p

例如下面的范例

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站\n\n\nDDKK.COM 弟弟快看,程序员编程资料站" | sed -n '/^$/!p'

运行结果如下

DDKK.COM 弟弟快看,程序员编程资料站
DDKK.COM 弟弟快看,程序员编程资料站

从结果中可以看出,所有的空行都已经被过滤掉了。

sed 从指定的行开头删除指定的内容

如果从指定的行开头删除指定的内容,可以直接使用 替换命令(s)

s/^[要删除的内容]//g

假设当前目录下存在文件 data10.txt,内容如下

//include <iostream> 
using namespace std; 

int main(void) 
{ 
   // Displays message on stdout. 
   cout >> "Hello, World !!!" >> endl;  
   return 0; // Return success. 
}

我们想把第一行的注释去掉,也就是删掉 //

使用sed 来实现,则命令如下

[www.ddkk.com]$ sed 's|^//||g' data10.txt

运行结果如下

include <iostream> 
using namespace std; 

int main(void) 
{ 
   // Displays message on stdout. 
   cout >> "Hello, World !!!" >> endl;  
   return 0; // Return success. 
}

sed 在指定的行开头插入指定的内容

如果要在指定的行开头插入指定的内容,可以直接使用 替换命令(s)

s/^/[要插入的内容]/

假设当前目录下存在文件 data9.txt,内容如下

!/bin/bash 
pwd 
hostname 
uname -a 
who 
who -r 
lsb_release -a

我们想把前 4 行注释掉,也就是在前 4 行的开头插入 #

使用sed 来实现,则命令如下

[www.ddkk.com]$ sed '1,4 s/^/#/' data9.txt

运行结果如下

#!/bin/bash 
#pwd 
#hostname 
#uname -a 
who 
who -r 
lsb_release -a

wc -l 命令

wc 命令用于统计文件的总行数。

wc 命令的使用语法格式如下

wc [-clmw] [file ...]

我们在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

那么,使用 wc -l data.txt 则会输出文件的总行数

[www.ddkk.com]$ wc -l data.txt
   4 data.txt

如果要使用 sed 实现类似的统计文件行数的功能,那么命令如下

[www.ddkk.com]$ sed -n '$ =' data.txt

输出结果为

4

head 命令

head 命令用于显示文件的前 n 行,嗯,默认是 10 行。

head 命令的语法格式如下

head [-n lines | -c bytes] [file ...]

我们在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

那么,使用 head -n 2 data.txt 命令就会显示文件的前两行

[www.ddkk.com] head -n 2 data.txt
1) 小明,23岁,北京大学
2) 小红,22岁,清华大学

如果要使用 sed 实现相同的功能,那么命令为

[www.ddkk.com]$ sed '2 q' data.txt

输出结果为

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学

tail -1 命令

tail -1 命令用于显示文件的最后一行。

我们在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

那么,使用 tail -1 data.txt 命令就会显示文件的最后一行

[www.ddkk.com] tail -1 data.txt
4) 小王,22岁,清华大学

这个命令如果使用 sed 来重写,那么就是

[www.ddkk.com] $ sed -n '$p' data.txt

运行结果如下

4) 小王,22岁,清华大学

dos2unix 命令

在考古界 DOS 操作系统中,换行由 回车(\r)换行(\n) 组合而成,俗称 CR/LF。

而Unix/Linux 操作系统中,换行只是简单的 换行(\n)

两者的不同,诞生了几个常用,曾经常用的命令,其中之一就是 dos2unix。

dos2unix 命令用于将 DOS 中的新行 回车换行(\r\n) 替换成 Unix 中的 新行(\n)

不管是回车(\r) 还是 换行(\n) ,它们都是不可见字符。但 Unix/Linux 的很多程序都可以显示这些不可见字符,其中 回车(\r) 使用 ^M 显示,而 换行(\n) 则使用 $ 显示。

我们先在 data8.txt 中保存以下内容

ddkk.cn
www.ddkk.com

命令如下

[www.ddkk.com]$ echo -e "ddkk.cn\r\nwww.ddkk.com" > data8.txt

注意,一定要有那个 -e 不然 \r\n 就是四个字符了。

我们可以使用 file data8.txt 查看下文件的信息

[www.ddkk.com]$ file data8.txt
data8.txt: ASCII text, with CRLF, LF line terminators

如果要显示文件内容,那么可以使用 cat -A,因为这可以把所有不可见字符打印出来

[www.ddkk.com]$ cat -A data8.txt 
ddkk.cn^M$
www.ddkk.com$

^M 就是不可见字符 \r 。

如果使用 sed 重写,那么命令为

[www.ddkk.com]$ sed 's/^M$//'  data8.txt  > data8-1.txt 

注意 ^M 不是两个字符,而是 CTRL+V 和 CTRL+M 两个组合键生成的。

嗯,上面的命令,在苹果电脑自带的 cat 命令是无效的。

运行结果如下

我们使用 file 命令看看 data8-1.txt,输出结果如下

data8-1.txt: ASCII text

如果我们使用 cat -A 把所有不可见字符打印出来,那么结果就是

ddkk.cn$
www.ddkk.com$

unix2dos 命令

unix2dos 命令和 doc2unix 命令类似,不过作用却是反过来的。

unix2dos 命令的作用就是把 UNIX 的新行 \n 换成 DOS 的新行 \r\n。

我们先在 data7.txt 中保存以下内容

ddkk.cn
www.ddkk.com

命令如下

[www.ddkk.com]$ echo -e "ddkk.cn\nwww.ddkk.com" > data7.txt

注意,一定要有那个 -e 不然 \n 就是两个字符了。

我们可以使用 file data7.txt 查看下文件的信息

[www.ddkk.com]$ file data7.txt
data7.txt: ASCII text

如果使用 sed 重写,那么命令为

[www.ddkk.com]$ sed 's/$/\r/' data7.txt  > data7-1.txt 

嗯,上面的命令,在苹果电脑自带的 cat 命令是无效的。

运行结果如下

我们使用 file 命令看看 data7-1.txt,输出结果如下

[www.ddkk.com]$ file data7-1.txt
data7-1.txt: ASCII text, with CRLF line terminators

如果我们使用 cat -A 把所有不可见字符打印出来,那么结果就是

[www.ddkk.com]$ cat -A data7-1.txt 
ddkk.cn^M$
www.ddkk.com^M$

^M 就是不可见字符 \r 。

cat -E 命令

cat -E 命令会将在所有的换行符前面插入 **美元符号 $ **。

我们先在 data6.txt 中保存以下内容

DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站
www.ddkk.com

命令如下

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站\nwww.ddkk.com" > data6.txt

注意,一定要有那个 -e 不然 \t 就是两个字符了。

我们可以使用 cat data6.txt 查看下文件的内容

[www.ddkk.com]$ cat data6.txt
DDKK.COM 弟弟快看,程序员编程资料站    DDKK.COM 弟弟快看,程序员编程资料站
www.ddkk.com

如果我们使用了 -E 选项,则可以明显的在行尾看到 $

[www.ddkk.com]$ cat -e data6.txt
DDKK.COM 弟弟快看,程序员编程资料站    DDKK.COM 弟弟快看,程序员编程资料站$
www.ddkk.com$

嗯,上面的命令,在苹果电脑自带的 cat 命令是无效的。

如果使用 sed 重写,那么命令为

[www.ddkk.com]$ sed 's|$|&$|' data6.txt

运行结果如下

DDKK.COM 弟弟快看,程序员编程资料站    DDKK.COM 弟弟快看,程序员编程资料站$
www.ddkk.com$

cat -ET 命令

cat -ET 命令会将在所有的换行符前面插入 **美元符号 $ **,并把所有的制表符 \t 替换为 ^I。

我们先在 data6.txt 中保存以下内容

DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站
www.ddkk.com

命令如下

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站\nwww.ddkk.com" > data6.txt

注意,一定要有那个 -e 不然 \t 就是两个字符了。

我们可以使用 cat data6.txt 查看下文件的内容

[www.ddkk.com]$ cat data6.txt
DDKK.COM 弟弟快看,程序员编程资料站    DDKK.COM 弟弟快看,程序员编程资料站
www.ddkk.com

如果要区分是空格还是制表符,可以使用 -ET 选项

[www.ddkk.com]$ cat -ET data6.txt
DDKK.COM 弟弟快看,程序员编程资料站^IDDKK.COM 弟弟快看,程序员编程资料站$
www.ddkk.com$

嗯,上面的命令,在苹果电脑自带的 cat 命令是无效的。

如果使用 sed 重写,那么命令为

[www.ddkk.com]$ sed -n 'l' data6.txt | sed 'y/\\t/^I/'

运行结果如下

DDKK.COM 弟弟快看,程序员编程资料站^IDDKK.COM 弟弟快看,程序员编程资料站$
www.Iwle.cn$

nl 命令

nl 命令用于将文件中的所有行进行编号。

例如使用 nl data.txt 输出的内容如下

[www.ddkk.com]$ nl data.txt
     1  1) 小明,23岁,北京大学
     2  2) 小红,22岁,清华大学
     3  3) 小李,25岁,斯坦福大学
     4  4) 小王,22岁,清华大学

如果使用 sed 来重写 nl,那么命令为

[www.ddkk.com]$ sed = data.txt | sed 'N;s/\n/\t/'

输出结果如下

1   1) 小明,23岁,北京大学
2   2) 小红,22岁,清华大学
3   3) 小李,25岁,斯坦福大学
4   4) 小王,22岁,清华大学

嗯,上面的命令苹果电脑自带的 sed 是不支持的。

cp 命令

cp 命令用于制作文件的副本。例如我们可以使用 cp 命令制作 data.txt 的一个副本 data5.txt。

[www.ddkk.com]$ cp data.txt data5.txt

然后我们可以使用 diff 命令查看下 data5.txt 和 data.txt 文件的不同

[www.ddkk.com]$ diff data5.txt data.txt

输出结果如下

我们可以使用 echo $ ? 查看下有几行不一样

[www.ddkk.com] echo $?
0

简直一模一样有没有。

接下来我们使用 sed 来重写 cp 命令

[www.ddkk.com]$ sed -n 'w data5-1.txt' data.txt

然后我们可以使用 diff 命令查看下 data5-1.txt 和 data.txt 文件的不同

[www.ddkk.com]$ diff data5-1.txt data.txt

输出结果如下

我们可以使用 echo $ ? 查看下有几行不一样

[www.ddkk.com] echo $?
0

expand 命令

expand 命令用于将 制表符(\t) 转换为 空格()

制表符(\t)就是键盘 Q 键左边那个键生成的。

我们先在 data4.txt 中保存以下内容

DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站

命令如下

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站\tDDKK.COM 弟弟快看,程序员编程资料站" > data4.txt

注意,一定要有那个 -e 不然 \t 就是两个字符了。

我们可以使用 cat data4.txt 查看下文件的内容

[www.ddkk.com]$ cat data4.txt
DDKK.COM 弟弟快看,程序员编程资料站    DDKK.COM 弟弟快看,程序员编程资料站

接下来我们使用 expand 命令将内容中的所有制表符都替换成了空格符

[www.ddkk.com]$ expand data4.txt > data4-1.txt

然后我们使用 diff 命令查看两个文件 data4.txt 和 data4-1.txt 的内容

[www.ddkk.com]$ diff data4.txt data4-1.txt

输出结果如下

1c1
< DDKK.COM 弟弟快看,程序员编程资料站  DDKK.COM 弟弟快看,程序员编程资料站
---
> DDKK.COM 弟弟快看,程序员编程资料站        DDKK.COM 弟弟快看,程序员编程资料站

哦,对了,忘记说了,expand 命令默认会把一个 \t 转换为 4/8 个空格
具体的空格数量取决于你电脑上的配置
不信的话你可以打开 data4-1.txt 文件数一数空格的数量

如果使用 sed 命令来重新实现,则命令为

[www.ddkk.com]$ sed 's/\t/        /g' data4.txt > data4-2.txt

注意: 苹果电脑自带的 sed 不支持该命令

然后我们可以使用 diff 命令查看下 data4-1.txt 和 data4-2.txt 文件的不同

[www.ddkk.com]$ diff data4-1.txt data4-2.txt

输出结果如下

我们可以使用 echo $ ? 查看下有几行不一样

[www.ddkk.com] echo $?
0

tee 命令

tee 命令可以将内容输出到文件的同时输出到标准输出。也就是可以实现同时输出到文件和标准输出。

例如下面的 tee 命令,把 DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站 输出到文件 data3.txt 的同时还会输出到控制台

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站" | tee data3.txt

运行结果如下

DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站

同时打开 data3.txt 可以看到内容如下

[www.ddkk.com]$ cat data3.txt
DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站

如果使用 sed 来实现,可以直接使用 w 命令

[www.ddkk.com]$ echo -e "DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站" | sed -n 'p; w data3.txt'

对了,运行命令前我们先需要删除 data3.txt,不然不知道是谁创建的,哈哈。

[www.ddkk.com]$ rm data3.txt

然后运行刚刚的命令,输出结果如下

DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站

同时打开 data3.txt 可以看到内容如下

[www.ddkk.com]$ cat data3.txt
DDKK.COM 弟弟快看,程序员编程资料站,DDKK.COM 弟弟快看,程序员编程资料站

cat -s 命令

cat -s 命令用于将多个连续的空行折叠为一个空行并输出。

我们现在当前目录下新建一个文件 data2.txt,内容如下

1) 小明,23岁,北京大学

2) 小红,22岁,清华大学

3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

我们使用 cat -s 命令输出文件 data2.txt 的内容

[www.ddkk.com]$ cat -s data2.txt

输出结果如下

1) 小明,23岁,北京大学

2) 小红,22岁,清华大学

3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大

从输出结果中可以可以看到,多个空行只会输出一个空行

如果使用 sed 来实现,那么就有点复杂了

[www.ddkk.com]$ sed '1s/^$//p;/./,/^$/!d' data2.txt

运行结果如下

1) 小明,23岁,北京大学

2) 小红,22岁,清华大学

3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

grep 命令

默认情况下,grep 命令会在匹配成功时输出 匹配成功的行

grep 命令的使用语法如下

grep [-abcDEFGHhIiJLlmnOoqRSsUVvwxZ] [-A num] [-B num] [-C[num]]
    [-e pattern] [-f file] [--binary-files=value] [--color=when]
    [--context[=num]] [--directories=action] [--label] [--line-buffered]
    [--null] [pattern] [file ...]

我们现在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

当我们使用 grep -v 匹配 小明 时的结果如下

[www.ddkk.com]$ grep "小明" data.txt

输出结果如下

1) 小明,23岁,北京大学

如果使用 sed 来实现,可以在匹配成功后直接使用 p 命令输出

[www.ddkk.com]$ sed -n '/小明/p' data.txt

运行结果如下

1) 小明,23岁,北京大学

grep -v 命令

默认情况下,grep -v 命令会在匹配失败的时候输出 匹配失败的行

我们现在当前目录下新建一个文件 data.txt,内容如下

1) 小明,23岁,北京大学
2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

当我们使用 grep -v 匹配 小明 时的结果如下

[www.ddkk.com]$ grep -v "小明" data.txt

输出结果如下

2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

如果使用 sed 来实现,那么要用到 ! 运算符,在匹配失败的时候使用 p 命令输出

[www.ddkk.com]$ sed -n '/小明/!p' data.txt

运行结果如下

2) 小红,22岁,清华大学
3) 小李,25岁,斯坦福大学
4) 小王,22岁,清华大学

tr 命令

tr 命令用于转换字符。

tr 命令的语法如下

tr [-Ccsu] string1 string2
tr [-Ccsu] string1 string2
tr [-Ccu] -d string1
tr [-Ccu] -s string1
tr [-Ccu] -ds string1 string2

tr 命令的转换是一个一个字母对应转换的。因此两个字符串 string 和 string2 的字符数最好要想等

例如将字母 ABC 转换为 def 的命令如下

[www.ddkk.com]$ echo "ABC" | tr "ABC" "def"

输出结果为

def

如果使用 sed 来实现,那么就要使用 y 命令

[www.ddkk.com]$ echo "ABC" | sed 'y/ABC/def/'

输出结果如下

def