grep 、sed、awk 被称为 linux 中的 “三剑客”。

  • grep 适合单纯的 查找匹配 文本
  • sed 适合 编辑 匹配到的文本
  • awk 适合 格式化 文本,对文本进行较复杂格式处理

GREP

语法

检索文件内容,列出模板所在的行。模板可以是字符串或正则表达式,支持 BRE 和 ERE。

grep [-A] [-B] [--color=auto] '模板' filename

-A 列出该行及其后 n 行

-B 列出该行及其前 n 行

--color=auto 标记颜色

-n 显示行号

范例

正向匹配

$ last | grep 'root'

反向匹配

$ last | grep -v 'root'

使用高亮

grep --color=auto 'MANPATH' /etc/man_db.conf

统计匹配次数

$ last | grep -c root
10

忽略大小写

$ grep -i shit file

显示行号

$ grep -n shit file

以文本方式对待二进制文件

$ grep -a shit bin

查看关键字所在行的前后内容

$ dmesg | grep -n -A1 -B5 --color=auto 'qxl' file

A: After。-A1 关键字所在行的后 1 行。

B: Before。-B5 关键字所在行的前 5 行。

SED

Stream EDitor

sed 是一种流编辑器,它是文本处理中非常强大的工具,能够完美的配合正则表达式使用,功能不同凡响。

处理时,把当前处理的行存储在临时缓冲区中,称为 “模板空间”(pattern space),接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容发送到屏幕。接着处理下一行,这样不断重复,直到文件末尾。它不会主动修改文件内容。

Sed 主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

语法

sed [options] ‘command’ file(s) sed [options] -f scriptfile file(s)

[ options ]

-e script 在命令行用指定的参数来处理,常用于同时进行 多个操作

-f script 用指定的 脚本 文件来处理

-n 仅显示 由脚本处理过的行,否则会显示文件所有行

-r 使用 ERE

-i 直接修改文件内容

[ command ]

[n1[,n2]]function

n1, n2:可选,表示选择范围的起止行数

function:

a 在当前行下面插入一行

c 修改指定行的内容

d 删除指定行

i 在当前行上面插入一行

p 输出

s 替换,可以直接进行替换,可搭配正则表达式

sed 后面如果要接超过两个以上的操作时,每个操作之前均要加 -e

范例

删除

删除单行
$ cat /etc/passwd | sed '4d'
删除连续多行
$ nl /etc/passwd | sed '2,5d'
删除第 3 行至末行
$ nl /etc/passwd | sed '3,$d'

整行替换

sed '行范围c 替换内容' file

行范围:单行用一个数字表示行号;多行用逗号 , 连接两个行号来表示。

紧随的 c 表示将范围内所有行修改为空格后面的字符串。

替换单行

行范围为一个数字:

$ sed '3c holly shit' file

把第 3 行的全部内容替换为 holly shit

替换连续多行

行范围为逗号连接的两个行号:

$ nl /etc/passwd | sed '2,5c No 2-5 number'

原来的第 2~5 行,会被替换为一行,内容为 No 2-5 number

字符串替换

sed 's/关键字/替换/g'

在行内替换匹配的字符串。

s 代表搜索,g 表示 global,全局替换。

如果没有 g 则只替换当前行的 第一个 匹配,加上 g 则替换当前行中 所有 匹配。

替换关键字
$ sed 's/good/bad/' file   # 每行仅替换第一个匹配
$ sed 's/good/bad/g' file  # 每行替换所有匹配
删除文件中的注释行

cat /etc/man_db.conf | grep 'MAN' | sed 's/#.*$//g'

从 ifconfig 文件中查看 IP 地址
$ ifconfig eth0 | grep 'inet ' | sed 's/^.*inet //g' | sed 's/ netmask.*$//g'
ipconfig 原始信息 取出 IP 地址所在行 删除 IP 地址前面字符 删除后面字符

插入行

a 在当前行下面插入

i 在当前行上面插入

在第 2 行下面加入一行
$ nl /etc/passwd | sed '2a drink tea'
在第 2 行上面插入一行
$ nl /etc/passwd | sed '2i drink tea'
在第 2 行下面加入两行
$ nl /etc/passwd | sed '2a Drink tea or ......\
> drink beer ?\
> holly shit'

不同的行需用 转义符 + 回车 分隔,最后一行的行尾加单引号结束。

查看行

查看第 5-7 行:

$ nl /etc/passwd | sed -n '5,7p'

混合使用

删除第 4 行,替换第 6 行
$ cat /etc/passwd | sed -e '4d' -e '6c no six line'
删除第 4 行,替换第 6~10 行
$ cat /etc/passwd | sed -e '4d' -e '6,10c holly shit'

直接修改原文件

使用选项 -i 可以直接修改原文件,慎用。

直接替换文件内容
$ sed -i 's/.$/!/g' test.txt
在最后一行后面加入一行
$ sed -i '$a This is a test' test.txt

$ 表示最后一行

AWK

本节内容特别感谢 朱双印个人日志

简介

AWK 是一种解释执行的编程语言。它非常强大,被设计用来专门处理文本数据。

awk 其实是一门编程语言,它支持条件判断、数组、循环等功能。所以,我们也可以把 awk 理解成一个脚本语言解释器。

由 GNU/Linux 发布的 AWK 版本通常被称之为 GNU AWK,由自由软件基金( Free Software Foundation, FSF)负责开发维护的。 目前总共有如下几种不同的 AWK 版本。

  • AWK——这个版本是 AWK 最原初的版本,它由 AT&T 实验室开发。
  • NAWK ——NAWK(New AWK)是 AWK 的改进增强版本。
  • GAWK—— GAWK 即 GNU AWK,所有的 GNU/Linux 发行版都包括 GAWK,且 GAWK 完全兼容 AWK 与 NAWK。

linux 中常用的是 gawk。

部分 AWK 的典型应用场景

AWK 可以做非常多的工作,下面只是其中的一小部分:

  • 文本处理
  • 生成格式化的文本报告
  • 进行算术运算
  • 字符串操作 …

工作流程

AWK 执行的流程:读取、执行、重复。

image-center

awk 是逐行处理的,默认把 “换行符” 作为一行结束的标记,默认使用空格作为分割符,把每一行切割成若干个字段。分割符可以由用户指定。

每一 也叫一个 记录,record。

每一行用分隔符切割之后,剩下的部分叫 字段,filed。

读取

AWK 从输入流(文件、管道或者标准输入)中 读入一行,将其放入内存中。

执行

对于每一行输入,所有的 AWK 命令依次执行。

默认情况下,AWK 命令是针对于每一行输入执行的,但是我们可以将其限制在指定的模式中。

重复

一直重复上述两个过程直到文件结束。

内建变量

awk 的内建变量。

变量名称 意义
NF 每行字段的总数
NR 当前所在的行号
FS 输入字段之间的分隔符
OFS 输出字段之间的分隔符
RS 输入行之间的分隔符
ORS 输出行之间的分隔符
ARGC 在命令行提供的参数的个数
ARGV 存储命令行输入参数的数组
ENVIRON 与环境变量相关的关联数组变量
FILENAME 当前文件名
$0 整个输入行
$n 当前输入行的第 n 个字段

NR, NF 等内置变量要大写,前面不用加 $ 。

语法

awk [options] 'pattern{action}' filenames
awk [options] '
BEGIN { actions }
/pattern/ { actions }
/pattern/ { actions }
………
END { actions }
' filenames  

[options] :选项

pattern :模板

actions :操作

程序的结构

开始块

BEGIN { actions }

BEGIN block,为可选部分。是在程序启动时执行的代码,整个过程中只执行一次。通常用于 初始化变量

BEGIN 是 AWK 的关键字,必须大写。

主体块

/pattern/ { actions }

Body Block,必选部分。

awk 读取的每一行都会执行一次主体块中的命令。

结束块

END { actions }

END Block,结束块也是可选的。

结束块是在程序结束时执行的代码。END 也是 AWK 的关键字,必须大写。

AWK 内部运转流程

for 每个文件:
	for 每一行:
 		for 每一组 (条件/模板)/操作:
			if 存在条件:
				if 条件为真
			else if 存在模板:
				if 匹配模板:
					if 存在操作:
						执行操作
					else:
                  		print 当前行
         	else (没有模板):
				执行操作

选项

可选。

模板

可选。

awk 在逐行处理数据流时,会把模板作为 条件 来进行检查、判断。

如果没有指定模板,文本流中的每一行都会执行对应的操作;如果指定了模板,则只有与模板匹配的、符合条件的行才会执行对应的操作。

模板可以是:

模板可以是空的,即可以被省略,此时意味着 每个输入行都满足条件,因此所有行都会被处理。如:

$ awk '{print $0}' file

/正则表达式/

awk 使用 ERE,扩展正则表达式。

正则表达式必须放在一对斜线中间 / /,如果正则表达式中需要使用 斜线,需要 转义

'/正则表达式/{操作}'

这样的形式是把 匹配 该正则表达式的 进行对应的操作。

$ awk '/^adm/{print $0}' /etc/passwd

查找以 adm 开头的行。

'/正则1/,/正则2/{操作}'

这样的形式是指定一个 匹配行的范围。正则1指定起始行,正则2指定结束行,针对从起始行开始一直到结束行的所有行进行操作。

$ awk '/Lee/,/Kevin/{print $0}' file
'(/正则1/)&&(/正则2/){操作}'

如果多个正则表达式需要逻辑运算,可以先用括号包围起来,再使用逻辑运算符。

通过 last 登陆记录统计最近所有用户的登陆次数。

$ last | awk 'BEGIN{print "登陆次数","\t","用户"} \
> ($1 !~ /\(/)&&($1 !~ /reboot/)&&($1 != "") \
> {c[$1]++} END{for(i in c){print c[i],"\t\t",i}}'
登陆次数         用户
66               neo
1                wtmp
5                trinity
10               root

其中的正则表达式 ($1 !~ /\(/)&&($1 !~ /reboot/)&&($1 != "") 是为了排除结果中非用户的行。

{m,n} 重复次数

在使用 {m,n}匹配 前一个字母的 重复次数 时,必须要指定 --posix--re-interval 其中的一个选项。

$ awk --posix '/go{2,5}gle/{print $0}' file
google
goooogle

关系表达式

使用运算符进行操作,可以是字符串或数字的 比较测试。这种模板通过比较测试来定义判断的条件。

awk 支持的关系运算符:
关系运算符 含义 用法示例
< 小于 x < y
<= 小于等于 x <= y
== 等于 x == y
!= 不等于 x != y
>= 大于等于 x >= y
> 大于 x > y
~ 与对应的正则匹配则为真 x ~ /正则/
!~ 与对应的正则不匹配则为真 x !~ /正则/
定位每行字段数等于 5 的行,查看第 4 个字段:
$ awk 'NF==5 {print $4}' file
查看第 3 行到第 6 行的内容:
$ awk 'NR>=3 && NR<=6 {print $0}' file
在关系表达式中使用正则:
$ awk --posix '$2~/go{1,5}/{print $1,$2}' file

BEGIN 块,END 块

😈 说实在的,这些东西放在 pattern 里,完全没有逻辑可言,毕竟它们算不上条件,无法进行判断,它们更像是 include,所以其实应该属于 “操作”。

BEGIN 块通常用来显示 表头

$ awk 'BEGIN{print "Album","Artist","Title"} {print $1,$3,$5} END{print "aaa","ccc","ddd"}' music

操作

  • 操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开。
  • 所有操作的最外层必须用大括号 { } 引用。
  • 在操作中使用 位置变量 时,不能用双引号 引用,即 "$1",会被当作普通字符串看待。

操作中使用的元素有:

  • 变量或数组 :$3ant[3]
  • 输出命令 :print
  • 内建函数 :split()length()
  • 控制流语句 :ifwhile

简单操作

大括号中只有一个命令,如输出:

$ awk '{print $2}' file

简单操作的组合

可以把多个命令都放在一对大括号中,用分号 ; 分隔开:

$ awk '{print $1; print $3; print $5}' file

也可以把每个命令放在单独的一对大括号中,再把所有大括号直接连接在一起:

$ awk '{print $1} {print $3} {print $5}' file

流控制语句

通过使用流控制语句,进行基于条件的操作。

if 语句常规结构

在多数编程语言中,如 JS,if 语句的结构是这样的:

if (...)
  {
  actions
  }

将其写到一行:

if (...) { actions }
AWK 中的 if 语句结构

AWK 在操作中沿用了这个格式,因此在操作中使用控制语句是这样的:

$ awk '{if(...){actions}}' file

如:

$ awk '{if(NR==1) {print $1; print $2}}' file
if...else...

if 的用法一样,if...else... 以及 if...elseif...else 都可以这样使用。

if(...) {actions} else {actions}
if(...) {actions} else if(...) {actions} else {actions}

如:

$  awk -F: '{if($3<1000) {print $1,"系统用户"} else{ print $1,"普通用户"} }' /etc/passwd
root 系统用户
bin 系统用户
neo 普通用户
$ cat shit
罗大佑:88
崔健:40
窦唯:20

$ awk -F: 'BEGIN{print "姓名\t\t年龄"} \
> {if($2<=30) {print $1 "\t\t青年"} \
> else if($2>30 && $2<50) {print $1 "\t\t中年"} \
> else{print $1 "\t\t老年"}}' shit
姓名            年龄
罗大佑          老年
崔健            中年
窦唯            青年
操作中省略 { }

如果控制语句中的操作命令只有 一个,则可以 省略 命令外面的 大括号

$ awk '{if(NR==1) print $2 }' file

循环控制语句

语法基于以下循环语句的结构:

for(初始化; 布尔表达式; 更新) {actions}
for(变量 in 数组) {actions}
while( 布尔表达式 ) {actions}
do {actions} while(条件)
for 循环
$ awk 'BEGIN{ for(i=1;i<=6;i++) {print i}}'
1
2
3
4
5
6
while 循环
$ awk -v i=1 'BEGIN{while(i<=4){print i;i++}}'
1
2
3
4

$ awk 'BEGIN{i=1;while(i<=4){print i;i++}}'
1
2
3
4
do ... while 循环
$ awk 'BEGIN{i=1;do{print "test";i++}while(i<1)}'
test

$ awk 'BEGIN{i=1;do{print "test";i++}while(i<=3)}'
test
test
test
跳出循环

continue 跳出当前循环,break 跳出整个循环。

$ awk 'BEGIN{for(i=0;i<6;i++){if(i==3){continue};print i}}'
0
1
2
4
5

$ awk 'BEGIN{for(i=0;i<6;i++){if(i==3){break};print i}}'
0
1
2
退出脚本

如果使用了 END,exit 便不是退出 awk,而是直接跳转去执行 END:

$ awk 'BEGIN{print "start";exit} {print $0} END{print "over"}' file
start
over

如果没有使用 END,exit 导致退出 awk,不再执行其余命令。

$ awk 'BEGIN{print 1;exit;print 2;print 3}'
1
跳过某行

使用 next 命令,让 awk 可以跳过当前行不处理,直接开始处理下一行:

$ cat shit
1
2
3

$ awk '{if(NR==2){next};print $0}' shit
1
3

分隔符

awk 默认使用空格、制表符做为输入和输出的字段分隔符。

输入字段分隔符

Field Separator,由内建变量 FS 表示。

$ echo "This-is-how-it-works" | awk 'BEGIN{RS="-"}{print $3}'
how

输出字段分隔符

Output Field Separator,由内建变量 OFS 表示。

$ echo "This,is,how,it,works" | awk 'BEGIN{FS=",";OFS=" _ "}{print $1,$4}'
This _ it

如果输出的格式表达式中使用了 逗号 来分隔,在输出中对应地会在该位置用 OFS 来分隔。

输入行分隔符

Row Separator,由内建变量 RS 表示。

$ echo "This is how it works" | awk 'BEGIN{RS=" "}{print $0}'
This
is
how
it
works

awk 在处理行时,只要遇到空格就换行,导致输出行中每行只有一个单词。

#### 输出行分隔符

Output Record Separator,由内建变量 ORS 表示。

df | awk 'BEGIN{ORS=" "}{print $5}'
Use% 62% 0% 1% 2% 0% 18% 1% 1% 0%

数组

AWK 中使用的数组,本质上是 关联数组,索引不仅可以用 “数字”,还可以用 “任意字符串”。

数组赋值

数组 无需声明,可以直接为数组中的元素赋值:

$ awk 'BEGIN{ ant[0]="a";ant[1]="b";ant[2]="c"; {print ant[1]} }'

判断数组元素是否存在

直接 引用 一个数组中 不存在 的元素时,awk 会 自动创建 这个元素,并且为其赋值为 空字符串。称之为引用时创建。

语法:if(索引 in 数组名)if(!(索引 in 数组名))

$ awk 'BEGIN{ ant[0]="a";ant[1]="b";ant[2]="c"; if(1 in ant) \
> {print ant[1]} }'
b

用字符串索引

$ awk 'BEGIN{ ant["lipi"]="aabb";ant["coco"]="3fsd";ant["nike"]="shit";\
> print ant["coco"]}'
3fsd

删除数组

$ awk 'BEGIN{ ant["lipi"]="aabb";ant["coco"]="3fsd";ant["nike"]="shit";\
> print ant["lipi"]; delete ant; print ant["coco"] }'
aabb

删除数组元素

$ awk 'BEGIN{ ant["lipi"]="aabb";ant["coco"]="3fsd";ant["nike"]="shit";\
> print ant["lipi"]; delete ant["coco"]; print ant["coco"] }'
aabb

输出数组所有元素

$ awk 'BEGIN{ ant[1]="aabb";ant[2]="3fsd";ant[3]="shit";for(i=1;i<=3;i++) \
> {print i,ant[i]}; }'
1 aabb
2 3fsd
3 shit
$ awk 'BEGIN{ ant["lipi"]="aabb";ant["coco"]="3fsd";ant["nike"]="shit";\
> for(i in ant) {print ant[i] }}'
shit
aabb
3fsd

关联数组 打印出来的元素是 无序 的,因此与赋值时顺序不一样。

范例

范例一

利用数组来统计日志文件中每个 IP 地址 出现的次数

假设日志文件的内容如下:

$ cat testlog
192.168.1.11
192.168.1.1
192.168.1.8
192.168.1.1
192.168.1.88
192.168.1.10
192.168.1.1
知识准备

awk 在使用数组时,有一个有意思的现象可以利用:

  • 先给一个 变量 a 赋值 字符串,如果把该变量做 自加操作 的话,初始的变量值,即字符串,会被 当作数字 0 参与运算:
$ awk 'BEGIN{ a="test";print a;a=a+1;print a;a++;print a}'
test
1
2
  • 如果变量的初始值为 的字符串,也会被当作数字 0 来处理,结果一样:
$ awk 'BEGIN{ a="";print a;a=a+1;print a;a++;print a}'

1
2
编程思路

直接引用一个数组中不存在的元素时,awk 会自动创建这个元素,并且为其赋值为 空字符串

把日志中每个不同的 IP 地址 ,即每个字段做为数组的 索引

同一个字段第一次出现时,该数组元素的索引是 IP 地址;第二次出现,将该数组元素进行 自加 一次,值变成数字 0,并加 1;依此类推。

统计完毕时,该地址出现了多少次,该数组元素的值就是多少。

实现思路

因为日志做了简化,每个 $1 即是 IP 地址。

因为每行数据都要处理,因此不需要模板,可以省略。

在主体程序块中只需一句变量自加的语句即可,count[$1]++

在结尾时可以用循环语句把每个 IP 地址出现的次数一一列出,放在 END 即可。

$ awk '{count[$1]++} END{for(i in count){print i,"\t",count[i]}}' testlog
192.168.1.8      1
192.168.1.10     1
192.168.1.11     1
192.168.1.88     1
192.168.1.1      3
范例二

上面这个方法非常适合于统计 字符串出现的次数,因此如果是字符串,要怎么来统计?

$ cat testfile
human: water rice meat fruit
monkey: fruit water
goat: water grass
rabbit: water grass fruit
lion: water meat

因为数据结构不太一样了,所以我们要重新考虑一下。

  • 鉴于每一行的单词数不一样,我们可能会用到变量 NF,即每记录的字段总数。
  • 要想为每个记录的所有字段迭代的话,还需要循环语句。
  • 同样是把每个单词做为数组的索引
$ awk '{for(i=1;i<=NF;i++){count[$i]++}} END{for(j in count){print j,"\t",count[j]}}' testfile
lion:    1
water    5
meat     2
fruit    3
rice     1
human:   1
goat:    1
rabbit:  1
grass    2
monkey:  1
范例三

数组在 AWK 中使用时,特别适用于处理 重复的行,无论这些行是否相邻。

虽然 uniq 可以去掉相邻的重复行,但对于不相邻的就无能为力了。

过滤重复的行

重复的行只显示一次:

$ awk '{ if (!a[$0]) print $0; a[$0]++ }'

可以 简写 为:

$ awk '!a[$0]++' file

此处省略了操作,因此 AWK 默认会使用 print

过滤空行

不显示空行:

$ awk 'NF' file
只显示重复的行
$ awk 'c[$0]++; c[$0]==2' shit

内建函数

AWK 为程序开发者提供了丰富的内建函数。

算术函数

取整函数
$ awk 'BEGIN{print int(3.14)}'  
3
随机数函数

rand 的功能是随机返回一个 0 到 1 的小数。

但如果单独使用时,一次生成的值是不变的:

$ awk 'BEGIN{print rand()}'        
0.237788
$ awk 'BEGIN{print rand()}'        
0.237788

虽然一次可以生成多个不同随机数,但下一次运行你会发现,生成的还是这些。rand 每一次都是使用同一个种子开始计算随机数,如此设计是为了便于调试代码,因为每次运行都可以生成同样的随机序列。但是,如果希望每次运行都生成不同的随机序列,需要让 rand 每次使用不同的种子,可以借助 srand 来完成。

0~1 之间的小数
$ awk 'BEGIN{srand();print rand()}'

这样就可以确保每次运行该命令时,得到的一定是不同的随机数。

小于 100 的整数
$ awk 'BEGIN{srand();print int(100*rand())}'

字符串函数

替换文本
gsub

gsub(原字符串, 新字符串, 正文)

global substitution,正文中的原字符串,会被 全部 替换成新字符串,如果正文省略,则会默认使用 $0

l 替换成 L

$ awk '{gsub("l","L",$0);print $0}' test
HeLLo WorLd !

把第一个字段中的小写字母替换成数字 8:

$ awk '{gsub("[a-z]","8",$1);print $0}' test
H8888 World !
sub

sub 功能与 gsub 相似,只不过它只会替换第一个匹配,而非全部。

$ awk '{gsub("l","L",$0);print $0}' test
HeLlo World !
字符串长度

length() 用于获取字符串的长度:

$ cat p
Hello World !
What Are You Doing?

$ awk '{for(i=1;i<=NF;i++){print $i,length($i)}}' test
Hello 5
World 5
! 1
What 4
Are 3
You 3
Doing? 6

如果省略传入的参数,则默认把 $0 做为参数:

$ awk '{print $0,length()}' test
Hello World ! 13
What Are You Doing? 19

输入控制

READ

从标准输入中读取一行。

read [ -p ][ -r ][ -s ][ -u[ n ] ] [ VariableName?Prompt ] [ VariableName ... ]

read 命令从 标准输入 中读取 一行,并将输入行的 每个字段的值 指定给 特定变量,用 IFS(内部字段分隔符)变量中的字符作为 分隔符

read 后面的变量可以有 一个或多个,如果输入多个字段,则第一个字段给第一个变量,第二个字段给第二个变量,如果输入字段数量过多,则最后剩下的所有字段都给最后一个变量。

如果没有指定变量名,读取的数据将被自动赋值给特定的变量 REPLY

范例

read 1987name

从标准输入读取输入并赋值给变量1987name。

read first last

从标准输入读取输入到第一个空格或者回车,将输入的第一个单词放到变量first中,并将该行其他的输入放在变量last中。

read

从标准输入读取一行并赋值给特定变量REPLY。

read -a arrayname

把单词清单读入arrayname的数组里。

read -p "text"

打印提示(text),等待输入,并将输入存储在REPLY中。

read -r line

允许输入包含反斜杠。

read -t 3

指定读取等待时间为3秒。

read -n 2 var

从输入中读取两个字符并存入变量var,不需要按回车读取。

read -d ":" var

用定界符“:”结束输入行。

输出控制

块操作

tee

读取标准输入,输出一份到标准输出,一份到文件。

常用于把输入 另存 为文件。

tee -a file

-a 追加到文件

范例

last | tee last.list | cut -d " " -f 1 另存一份

ls -l /home | tee -a ~/homefile | less 追加到文件

cat

把一个或多个文件或标准输入合并在一起,发送给标准输出。

cat [OPTION]... [FILE]...

-v 显示不可打印字符

-E 显示行尾符 $

-T 显示 tab 键,用 ^I表示

-A 相当于 -vET

-b 非空行显示行号

-n 所有行显示行号

tac

功能与 cat 一致,只不过是反向显示,首行变末行。

tac file

nl

把文件内容发送到标准输出,并在每行前面加上行号。

nl [OPTION]... [FILE]...

-b 指定行号显示方式:

	`-ba`	所有行显示行号

	`-bt`	非空行显示行号(默认值)

-n 指定行号对齐方式:

	`-n ln`	左对齐

	`-n rn`	右对齐

	`-n rz`	右对齐,并用0填充空位

-w 指定行号占用的字符数

more

以翻页方式查看文件内容。

more file

每页的最后一行会显示出当前显示的百分比,还可以在最后一行输入命令:

空格 向下翻一页

回车 向下翻一行

/字符串 向下查找关键字

:f 立刻显示出文件名以及目前显示的行数;

q 立即退出 more

b 往回翻页,对管道不起作用

less

less file

可以输入的命令:

空格 向下翻一页;

pagedown 向下翻一页;

pageup 向上翻一页;

/字符串 向下查找

?字符串 向上查找

n 重复查找

N 反向重复查找

g 到第一行

G 到最后一行

q 退出

-n 显示的行数,缺省时默认显示 10 行

head -n 6 file 显示前6行

head -n -6 file 去掉后6行,显示剩下的所有

tail

tail -n 6 file 显示后 6 行

tail -n +10 file 去掉前 10 行,显示剩余的行

tail -f log 持续监测 log 文件,一旦有新内容追加,马上显示出来

od

把输入 转换八进制 或其它格式,发送到标准输出。

od 命令用指定格式显示由 File 参数指定的文件。如果 File 参数没有给定,od 命令读取标准输入。

在第一个语法格式中,输出格式是由 -t 标志指定。如果没有指定格式类型,-t o2 是缺省值。

在第二个语法格式中,输出格式由标志组合指定。Offset 参数指定了文件中文件输出的开始点。缺省情况下,Offset 参数解释为八进制字节。如果附加了 . 点后缀,参数解释为十进制的;如果参数前导以 x 或 0x 开始,处理为十六进制。 如果 b 后缀添加到参数,解释为块是 512 字节;如果 B 后缀添加到参数上,解释为块是 1024 字节。

Label 参数解释为首字节显示的伪地址。如果使用了该参数,它在 () 括号中给出,遵循 Offset 参数。相对于 Offset 参数,后缀有同样的意义。

当 od 命令读取标准输入时,Offset 参数和 Label 参数前头必须有个 +(加号)。

环境变量的设置如 LANG 和 LC_ALL 影响着 od 命令的操作。

使用 类型字符串 来格式化输出以显示文件:

od [ -v ] [ -A AddressBase ] [ -N Count ] [ -j Skip ] [ -t TypeString ... ] [ File ... ]

要使用 标志 来格式化输出以显示文件:

od [ -a ] [ -b ] [ -c ] [ -C ] [ -d ] [ -D ] [ -e ] [ -f ] [ -F ] [ -h ] [ -H ] [ -i ] [ -I ] [ -l ] [ -L ] [ -o ] [ -O ] [ -p ] [ -P ] [ -s ] [ -v ] [ -x ] [ -X ] [ [ -S [ N ] ] [ -w [ N ] ] [ File ] [ [ + ] Offset [ . | b | B ] [ + ] Label [ . | b | B ] ] [ File ... ]

-t 后面可以接各种类型的输出

a 利用默认的字符来输出

c 使用 ASCII 字符来输出

d[size] 用十进制输出数据,每整数占用的字节数

f[size] 用浮点数值输出数据,每整数占用的字节数

o[size] 用八进制来输出数据,每整数占用的字节数

x[size] 用十六进制输出数据,每整数占用的字节数

范例

od -t c file

以 ASCII 方式来查看文件内容

od -t oCc file

以 8 进制列出储存值与 ASCII 的对照表

printf

格式化输出

printf 命令模仿 C 程序库(library)里的 printf() 程序。

printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。

printf 不是管道命令,无法直接读取标准输入,需要 把文件内容先读取出来,作为 printf 的处理对象。而且处理时 不会自动换行,必须手动在格式中加入换行符,否则所有内容都显示在一行。

语法

printf FORMAT [ARGUMENT]...

FORMAT

FORMAT 格式字符串,用来描述输出的排列方式,需用 引号 括起来。

此字符串包含 按字面显示的字符 以及 格式替代符

一般字符按字面显示,转义序列需解释后再输出成对应的字符。

  • 格式替代符

格式替代符是特殊的占位符,用来描述如何显示对应的参数。

格式替代符由两部分组成:% + 指示符

%s 字符串

%d, %i 十进制整数

%b 相对应的参数被视为含有要被处理的转义序列字符串

%c ASCII字符。显示相对应参数的第一个字符

%e, %E, %f 浮点格式

%g %e 或 %f 转换,看哪一个较短,则删除结尾的零

%G %E 或 %f 转换,看哪一个较短,则删除结尾的零

%o 不带正负号的八进制值

%u 不带正负号的十进制值

%x 不带正负号的十六进制值,使用 a~f 表示 10~15

%X 不带正负号的十六进制值,使用 A~F 表示 10~15

%% 字面意义的 %

%8s 总长 8 位,字符串

%8i 总长 8 位,整数

%8.2f 整数 8 位,小数点后2位

  • 转义序列

\a 警告声

\b 回退键

\f 换页

\n 换行

\r 回车

\t 水平制表符

\v 垂直制表符

\xNN 两位数字,查询 ASCII

arguments

是与格式替代符相对应的参数列表,如一系列的 字符串或变量值

范例
  • 仅列出姓名与成绩,用 tab 分隔

printf '%s\t %s\t %s\t %s\t %s\t \n' $(cat printf.txt)

%s 代表一个不固定长度的字串,而字串与字串中间以 \t 来分隔。

  • 第二行以后,分别以字串、整数、小数点来显示:

printf '%10s %5i %5i %5i %8.2f \n' $(cat printf.txt | grep -v Name)

查 ASCII 对照
  • 16 进制的 45 对应 ASCII 的哪个字符?

printf '\x45\n'

E

wc

统计 文件的字符数、行数、单词数。

wc -lwm

-l 行数

-w 单词数

-m 字符数

范例
  • 统计字、行、字符数
cat /etc/man_db.conf | wc
131   723   5171    # 默认同时列出行数、单词数、字符数
  • 分析 last 输出信息,计算登陆系统的总人次

last 最后两行并非帐号内容,分别以 reboot 和 wtmp 开头,另外还有一行以(unknown开头的行也是无用行,因此需要去掉这三行及空行。

last | grep [a-zA-Z] | grep -v 'wtmp' | grep -v 'reboot' | grep -v 'unknown' |wc -l

先取出非空行,然后去除上述关键字那三行,再计算行数。

行操作

sort

sort 命令用于 给行排序,可以按不同的数据型态(如数字或文字)来排序。

排序的字符与 语系 的编码有关,因此,排序时建议使用 LANG=C 来统一语系。

sort 默认按每行第一个字符,按字母顺序排序。

sort -fbMnrtuk file or STDIN

-f 忽略大小写

-b 忽略前置空白字符

-M 根据月份英文名字排序

-n 用纯数字排序

-r 反向排序

-u 相同的数据仅保留一行(去重复)

-t 分隔符,默认用 [tab] 分隔

-k 切割之后,依据第几段排序

范例
cat /etc/passwd | sort   # 按第一段(帐号)排序
cat /etc/passwd | sort -t ':' -k 3 -n   # 按第三段排序
last | cut -d ' ' -f 1 | sort   # 按第一段排序
last | cut -d ' ' -f 1 | sort -u    # 去掉重复项

uniq

查找省略 连续重复 的行。

uniq -ic

-i 忽略大小写

-c 统计重复次数

范例一

查看登陆次

用 last 列出历史登陆记录 只保留帐号栏 去掉 unknown 去掉 reboot 排序 去掉重复项并统计重复次数
$ last | cut -d ' ' -f 1 | grep -v 'unknown' | grep -v 'reboot' | sort | uniq -c
      1
     66 liloli
      5 neo
     10 root
      1 wtmp
范例二
$ cat shit
human: water rice meat fruit
monkey: fruit water
monkey: fruit water
monkey: fruit water
monkey: fruit water
monkey: fruit water
monkey: fruit water
goat: water grass
monkey: fruit water
rabbit: water grass fruit
lion: water meat


$ uniq shit -c
      1 human: water rice meat fruit
      6 monkey: fruit water
      1 goat: water grass
      1 monkey: fruit water
      1 rabbit: water grass fruit
      1 lion: water meat

cut

把文件的每一行 截取一部分,发送给标准输出。

cut [options] strings

-d 指定分隔符

-f 按段截取,接范围

-c 按列截取,接范围

cut 主要的用途在于 截取 文本文件或标准输入中,每一行里的指定 字符串

cut -d '分隔符' -f fields

有特定 分隔符 的文件,截取 某几段

cut -c 字符区间

用于 排列整齐 的文件,截取 某几列

截取指定段

echo ${PATH} | cut -d ':' -f 5 取第5个值

echo ${PATH} | cut -d ':' -f 3,5 取第3、第5个值

echo ${PATH} | cut -d ':' -f 3-5 取第3到第5个值

last | cut -d ' ' -f 1 last 输出的信息只保留用户名

截取指定列

export | cut -c 12- 截取第 12 列之后的字符

export | cut -c 12-20 截取第 12-20 列的字符

export | cut -c -20 | head -n 6 截取第 20 列之前的字符

export | cut -c 1-5,20,30-35 | head -n 6 截取指定列的字符

join

join 把两个文件的 相关连的行连接在一起

使用 join 之前,应该先把文件内容排序。

join [-ti12] file1 file2

-t 分隔符

-i 忽略大小写

-1 第一个文件的关键字段

-2 第二个文件的关键字段

join 默认分隔符为 空格,比较 第一个字段,如果相同,就把两个文件的这两行数据连接在一起。

范例
  • 把 /etc/passwd 与 /etc/shadow 同一用户的数据整合至一行
join -t ':' /etc/passwd /etc/shadow | head -n 3
# 因为两文件关键字段均为第一段,故省略不写 -1,-2
root:x:0:0:root:/root:/bin/bash:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin:\*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin:\*:16372:0:99999:7:::

🚩 连接时,第二个文件的第一字段被删除,避免重复。

  • 整合 /etc/passwd 和 /etc/group,关键字为 GID

GID 为 /etc/passwd 的第4段,为 /etc/group 的第3段

join -t ':' -1 4 /etc/passwd -2 3 /etc/group | head -n 3

0:root:x:0:root:/root:/bin/bash:root:x:
1:bin:x:1:bin:/bin:/sbin/nologin:bin:x:
2:daemon:x:2:daemon:/sbin:/sbin/nologin:daemon:x:
# 连接时,GID 被提到行首,并删除原始位置上的 GID

paste

paste 不检查文件内容,把 多个文件相同的行合并在一起,中间用 tab 分隔,输出到标准输出。

paste [-d] file1 file2 ...

-d 分隔符,默认为 tab

- 代替 STDIN

  • 拼合 /etc/passwd/etc/shadow
paste /etc/passwd /etc/shadow

root:x:0:0:root:/root:/bin/bash root:$6$wtbCCce/PxMeE5wm$KE2IfSJr...:16559:0:99999:7:::
bin:x:1:1:bin:/bin:/sbin/nologin bin:\*:16372:0:99999:7:::
daemon:x:2:2:daemon:/sbin:/sbin/nologin daemon:\*:16372:0:99999:7:::
  • 输出 /etc/group,然后与上例连在一起,取前三行
cat /etc/group | paste /etc/passwd /etc/shadow - | head -n 3
#  - 代表 STDIN ,即 cat /etc/group的输出     ^

字符串操作

这里的工具要么是处理简单的字符串,要么是把整个文件内容做为一个大的字符串,或是一个文本流来一起处理。

tr

可以对来自标准输入的 单个字符 进行 替换压缩删除

语法
tr [OPTION] SET1 [SET2]

SET1 为查找的对象,SET2 为替换的内容,除普通的 字符串,它们还支持 字符类 表示,如 [:lower:][:space:]

SET1 中的字符串在操作时会按单个字符来查找,如 'hold' 不会去查找 hold 这个单词,而是去查找 hold 这些单个的字符,如果是删除就只删除这些单个的字符,如果是替换则只替换成 SET2 中相同位置的单个字符。SET1 'abc',SET 2 'xyz',则所有字母 a 被换成 x,依此类推。

[OPTION]

条件类:

-c 查找对象为 SET1 中各个 字符的补集-c 'ab\n' 代表除 ab、换行符以外的所有字符。

-t 如果 SET1 的字符数比 SET2 的多,则先把 SET1 切成同样多的,再操作。

操作类:

-d 删除 SET1,此时会忽略 SET2

-s 把连续重复的字符以单独一个字符表示,压缩 成一个

[SET1] 和 [SET2] 代表是用

字符类

[:alnum:]:字母和数字

[:alpha:]:字母

[:cntrl:]:控制(非打印)字符

[:digit:]:数字

[:graph:]:图形字符

[:lower:]:小写字母

[:print:]:可打印字符

[:punct:]:标点符号

[:space:]:空白字符

[:upper:]:大写字母

[:xdigit:]:十六进制字符

小写字母 换 大写字母
$ last | tr '[a-z]' '[A-Z]'
$ tr '[:lower:]' '[:upper:]'
花括号 换 圆括号
$ tr '{}' '()' < inputfile > outputfile
空白字符 换 制表符
$ echo "This is for testing" | tr [:space:] '\t'
压缩连续的空格
$ echo "This   is   for testing" | tr -s [:space:] '\t'
删除冒号
$ cat /etc/passwd | tr -d ':'
删除所有数字
$ echo "my username is 432234" | tr -d [:digit:]
my username is
删除所有非数字
$ echo "my username is 432234" | tr -cd [:digit:]
432234

col

col 是一种文本过滤器,它可以过滤来自标准输入或文件的文本。它会尝试 删除 文本中的 RI (逆向换行符),并把 空格 替换 成 tab

col -xb

-x 把 tab 键转换成空格

  • 用 cat -A 显示所有特殊字符,把 tab 换成空格

cat -A /etc/man_db.conf 此时会看到很多 ^I 的符号,那就是 tab

cat /etc/man_db.conf | col -x | cat -A | more

expand

expand 用多个空格替换 tab

expand [-t] file

-t 替换空格的个数

grep '^MANPATH' /etc/man_db.conf | head -n 3 | expand -t 6 - | cat -A

unexpand

把空格转换成 tab

用法同 EXPAND。