在 shell 编程中,常需要处理文本,这里介绍几个文本处理命令。

一、grep 命令

grep 命令由来已久,用 grep 命令来查找 文本十分方便。在 POSIX 系统上,grep 可以在两种正则表达式风格中选择一种(BRE 和 ERE),或是执行简单的字符串匹配。传统上,有三种程序可以用来查找整个文本文件:

1)grep:最早的文本匹配程序。使用 POSIX 标准定义的基本正则表达(Basic Regular Expression,BRE);

2)egrep:扩展 grep。使用扩展正则表达式(Extended Regular Expression,ERE);

3)fgrep:快速 grep。匹配固定字符串而非正则表达式,它使用优化的算法,能更有效地匹配固定字符串。

  在目前的 POSIX 标准中,这三个程序已经被整合成为一个程序 grep,通过对 grep 命令加以不同的选项进行选择控制。

  grep 命令由一个选项、一个要匹配的模式和要搜索的文件组成,语法如下:

grep [options] PATTERN [FILES]

  如果没有提供文件名,则 grep 命令将搜索标准输入。grep 命令将会根据所提供的模式对文件进行匹配,发现匹配查找模式的行时,将该行显示出来。当 grep 命令同时搜索多个文件时,将会在搜索结果每一行前面加上文件名与一个冒号。grep 命令的主要选项如下:

选项 功能
-E 使用扩展正则表达式进行匹配(取代传统的 egrep 命令)
-F 使用固定字符串进行匹配(取代传统的 fgrep 命令)
-c   输出匹配行的数目,而不是输出匹配的行
-h 取消每个输出行的普通前缀,即匹配查询模式的文件名
-i 忽略大小写
-l 只列出包含匹配行的文件名,而不输出真正的匹配行
-v 对匹配模式取反,即搜索匹配不到的行
-n 输出行号

  用例子演示一下:

[tongye@localhost ~]$ grep -ni ROOT /etc/passwd
:root:x:::root:/root:/bin/bash
:operator:x:::operator:/root:/sbin/nologin

  该命令将会在 /etc/passwd 中查找有 root 的行,并将该行显示出来, -n 选项输出行号,-i 选项忽略大小写。

二、sed 命令

  sed( stream editor 流编辑器) ,可以用来在管道或者命令序列中编辑数据。sed 的语法如下:

sed option command file

  其中,command 是命令部分,用来指示 sed 该执行何种操作,file 则是 sed 命令将要操作的对象,通常是一个文件,如果没有文件,则使用标准输入。option 是 sed 命令可以使用的选项,主要有三个选项: -n、-e、-f,在后面再介绍。

  sed 命令读取每一个文件,一次读一行,将读取的行放到内存的一个区域--称为模式空间(pattern space),所有编辑上的操作都会应用到模式空间的内容。当所有操作完成后,sed 命令会将模式空间的最后内容打印到标准输出,再回到开始处,读取另一个输入行。为了演示 sed 命令,笔者写了一小段文本 test.txt 用作试验的素材(英语差,语法问题请忽略)

hello,my name is tongye
I want to write a program named HelloWorld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by tongye
end

2.1  使用 s 参数执行替换操作

  sed 命令一个常用的功能是进行替换操作,sed 替换操作的一般格式如下:

sed 's/string1/string2/' file    # 将文件中每行的第一个 string1 替换成 string2

  在上述语句中,参数 s 表示这是一个替换操作,/ 字符是界定符,用于分隔正则表达式与替代文本。界定符可以是任何可显示的字符,但是 / 字符是最常用的界定符。另外,在处理文件名称时,一般使用分号、冒号或逗号作为界定符。string1 是被替换的文本,可以是正则表达式;string2 是替换文本。

[tongye@localhost Shell_Program]$ sed 's/tongye/ttyezi/' test.txt
hello,my name is ttyezi
I want to write a program named HelloWorld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by ttyezi
end

  需要注意的是,上述语句只能替换第一个匹配到的文本,若想要将每一个匹配到的文本都替换掉,需要在结尾加上 g 参数(global),即:

sed 's/string1/string2/g' file    # 将文件中所有的 string1 都替换成 string2

  如果需要删除文本中的一个字符串,可以在替换文本处不放入任何文本(即空)来实现,如下:

sed 's/string1//g' file               # 删除文件中所有的 string1

2.2 使用 -e 选项和 -f 选项同时执行多个编辑命令

  当 sed 后面需要同时接多个编辑命令的时候,需要使用 -e 选项。每一个编辑命令都使用一个 -e 选项,如:

[tongye@localhost Shell_Program]$ sed -e 's/tongye/ttyezi/g' -e 's/HelloWorld/helloworld/g' test.txt
hello,my name is ttyezi
I want to write a program named helloworld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by ttyezi
end

  当需要编辑的项目很多时,如果把每一个编辑命令都接到 sed 后面,无疑会让代码很复杂,不易阅读且容易出错。这时,可以将所有的编辑命令都写进一个脚本,再使用 sed 搭配 -f 选项来操作:

# substitute.sed 存放着编辑命令
s/tongye/ttyezi/g
s/HelloWorld/helloworld/g
s;^\(.\).*\$;The first letter of this line is the same as its last letter; [tongye@localhost Shell_Program]$ sed -f substitute.sed test.txt
hello,my name is ttyezi
I want to write a program named helloworld.c
The first letter of this line is the same as its last letter
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by ttyezi
end

2.3 使用 -n 选项与 p 参数打印特定的行

  sed 默认情况下会把输入的每一行都打印到标准输出上,如果不想输出所有行,可以使用 -n 选项。使用了 -n 选项的 sed 命令将不打印任何行, -n 选项通常与参数 p 配合起来使用,p 参数可以让 sed 命令打印出符合指定范围或模式的所有行:

[tongye@localhost Shell_Program]$ sed -n 's/tongye/ttyezi/p' test.txt
hello,my name is ttyezi
writed by ttyezi

  该语句将文件中的 tongye 替换成 ttyezi,并且只打印发生替换操作的两行;

[tongye@localhost Shell_Program]$ sed -n '1,3p' test.txt        # 打印 1 到 3 行
hello,my name is tongye
I want to write a program named HelloWorld.c
now,let`s begin [tongye@localhost Shell_Program]$ sed -n '4p' test.txt       # 只打印第 4 行
#include "stdio.h"

  使用 sed -n '1,3p' 来只打印文件的前三行,注意这里的 '1,3' 表示的是一个范围;

[tongye@localhost Shell_Program]$ sed -n '/HelloWorld/p' test.txt
I want to write a program named HelloWorld.c

  该命令只打印包含 HelloWorld 的行。

2.4 使用 d 参数执行删除操作

  要删除某一个特定的行,可以使用参数 d,只需要指定行号或者行范围,就可以从输入中删除指定的行:

[tongye@localhost Shell_Program]$ sed '1,4d' test.txt      # 删除第 1 到 4 行,然后将剩余的行打印到标准输出
main(){
printf("Hello world");
}
oh,it`s symple
writed by tongye
end

  sed 也可以使用参数 d 来删除符合匹配模式的行:

[tongye@localhost Shell_Program]$ sed '/tongye/d' test.txt     # 删除所有含有 tongye 的行
I want to write a program named HelloWorld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
end

2.5 使用 -i 选项或输出重定向来保存 sed 编辑的内容

  在上面的所有操作中,我们发现虽然 sed 操作确实完成了,输出到标准输出上的文本内容确实发生了变化,但是如果我们再次打开所编辑的文件,会发现文件内容并没有发生更改。如果需要保存 sed 编辑的内容,可以使用 -i 选项:

[tongye@localhost Shell_Program]$ sed -i 's/tongye/ttyezi/' test.txt
[tongye@localhost Shell_Program]$ cat test.txt
hello,my name is ttyezi
I want to write a program named HelloWorld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by ttyezi
end

  也可以使用输出重定向的方式,将编辑后的内容保存到一个新的文本文件中而不是输出到标准输出:

[tongye@localhost Shell_Program]$ sed 's/hello/HELLO/' test.txt > test1.txt
[tongye@localhost Shell_Program]$ cat test1.txt
HELLO,my name is ttyezi
I want to write a program named HelloWorld.c
now,let`s begin
#include "stdio.h"
main(){
printf("Hello world");
}
oh,it`s symple
writed by ttyezi
end

三、awk 命令

3.1 awk 的基本模式与操作 

  shell 中提供 awk 命令来重新编排字段。实际上, awk 本身所提供的功能十分完备,已经是一种很好用的程序语言了,这里暂且只讨论它在 shell 脚本中的一些长处:文本处理功能。awk 命令的基本模式如下:

awk 'program' [ file ...]

  awk 读取命令行上所指定的各个文件(若没有文件,则为标准输入),一次读取一条记录(行),再针对每一行,应用 program 所指定的命令。 awk程序(program)基本架构为:

pattern {action}
pattern {action}
...

  pattern 部分几乎可以是任何表达式,但是在单命令行程序里,它通常是由斜杠括起来的 ERE。 action 为任意的 awk 语句,但是在单命令行程序里,通常是一个直接明了的 print 语句。pattern 或 action 都可以省略(不要全部省略)。省略 pattern,则会对每一条输入记录执行 action;省略 action 则默认 action 为{ print } ,将打印显示整条记录。

[tongye@localhost etc]$ awk '/root/ {print}' /etc/passwd
root:x:::root:/root:/bin/bash
operator:x:::operator:/root:/sbin/nologin

  上述指令将打印文本中所有包含 root 的行。  

3.2 字段

  awk 设计的重点就在字段与记录上:awk 读取输入记录(通常是一些行),然后自动将各个记录切分为字段。awk 将每条记录内的字段数目,存储到内建变量 NF 中。awk 默认以空白分隔字段(如空格符、制表符),不过,也可以设置成其他的,通过设置 FS 变量(列数据分隔符)。如果需要使用字段值,可以使用 $ 字符来引用,$1表示第一个字段值、$2表示第二个字段值、...  另外,$0表示整条记录。举个例子验证一下:

[tongye@localhost Shell_Program]$ awk '{print $1}' test.txt
hello,my
I
now,let`s
#include
main(){
printf("Hello
}
oh,it`s
writed
end

  这句指令将会按照默认的分隔符(空白符)来对字段进行划分,并打印第一个字段的值到标准输出。

3.3 设置字段分隔符

  可以使用 -F 选项来修改字段分隔符。-F 选项会自动的设置 FS 变量,只需将 FS 变量放到 -F 选项后面即可。FS 变量可以被设置为单个字符(此时,只要该字符出现一次,就分隔出一个字段),也可以被设置为一个完整的 ERE(此时,每一个匹配该 ERE 的文本都将被视为字段分隔符):

awk -F: '{print $1,$2}' /etc/passwd

  这段指令将使用冒号 : 作为字段分隔符去处理 /etc/passwd 文件,然后输出文件的第1、第2个字段到标准输出。

  -F 选项设置的字段分隔符是相对于输入而言的。而 awk 的输入、输出分隔符用法是分开的,因此即使使用 -F 选项设置了 FS 变量,输出的字段分隔符还是默认的空白符,这样可能会影响结果判断,使用 -v 选项可以设置输出字段分隔符,通过改变 OFS 变量(列数据输出分隔符)的值,使用形式也 -F 选项有所区别:

awk -v 'OFS=/' '{print $1,$2}' /etc/passwd

  -v 选项的用法如上,该指令的将把 awk 的输出与分隔符设置为斜杠符 / 。举一个例子:

[tongye@localhost Shell_Program]$ awk -F '/..' -v 'OFS=/' '{ print $1,$2,$3 }' /etc/passwd
root:x:::root:/ot:/n
bin:x:::bin:/n:/in
daemon:x:::daemon:/in:/in
adm:x:::adm:/r/m:
lp:x:::lp:/r/ool
sync:x:::sync:/in:/n
shutdown:x:::shutdown:/in:/in
halt:x:::halt:/in:/in
mail:x:::mail:/r/ool
operator:x:::operator:/ot:/in
games:x:::games:/r/mes:
ftp:x:::FTP User:/r/p:
nobody:x:::Nobody:/sbin/login
systemd-network:x:::systemd Network Management:/sbin/login
dbus:x:::System message bus:/sbin/login
polkitd:x:::User for polkitd:/sbin/login
tss:x:::Account used by the trousers package to sandbox the tcsd daemon:/v/ll:
abrt:x::::/c/rt:
sshd:x:::Privilege-separated SSH:/r/pty
postfix:x::::/r/ool
chrony:x::::/r/b
tongye:x:::tongye:/me/ngye:

  这段指令使用正则表达式 /.. 作为段分隔符去处理 /etc/passwd 文件,文件中每个匹配该正则表达式的文本都被视为一个字段分隔符。然后使用斜杠符 / 作为输出字段分隔符,并将第1、2、3个字段输出到标准输出。

3.4 打印行 print 与 printf

  print 上面已经用到过,这是 awk 里面最常使用的一条语句,可以用来进行简单的打印工作。print 的参数可以是字段列表、变量或者字符串:

[tongye@localhost Shell_Program]$ awk -F: -v 'OFS=:' '{print "username is",$1}' /etc/passwd

  print 命令将后面的参数一个一个打印到标准输出,如果没有后接参数,则默认参数为 $0,将打印整条记录。注意,print 的参数之间需要用逗号隔开,否则输出结果将会连到一起没有间隔。

  对于上面的语句,print 后面的参数混合了字符串和变量,当参数较多时,写起来会比较不方便。此时,可以使用 printf 语句来替代 print 语句。printf 语句可以将所有参数放到一对双引号中去,与 C 中的 printf 用法类似:

[tongye@localhost Shell_Program]$ awk -F: '{printf "username is %s\n",$1}' /etc/passwd
username is root
username is bin
username is daemon
username is adm
username is lp
username is sync
username is shutdown
username is halt
username is mail
username is operator
username is games
username is ftp
username is nobody
username is systemd-network
username is dbus
username is polkitd
username is tss
username is abrt
username is sshd
username is postfix
username is chrony
username is tongye

  需要注意的是,awk 的 print 语句会自动提供换行符,而 printf 语句不能,需要自己提供 \n 来进行换行。

参考资料:

《Linux 程序设计 第四版》

《Shell 脚本学习指南》

《UNIX/Linux/OS X 中的 Shell 编程 第四版》

shell基础 -- grep、sed、awk命令简介的更多相关文章

  1. Shell学习:grep, sed, awk命令的练习题

    http://www.cnblogs.com/chengmo/archive/2013/01/17/2865479.html 文件:datafileSteve Blenheim:238-923-736 ...

  2. grep/sed/awk命令查看指定时间段的日志

    *grep命令 今天遇到研发要求查询定时任务(elastic-job)在14:00-14:40的日志,使用grep命令很方便: 命令: grep '时间' '日志文件名 ' 1.例如查询2020-02 ...

  3. Linux三剑客grep/sed/awk

    grep/sed/awk被称为linux的“三剑客” grep更适合单纯的查找或匹配文本: sed更适合编辑匹配到的文本: awk更适合格式化文本,对文本进行较复杂各式处理: Grep --color ...

  4. linux三剑客grep|sed|awk实践

    最好先学习正则表达式的基本用法,以及正则表达式BREs,EREs,PREs的区别 此坑待填 grep sed awk

  5. grep sed awk 3个Linux中对文件内容操作的命令

    在学习Linux命令中,发现3个有关于文件内容操作的命令grep,sed和awk,在这里简单汇总这3个命令主要作用,在实际中找到最合适的情景应用,详细用法可以参考其他文章. 1.grep命令 主要作用 ...

  6. 5_find grep sed awk 详解

    find :查找文件系统中指定的文件.可以按文件名(-name)  权限(-perm) 归属人 查找. find   要查找文件的路径   表达式 *通配符  可以添加在文件名的任意位置 常用的例子( ...

  7. 【Linux】 字符串和文本处理工具 grep & sed & awk

    Linux字符串&文本处理工具 因为用linux的时候主要用到的还是字符交互界面,所以对字符串的处理变得十分重要.这篇介绍三个常用的字符串处理工具,包括grep,sed和awk ■ grep ...

  8. linux grep sed awk

    $ grep ‘test’ d* 显示所有以d开头的文件中包含 test的行. $ grep ‘test’ aa bb cc 显示在aa,bb,cc文件中匹配test的行. $ grep ‘[a-z] ...

  9. [svc]linux正则实战(grep/sed/awk)

    企业实战: 过滤ip 过滤出第二行的 192.168.2.11. eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 ine ...

随机推荐

  1. TensorFlow简要教程及线性回归算法示例

    TensorFlow是谷歌推出的深度学习平台,目前在各大深度学习平台中使用的最广泛. 一.安装命令 pip3 install -U tensorflow --default-timeout=1800 ...

  2. 工具 | Axure基础操作 No.6

    这个是基础教程最后一篇,但是这仅仅是个开始,需要学的东西还有很多.坚持! 1.生成部分原型页面 不能单独生成子级的页面,会自动的勾选上父级.如果想单独的生成的话,就得把这个页面的级别提高,变成一级页面 ...

  3. iOS OC与JS的交互(JavaScriptCore实现)

    本文包括JS调用OC方法并传值,OC调用JS方法并传值 本来想把html放进服务器里面,然后访问,但是觉得如果html在本地加载更有助于理解,特把html放进项目里 HTML代码 <!DOCTY ...

  4. 从对集合数据去重到Distinct源码分析

    今天在写代码的时候要对数据进行去重,正打算使用Distinct方法的时候,发现这个用了这么久的东西,竟然不知道它是怎么实现的,于是就有了这篇文章. 使用的.net core2.0 1.需求 假如我们有 ...

  5. Redis笔记 -- 链表和链表节点的API函数(三)

    链表和链表节点API 函数 作用 时间复杂度 listSetDupMethod 将给定的函数设置为链表的节点值复制函数 复制函数可以通过链表的dup属性直接获得,O(1) listGetDupMeth ...

  6. Delphi Firemonkey在主线程 异步调用函数(延迟调用)

    先看下面的FMX.Layouts.pas中一段代码 procedure TCustomScrollBox.MouseDown(Button: TMouseButton; Shift: TShiftSt ...

  7. 两张图证明 WolframAlpha 的强大

    引用于:https://capbone.com/wolfram-alpha/ 两张图证明 WolframAlpha 的强大 之前在" 我手机中有哪些应用 "里提到过 Wolfram ...

  8. Dart 语言了解

    Dart 语言了解 概念 当您了解Dart语言时,请记住以下事实和概念: 您可以放在变量中的所有内容都是一个对象,每个对象都是一个类的实例.偶数,函数和 null对象.所有对象都从Object类继承. ...

  9. scala (4) 可变数组和不可变数组

    在scala中数组分为不可变长数组(在immutable包下)和可变长数组(在mutable包下) 不可变长数组指的是长度不可变,但是数组中角标对应的元素的值是可变的 可变数组指的是长度和数组中角标对 ...

  10. SAP OData $batch processing

    例として.1回の呼び出しで100個の新しい商品を作成したい場合.最も簡単な方法は.$ batch要求を使用して100個のPOST呼び出しすべてを単一のサービス呼び出しにまとめることです. URIの末尾 ...