细说Linux系统管理
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

3.2 字符截取和替换命令

在Linux中,文件的结构是在目录中保存文件或子目录,而在文件中保存字符串。因为Shell编程主要就是和字符串打交道,所以我们在这里介绍字符截取和替换命令。

其实grep也是一个字符串处理命令,它的作用是在文件中提取符合条件的行。但是因为知识点的需要,我们在前面已经讲解了grep命令,不过大家要知道grep命令属于字符串处理命令。

3.2.1 cut列提取命令

grep命令是在文件中提取符合条件的行,也就是分析一行的信息,如果行中包含需要的信息,就把该行提取出来。而如果要进行列提取,就要利用cut命令了。不过要小心,虽然cut命令用于提取符合条件的列,但是也要一行一行地进行数据读取。也就是说,先要读取文本的第一行数据,在此行中判断是否有符合条件的字段,然后再处理第二行数据。我们也可以把cut称为字段提取命令。命令格式如下:

    [root@localhost ~]# cut [选项] 文件名
    选项:
        -f列号:        提取第几列
        -d分隔符:      按照指定分隔符分割列
        -c字符范围:     不依赖分隔符来区分列,而是通过字符范围(行首为0)来进行字段
                        提取。“n-”表示从第n个字符到行尾;“n-m”表示从第n个字符到第m
                        个字符;“-m”表示从第1个字符到第m个字符

cut命令的默认分隔符是制表符,也就是Tab键,不过对空格符支持得不怎么好。我们先建立一个测试文件,然后看看cut命令的作用。

    [root@localhost ~]# vi student.txt
    ID     Name   gender  Mark
    1      Liming  M      86
    2      Sc     M      90
    3      Gao    M      83

建立学员成绩表,注意这张表中所有的分隔符都是制表符,不能是空格,否则后面的实验会出现问题。先看看cut命令该如何使用,命令如下:

    [root@localhost ~]# cut -f 2 student.txt
    #提取第二列的内容
    Name
    Liming
    Sc
    Gao

如果想要提取多列呢?将列号直接用“, ”隔开,命令如下:

    [root@localhost ~]# cut -f 2,3 student.txt
    #提取第二列和第三列的内容
    Name   gender
    Liming  M
    Sc     M
    Gao    M

cut命令可以按照字符进行提取。需要注意的是,“8-”代表提取所有行从第8个字符到行尾,而“10-20”代表提取所有行的第10~20个字符,而“-8”代表提取所有行从行首到第8个字符,命令如下:

    [root@localhost ~]# cut -c 8- student.txt
    #提取每行从第8个字符到行尾,好像很乱啊,那是因为每行的字符个数不相等
            gender  Mark
    g      M      86
    90
          83

当然,cut命令也可以手工指定分隔符。例如,我想看看当前Linux服务器中有哪些用户、这些用户的UID是什么,就可以这样操作:

    [root@localhost ~]# cut -d ":" -f 1,3 /etc/passwd
    #以“:”作为分隔符,提取/etc/passwd文件的第一列和第三列
    root:0
    bin:1
    daemon:2
    adm:3
    lp:4

cut命令很方便,不过最主要的问题是对空格识别得不好,很多命令的输出格式中都不是制表符,而是空格符,比如:

    [root@localhost ~]# df
    #统计分区使用状况
    文件系统                1K-块        已用          可用   已用%    挂载点
    /dev/sda3          19923216     1848936     17062212      10%      /
    tmpfs                 312672          0       312672      0%       /dev/shm
    /dev/sda1             198337     26359        161738      15%      /boot
    /dev/sr0             3626176    3626176            0      100%     /mnt/cdrom

如果想用cut命令截取第一列和第三列,就会出现这样的情况:

    [root@localhost ~]# df -h | cut -d " " -f 1,3
    文件系统
    /dev/sda3
    tmpfs
    /dev/sda1
    /dev/sr0

第三列去哪里了啊?其实因为df命令输出的分隔符不是制表符,而是多个空格符,所以cut命令会忠实地将每个空格符当作一个分隔符,而这样数,第三列刚好也是空格,所以输出才会是上面这种情况。总之,cut命令不能很好地识别空格符。如果想要以空格符作为分隔符,就要使用awk命令了。

3.2.2 awk编程

1.概述

本小节我们要讲awk编程,光看标题就觉得好大啊,awk已经不能被看成一条命令了,而是一种语言。awk编程用于在Linux/UNIX下对文本和数据进行处理。数据可以来自标准输入、一个或多个文件,或者其他命令的输出。它支持用户自定义函数和动态正则表达式等先进功能,是Linux/Unix下的一个强大的编程工具。它在命令行中使用,但更多的是作为脚本来使用的。awk处理文本和数据的方式是这样的:它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕);如果没有指定模式,则所有被操作所指定的行都将被处理。awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了贝尔实验室和GNU的一些扩展。下面介绍的awk是以GUN的gawk为例的,在Linux系统中已把awk链接到gawk为例,所以下面全部以gawk为例进行介绍。

awk有许多用途,包括从文件中提取数据、统计在文件内出现的次数及生成报告。由于awk的基本语法与C语言的相似,所以,假如你已经熟悉了C语言,就会了解awk的大部分用法。在许多方面,可以说awk是C语言的一种简易版本。如果你还不熟悉C语言,那么学习awk要比学习C语言容易一些。awk对于Linux环境是易适应的,awk含有预定义的变量,可自动实现许多编程任务,提供常规变量,支持C格式化输出。awk可以把Shell脚本和C编程的精华结合在一起。在awk内执行同一任务通常有许多不同的方法,应判断哪种方法最适合应用。awk会自动读取每个记录,把记录分成字段,并在需要时进行类型转换。变量使用的方式确定了它的类型,用户不必声明变量的任何类型。

2.printf格式化输出

printf是awk的重要格式化输出命令,我们需要先介绍一下printf命令如何使用。需要注意,在awk中可以识别print输出动作和printf输出动作(区别是:print会在每个输出之后自动加入一个换行符;而printf是标准格式输出命令,并不会自动加入换行符,如果需要换行,则需要手工加入换行符),但是在Bash中只能识别标准格式化输出命令printf。所以我们在本小节中介绍的是标准格式化输出命令printf。命令格式如下:

    [root@localhost ~]# printf ’输出类型输出格式’ 输出内容
    输出类型:
        %ns:        输出字符串。n是数字,指代输出几个字符
        %ni:        输出整数。n是数字,指代输出几个数字
        %m.nf:      输出浮点数。m和n是数字,指代输出的整数位数和小数位数。如%8.2f
                    代表共输出8位数,其中2位是小数,6位是整数
    输出格式:
        \a:         输出警告声音
        \b:         输出退格键,也就是Backspace键
        \f:         清除屏幕
        \n:         换行
        \r:         回车,也就是Enter键
        \t:         水平输出退格键,也就是Tab键
        \v:         垂直输出退格键,也就是Tab键

为了演示printf命令,我们需要修改一下刚刚cut命令使用的student.txt文件。文件内容如下:

    [root@localhost ~]# vi student.txt
    ID     Name     PHP    Linux   MySQL   Average
    1      Liming   82     95      86     87.66
    2      Sc       74     96     87     85.66
    3      Gao      99     83     93     91.66

我们使用printf命令输出这个文件的内容,如下:

    [root@localhost ~]# printf '%s' $(cat student.txt)
    IDNamegenderPHPLinuxMySQLAverage1LimingM82958687.662ScM74968785.663GaoM998393
    91.66[root@localhost ~]#

输出结果十分混乱。这就是printf命令,如果不指定输出格式,则会把所有输出内容连在一起输出。其实文本的输出本身就是这样的,cat等文本输出命令之所以可以按照格式漂亮地输出,那是因为cat命令已经设定了输出格式。那么,为了用printf输出合理的格式,应该这样做:

    [root@localhost ~]# printf '%s\t %s\t %s\t %s\t %s\t %s\t \n' $(cat student.txt)
    #注意:在printf命令的单引号中只能识别格式输出符号,而手工输入的空格是无效的
    ID      Name    PHP    Linux   MySQL   Average
    1       Liming  82     95     86     87.66
    2       Sc     74      96     87     85.66
    3       Gao     99     83     93     91.66

再强调一下:在printf命令的单引号中输入的任何空格都不会反映到格式输出中,只有格式输出符号才能影响printf命令的输出结果。

解释一下这个命令:因为我们的文档有6列,所以使用6个“%s”代表这6列字符串,每个字符串之间用“\t”分隔;最后还要加入“\n”,使得每行输出都换行,否则这些数据还是会连成一行的。

如果不想把成绩当成字符串输出,而是按照整型和浮点型输出,则要这样做:

    [root@localhost ~]# printf '%i\t %s\t %i\t %i\t %i\t %8.2f\t \n' \
    $(cat student.txt | grep -v Name)
    1       Liming  82     95     86        87.66
    2       Sc     74     96     87        85.66
    3       Gao     99     83     93        91.66

先解释“cat student.txt | grep -v Name”这条命令。这条命令会把第一行标题取消,剩余的内容才用printf格式化输出。在剩余的内容中,第1、3、4、5列为整型,所以用“%i”输出;而第2列是字符串,所以用“%s”输出;而第6列是小数,所以用“%8.2f”输出。“%8.2f”代表可以输出8位数,其中有2位是小数,有6位是整数。

printf命令是awk中重要的输出动作,不过awk中也能识别print动作,区别刚刚已经介绍了,当然稍后我们还会举例来说明一下这两个动作的区别。注意:在Bash中只有printf命令。另外,printf命令只能格式化输出具体数据,不能直接输出文件内容或使用管道符,所以printf命令的格式还是比较特殊的。

3.awk基本使用

awk命令的基本格式如下:

    [root@localhost ~]# awk ’条件1{动作1} 条件2{动作2}…' 文件名
    条件(Pattern):
        一般使用关系表达式作为条件。这些关系表达式非常多,具体参考表3-3。例如:
        x > 10  判断变量x是否大于10
        x == y  判断变量x是否等于变量y
        A ~ B   判断字符串A中是否包含能匹配B表达式的子字符串
        A ! ~ B  判断字符串A中是否不包含能匹配B表达式的子字符串
    动作(Action):
        格式化输出
        流程控制语句

我们先来学习awk的基本用法,也就是只看看格式化输出动作是干什么的。至于条件类型和流程控制语句,我们在后面再详细介绍。看看这个例子:

表3-3 awk支持的主要条件类型

    [root@localhost ~]# awk '{printf $2 "\t" $6 "\n"}' student.txt
    #输出第二列和第六列的内容
    Name   Average
    Liming  87.66
    Sc     85.66
    Gao    91.66

在这个例子中没有设定任何的条件类型,所以这个文件中的所有内容都符合条件,动作会无条件执行。动作是格式化输出printf,“$2”和“$6”分别代表第二个字段和第六个字段,所以这条awk命令会列出student.tx文件的第二个字段和第六个字段。本来printf命令中定义输出格式应该使用单引号,可以单引号被awk命令固定使用了,所以只能使用双引号。

虽然都是截取列的命令,但是awk命令比cut命令智能多了,cut命令是不能很好地识别空格作为分隔符的;而对于awk命令来说,只要分隔开,不管是空格还是制表符,都可以识别。比如刚刚截取df命令的结果时,cut命令已经力不从心了,我们来看看awk命令,命令如下:

    [root@localhost ~]# df -h | awk '{print $1 "\t" $3}'
    文件系统        已用
    /dev/sda3      1.8G
    tmpfs   0
    /dev/sda1      26M
    /dev/sr0       3.5G

在这两个例子中,我们分别使用了printf动作和print动作。发现了吗?如果使用printf动作,就必须在最后加入“\n”,因为printf只能识别标准输出格式;如果我们不使用“\n”,它就不会换行。而print动作则会在每次输出后自动换行,所以不用在最后加入“\n”。

4.awk的条件

我们来看看awk可以支持什么样的条件类型吧。awk支持的主要条件类型如表3-3所示。

1)BEGIN

BEGIN是awk的保留字,是一种特殊的条件类型。BEGIN的执行时机是“在awk程序一开始,尚未读取任何数据之前”。一旦BEGIN后的动作执行一次,当awk开始从文件中读入数据时,BEGIN的条件就不再成立,所以BEGIN定义的动作只能被执行一次。例如:

    [root@localhost ~]# awk 'BEGIN{printf "This is a transcript \n" }
    {printf $2 "\t" $6 "\n"}' student.txt
    #awk命令只要检测不到完整的单引号就不会执行,所以这条命令的换行不用加入“\”,就是一行命令
    #这里定义了两个动作
    #第一个动作使用BEGIN条件,所以会在读入文件数据前打印“这是一张成绩单”(只会执行一次)
    #第二个动作会打印文件的第二个字段和第六个字段
    This is a transcript
    Name   Average
    Liming  87.66
    Sc     85.66
    Gao    91.66

2)END

END也是awk的保留字,不过刚好和BEGIN相反。END是在awk程序处理完所有数据,即将结束时执行的。END后的动作只在程序结束时执行一次。例如:

    [root@localhost ~]# awk 'END{printf "The End \n" }
    {printf $2 "\t" $6 "\n"}' student.txt
    #在输出结尾输入“The End”,这并不是文档本身的内容,而且只会执行一次
    Name   Average
    Liming  87.66
    Sc     85.66
    Gao    91.66
    The End

3)关系运算符

举几个例子来看看关系运算符。假设我想看看平均成绩大于等于87分的学员是谁,就可以这样输入命令:

    例子1:
    [root@localhost ~]# cat student.txt | grep -v Name |  \
     awk '$6 >= 87 {printf $2 "\n" }'
    #使用cat输出文件内容,用grep取反包含“Name”的行
    #判断第六个字段(平均成绩)大于等于87分的行,如果判断式成立,则打印第六列(学员名)
    Liming
    Gao

在加入了条件之后,只有条件成立,动作才会执行;如果条件不满足,则动作不执行。通过这个实验,大家可以发现,虽然awk是列提取命令,但是也要按行来读入。这条命令的执行过程是这样的:

(1)如果有BEGIN条件,则先执行BEGIN定义的动作。

(2)如果没有BEGIN条件,则读入第一行,把第一行的数据依次赋予$0、$1、$2等变量。其中,$0代表此行的整体数据,$1代表第一个字段,$2代表第二个字段。

(3)依据条件类型判断动作是否执行。如果条件符合,则执行动作;否则读入下一行数据。如果没有条件,则每行都执行动作。

(4)读入下一行数据,重复执行以上步骤。

如果我想看看Sc用户的平均成绩呢?

    例子2:
    [root@localhost ~]#  awk '$2~ /Sc/ {printf $6 "\n"}' student.txt
    #如果第二个字段中包含“Sc”字符,则打印第六个字段
    85.66

这里要注意,在awk中,只有使用“//”包含的字符串,awk命令才会查找。也就是说,字符串必须用“//”包含,awk命令才能正确识别。

4)正则表达式

如果想让awk识别字符串,则必须使用“//”包含,例如:

    [root@localhost ~]# awk '/Liming/ {print}' student.txt
    #打印Liming的成绩
    1      Liming  82     95     86     87.66

当使用df命令查看分区的使用情况时,如果我只想查看真正的系统分区的使用情况,而不想查看光盘和临时分区的使用情况,则可以这样做:

    [root@localhost ~]# df -h | awk '/sda[0-9]/ {printf $1 "\t" $5 "\n"} '
    #查询包含“sda数字”的行,并打印第一个字段和第五个字段
    /dev/sda3      10%
    /dev/sda1      15%

5.awk内置变量

我们已经知道了,在awk中,“$1”代表第一个字段(列),“$2”代表第二个字段。而“$n”(n为数字)就是awk的内置变量,下面介绍一下awk中常见的内置变量,如表3-4所示。

表3-4 awk中常见的内置变量

刚刚我们使用awk命令都是使用任意空格(制表符或空格)作为分隔符的,如果我们要截取的数据不是用空格作为分隔符的,那又该怎么办呢?这时“FS”内置变量就该出场了,命令如下:

    [root@localhost ~]# cat /etc/passwd | grep "/bin/bash" | \
     awk '{FS=":"} {printf $1 "\t" $3 "\n"}'
    #查询可以登录的用户的用户名和UID
    root:x:0:0:root:/root:/bin/bash
    user1   501

这里“:”分隔符生效了,但是对第一行却没有起作用,原来我们忘记了“BEGIN”条件。再来试试:

    [root@localhost ~]# cat /etc/passwd | grep "/bin/bash" | \
    awk 'BEGIN {FS=":"} {printf $1 "\t" $3 "\n"}'
    root   0
    user1   501

这次的输出就没有任何问题了。

再来看看内置变量“NF”和“NR”是用来干什么的,命令如下:

    [root@localhost ~]# cat /etc/passwd | grep "/bin/bash" | \
    awk 'BEGIN {FS=":"} {printf $1 "\t" $3 "\t行号:" NR "\t字段数:" NF "\n"}'
    #解释一下awk命令
    #开始执行{分隔符是“:”} {输出第一个字段和第三个字段输出行号(NR值)字段数(NF值)}
    root    0       行号:1        字段数:7
    user1   501     行号:2        字段数:7

有点奇怪,root行确实是第一行,而user1行应该是/etc/passwd文件的最后一行,怎么能是第二行呢?那是因为grep命令把所有的伪用户都过滤了,传入awk命令的只有两行数据。这里为了便于理解,在输出的说明中使用了中文。如果想支持中文,则Linux必须安装中文字体,当然远程工具也要支持中文(Linux本机的纯字符界面是不能输入中文的)。如果你在做实验时不能输入中文,那只要明白是什么意思,换成英文也可以。

如果我只想看看sshd这个伪用户的相关信息,则可以这样使用:

    [root@localhost ~]# cat /etc/passwd | \
    awk'BEGIN{FS=":"}$1=="sshd"{printf$1"\t"$3"\t行号:"NR"\t字 段数:"NF"\n"}'
    #可以看到sshd伪用户的UID是74,是/etc/passwd文件的第28行,此行有7个字段
    sshd   74      行号:28       字段数:7

6.awk流程控制

之所以称为awk编程,是因为在awk中允许定义变量,允许使用运算符,允许使用流程控制语句和定义函数。这样就使得awk编程成了一门完整的程序语言,当然难度也比普通的命令要大得多。所有语言的流程控制都非常类似,稍后我们会详细地讲解Bash的流程控制。在这里只举一些例子,用来演示awk流程控制的作用。如果你现在看不懂这些例子,则可以等学习完Bash流程控制之后,回过头来学习。

我们再利用student.txt文件做一个练习,后面的使用比较复杂,我们再看看这个文件的内容,如下:

    [root@localhost ~]# cat student.txt
    ID     Name   PHP    Linux   MySQL   Average
    1      Liming  82     95     86     87.66
    2      Sc     74     96     87     85.66
    3      Gao    99     83     93     91.66

先来看看如何在awk中定义变量与调用变量的值。假设我想统计PHP成绩的总分,就应该这样做:

    [root@localhost ~]# awk 'NR==2{php1=$3}
    NR==3{php2=$3}
    NR==4{php3=$3; totle=php1+php2+php3; print "totle php is " totle}' student.txt
    #统计PHP成绩的总分
    totle php is 255

这条命令有点复杂了,我们解释一下。“NR==2{php1=$3}”(条件是NR==2,动作是php1=$3)是指如果输入数据是第二行(第一行是标题行),就把第二行的第三个字段的值赋予变量“php1”。“NR==3{php2=$3}”是指如果输入数据是第三行,就把第三行的第三个字段的值赋予变量“php2”。“NR==4{php3=$3; totle=php1+php2+php3; print "totle php is " totle}”(“NR==4”是条件,后面{}中的都是动作)是指如果输入数据是第四行,就把第四行的第三个字段的值赋予变量“php3”;然后定义变量totle的值是“php1+php2+php3”;最后输出“totle php is”关键字,后面加变量totle的值。

在awk编程中,因为命令语句非常长,所以在输入格式时需要注意以下内容:

· 多个条件{动作}可以用空格分隔,也可以用回车分隔。

· 在一个动作中,如果需要执行多条命令,则需要用“; ”分隔,或用回车分隔。

· 在awk中,变量的赋值与调用都不需要加入“$”符号。

· 在条件中判断两个值是否相同,请使用“==”,以便和变量赋值进行区分。

再看看如何实现流程控制。假设Linux成绩大于90分,就是一个好男人,命令如下:

    [root@localhost ~]# awk '{if (NR>=2)
    {if ($4>90) printf $2 " is a good man! \n"}}' student.txt
    #程序中有两个if判断,第一个判断行号大于2,第二个判断Linux成绩大于90分
    Liming is a good man!
    Sc is a good man!

其实在awk中,if判断语句完全可以直接利用awk自带的条件来取代,刚刚的脚本可以改写成这样:

    [root@localhost ~]# awk ' NR>=2 {test=$4}
    test>90 {printf $2 " is a good man! \n"}' student.txt
    #先判断行号,如果大于2,就把第四个字段的值赋予变量test
    #再判断成绩,如果test的值大于90分,就打印好男人
    Liming is a good man!
    Sc is a good man!

7.awk函数

awk编程也允许在编程时使用函数,下面我们讲讲awk的自定义函数。awk函数的定义方法如下:

    function函数名(参数列表){
    函数体
    }

我们定义一个简单的函数,使用该函数来打印student.txt的学员姓名和平均成绩。命令如下:

    [root@localhost ~]# awk 'function test(a, b) { printf a "\t" b "\n" }
    #定义函数test,包含两个参数,函数体的内容是输出这两个参数的值
    { test($2, $6) } ' student.txt
    #调用函数test,并向两个参数传递值
    Name   Average
    Liming  87.66
    Sc     85.66
    Gao    91.66

8.awk中的脚本调用

对于小的单行程序来说,将脚本作为命令行自变量传递给awk是非常简单的;而对于多行程序来说,就比较难处理了。当程序是多行的时候,使用外部脚本是很适合的。首先在外部文件中写好脚本,然后可以使用awk命令的-f选项,使其读入脚本并且执行。

例如,我们可以先编写一个awk脚本。

    [root@localhost ~]# vi pass.awk
    BEGIN   {FS=":"}
    { print  $1  "\t"  $3}

然后使用“-f”选项来调用这个脚本。

    [root@localhost ~]# awk -f pass.awk /etc/passwd
    root   0
    bin    1
    daemon  2
    …省略部分输出…

如果是一些较为复杂的awk语句,而且需要重复调用,那么把它放入脚本文件中是最为经济和方便的方法了。

3.2.3 sed命令

sed是一种几乎可以应用在所有UNIX平台(包括Linux)上的轻量级流编辑器。sed有许多很好的特性。首先,它相当小巧,通常要比你所喜爱的脚本语言小很多倍。其次,因为sed是一种流编辑器,所以,它可以对从如管道这样的标准输入中接收的数据进行编辑。因此,无须将要编辑的数据存储在磁盘上的文件中。因为可以轻易将数据管道输出到sed,所以,将sed用作强大的Shell脚本中长而复杂的管道很容易。

sed主要是用来将数据进行选取、替换、删除、新增的命令。我们看看命令的语法:

    [root@localhost ~]# sed [选项] '[动作]' 文件名
    选项:
        -n:               一般sed命令会把所有数据都输出到屏幕上。如果加入此选项,则只会
                          把经过sed命令处理的行输出到屏幕上
        -e:               允许对输入数据应用多条sed命令编辑
        -f脚本文件名:   从sed脚本中读入sed操作。和awk命令的-f选项非常类似
        -r:               在sed中支持扩展正则表达式
        -i:               用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出
    动作:
        a \:              追加,在当前行后添加一行或多行。当添加多行时,除最后一行外,
                          每行末尾需要用“\”代表数据未完结
        c \:              行替换,用c后面的字符串替换原数据行。当替换多行时,除最后一行
                          外,每行末尾需用“\”代表数据未完结
        i \:              插入,在当前行前插入一行或多行。当插入多行时,除最后一行外,
                          每行末尾需要用“\”代表数据未完结
        d:                删除,删除指定的行
        p:                打印,输出指定的行
        s:                字符串替换,用一个字符串替换另一个字符串。格式为“行范围s/
                          旧字串/新字串/g”(和Vim中的替换格式类似)

大家需要注意,sed所做的修改并不会直接改变文件的内容(如果是用管道符接收的命令的输出,则连文件都没有),而是把修改结果只显示到屏幕上,除非使用“-i”选项才会直接修改文件。

1.行数据操作

闲话少叙,直奔主题,我们举几个例子来看看sed命令到底是干什么的。假设我想查看一下student.txt文件的第二行,就可以利用“p”动作了。

    [root@localhost ~]# sed '2p' student.txt
    ID     Name    PHP    Linux   MySQL   Average
    1      Liming  82     95      86     87.66
    1      Liming  82     95      86     87.66
    2      Sc     74      96     87     85.66
    3      Gao     99     83     93     91.66

好像看着不怎么顺眼啊!“p”动作确实输出了第二行数据,但是sed命令还会把所有数据都输出一次,这时就会看到这个比较奇怪的结果。那如果我想指定输出某行数据,就需要“-n”选项的帮助了。

    [root@localhost ~]# sed -n '2p' student.txt
    1      Liming  82     95     86     87.66

这样才可以输出指定的行。大家可以这样记忆:当我们需要输出指定的行时,需要把“-n”选项和“p”动作一起使用。

再来看看如何删除文件中的数据。

    [root@localhost ~]# sed '2,4d' student.txt
    #删除从第二行到第四行的数据
    ID     Name    PHP    Linux   MySQL   Average
    [root@localhost ~]# cat student.txt
    #文件本身并没有被修改
    ID     Name    PHP    Linux   MySQL   Average
    1      Liming  82     95      86     87.66
    2      Sc     74      96     87     85.66
    3      Gao     99     83     93     91.66

看到这条命令首先需要注意,所有的动作必须使用“单引号”包含;其次,在动作中可以使用数字代表行号,逗号代表连续的行范围。还可以使用“$”代表最后一行,如果动作是“2, $d”,则代表从第二行删除到最后一行。

再来看看如何追加和插入行数据。

    [root@localhost ~]# sed '2a hello' student.txt
    #在第二行后加入hello
    ID     Name    PHP    Linux   MySQL   Average
    1      Liming  82     95      86     87.66
    hello
    2      Sc     74      96     87     85.66
    3      Gao     99     83     93     91.66

“a”动作会在指定行后追加数据。如果想要在指定行前插入数据,则需要使用“i”动作。

    [root@localhost ~]# sed '2i hello \
    > world' student.txt
    #在第二行前插入两行数据
    ID     Name    PHP    Linux   MySQL   Average
    hello
    world
    1      Liming  82     95      86     87.66
    2      Sc     74      96     87     85.66
    3      Gao     99     83     93     91.66

如果想追加或插入多行数据,那么,除最后一行外,每行的末尾都要加入“\”代表数据未完结。

再来看看“-n”选项的作用,命令如下:

    [root@localhost ~]# sed -n '2i hello \
    #只查看sed命令操作的数据
    world' student.txt
    hello
    world

看到了吧,“-n”只用于查看sed命令操作的数据,而并非查看所有的数据。

再来看看如何实现行数据替换。假设李明老师的成绩太好了,我实在是不想看到他的成绩刺激我,那我就可以这样做:

    [root@localhost ~]# cat student.txt | sed '2c No such person'
    ID     Name   PHP    Linux   MySQL   Average
    No such person
    2      Sc     74     96     87     85.66
    3      Gao    99     83     93     91.66

哈哈,第二行数据变成了“查无此人”,看着心情马上就好了起来。通过这个例子我们看到了,sed也可以接收和处理管道符传输的数据。

sed命令在默认情况是不会修改文件内容的。如果我确定需要让sed命令直接处理文件的内容,则可以使用“-i”选项。不过要小心,这样非常容易误操作,在操作系统文件时请小心谨慎。可以使用这样的命令:

    [root@localhost ~]# sed -i '2c No such person' student.txt

2.字符串替换

“c”动作是进行整行替换的,如果仅仅想替换行中的部分数据,就要使用“s”动作了。“s”动作的格式如下:

    [root@localhost ~]# sed 's/旧字符串/新字符串/g' 文件名

替换的格式和Vim非常类似。假设我觉得自己的PHP成绩太低了,想作弊改高一点,就可以这样做:

    [root@localhost ~]# sed '3s/74/99/g' student.txt
    #在第三行中,把74换成99
    ID     Name    PHP    Linux   MySQL   Average
    1      Liming  82     95      86     87.66
    2      Sc     99      96     87     85.66
    3      Gao     99     83     93     91.66

这样看起来就比较舒服了吧。如果我想把高老师的成绩注释掉,让它不再生效,则可以这样做:

    [root@localhost ~]# sed '4s/^/#/g' student.txt
    #这里使用正则表达式,“^”代表行首
    ID     Name    PHP    Linux   MySQL   Average
    1      Liming  82     95      86     87.66
    2      Sc     74     96      87     85.66
    #3     Gao     99     83     93      91.66

在sed中只能指定行范围,但是很遗憾,我在李明和高老师的中间,不能只把他们两个注释掉,那么我们可以这样做:

    [root@localhost ~]# sed -e 's/Liming//g ; s/Gao//g' student.txt
    #同时把“Liming”和“Gao”替换为空
    ID     Name   PHP    Linux   MySQL   Average
    1             82     95     86     87.66
    2      Sc     74     96     87     85.66
    3             99     83     93     91.66

“-e”选项可以同时执行多个sed动作,当然,如果只执行一个动作,则也可以使用“-e”选项,但是这时没有什么意义。还要注意,多个动作之间要用“; ”或回车分隔,例如,上一条命令也可以这样写:

    [root@localhost ~]# sed -e 's/Liming//g
    > s/Gao//g' student.txt
    ID     Name   PHP    Linux   MySQL   Average
    1             82     95     86     87.66
    2      Sc     74     96     87     85.66
    3             99     83     93     91.66

好了,李明老师和高老师的成绩被我折腾够了,关于sed命令我们就讲到这里吧。