sed awk 第二版学习(六)—— 编写 awk 脚本

news2024/10/11 18:49:36

目录

一、awk 程序设计模型

二、模式匹配

三、记录和字段

1. 字段的引用和分离

2. 字段的划分

四、表达式

五、系统变量

1. FS、OFS、RS、ORS

2. NF

3. NR、FILENAME、FNR

4. CONVFMT、OFMT

5. 两个例子

(1)处理多行记录

(2)支票簿的结算

六、关系操作符和布尔操作符

七、格式化打印

八、向脚本传递参数


        awk 是输入驱动的,也就是说,除非有可以在其上操作的输入行,否则将什么也不能做。当调用 awk 程序时,它将读入所提供的脚本,并检查其中的指令的语法,然后 awk 将对每个输入行执行脚本中的指令。

        可以用 awk 的 BEGIN 模式指定在第一个输入行读入之前要执行的动作。

$ awk 'BEGIN { print "Hello, world" }'
Hello, world

一、awk 程序设计模型

        awk 为程序员提供了定义得当且有用的模型。awk 程序由所谓的主输入(main input)循环组成。这个循环是一个例程,它将一直重复执行直到有一些存在的条件终止它。不必写这个循环,它是现成的。它作为一个框架存在,在这个框架中编写的代码能够执行。awk 中的主输入模型可以从文件中读取一行并使得例程可以访问它。自己编写的处理操作代码假设有一个可用的输入行,这说明了基本的 awk 简化操作可以使得编程更容易。主输入循环执行的次数和输入的行数相同,它仅当有一个输入行时才执行。当没有其它输入行时循环将终止。

        awk 允许编写两个特殊的过程,它们是与 BEGIN 和 END 规则相关的过程,在任何输入被读取前和所有输入都被读取后执行。或者说,在主输入循环执行前和主输入循环终止后可以做一些处理。BEGIN 和 END 过程是可选的。

        对于这三个组成部分,主输入循环或称为“处理过程中所做的处理”,是主要的处理部分。在主输入循环中,指令被写成一系列的“模式/动作”。每个“模式/动作”过程负责读取输入行。所编写的过程应用于每个输入行,而且一次一行。模式用于测试输入的规则,以确定动作是否将应用于这些输入行。动作可能很复杂,它由语句、函数和表达式组成。

        awk 脚本中的流程和控制如下图所示。

二、模式匹配

        当 awk 读入一输入行时,它试图匹配脚本中的每个模式匹配规则。只有与一个特定的模式匹配的输入行才能成为操作对象。如果没有指定操作,与模式相匹配的输入行将被打印出来(执行打印语句是一个默认操作)。

        awkscr 脚本文件内容如下:

# 测试一下是整数、字符串还是空行
/[0-9]+/ { print "That is an integer" }
/[A-Za-z]+/ { print "This is a string" }
/^$/ { print "This is a blank line." }

        三个正则表达式分别匹配整数、字符串和空行,如果一个输入行能够和任何一个模式匹配,那么就执行相应的打印语句。以下是一个使用标准输入的运行示例:

$ awk -f awkscr
4
That is an integer
t
This is a string
4T
That is an integer
This is a string
(回车)
This is a blank line.
44
That is an integer
CTRL-D
$

        注意,输入“4T”被标识为既是整数又是字符串。一行可以匹配一条或多条规则。

        awk 允许在程序的任何地方添加注释。注释以字符“#”开始,以换行符结束(awk 只支持单行注释)。对于较长的脚本,注释可以用来描述输入文件的预期的结构。如:

# blocklist.awk -- 打印表格中的名字和地址。
# fields: name, company, street, city, state and zip, phone

        将这些信息嵌入在程序脚本中很有用,因为除非输入文件的结构和所编写的程序脚本的结构一致,否则程序将无法工作。

三、记录和字段

        awk 假设它的输入是有结构的,而不只是一串无规则的字符。在最简单的情况下,它将每个输入行作为一条记录,而将由空格或制表符分隔的单词作为字段。用来分隔字段的字符被称为分隔符,连续的两个或多个空格和/或制表符被作为一个分隔符

1. 字段的引用和分离

        awk 允许使用字段操作符 $ 来指定字段。在该操作符后面跟一个数字或变量,用于标识字段的位置。“$1”表示第一个字段,“$2”表示第二个字段等等,“$0”表示整个输入记录。

        在下面的例子中,一个输入行形成包含三个字段的记录:在名字和姓之间有一个空格,在姓和电话号码之间有一个制表符。print 语句中分隔每个参数的逗号使得输出的各值之间有一个空格

$ awk '{ print $2, $1, $3 }' names
Robinson John 666-555-1111

        可以使用任何计算值为整数的表达式来表示一个字段,而不只是用数字和变量。

$ echo a b c d | awk 'BEGIN { one = 1; two = 2 }
> { print $(one + two) }'
c

        可以在命令行中使用 -F 选项改变字段分隔符。它后面跟着(或者紧跟,或者有空白)分隔符。下面的例子将字段分隔符改为制表符。

$ awk -F"\t" '{ print $2 }' names
666-555-1111

        “\t” 是表示一个实际的制表符的转义序列,它应由单引号或双引号包围着。

        awk 程序可以用块格式执行打印:

# blocklist.awk -- 用块格式打印姓名和地址
# 输入文件 -- 姓名、公司、街道、城市、州和邮编、电话
phone
{   print "" # output blank line
    print $1 # name
    print $2 # company
    print $3 # street
    print $4, $5 # city, state zip
}

        第一个 print 语句指定一个空串(""),这使得在输出中的记录由空行隔开(不加任何参数的 print 语句本身输出当前行)。names 文件内容如下:

John Robinson,Koren Inc.,978 4th Ave.,Boston,MA 01760,696-0987
Phyllis Chapman,GVE Corp.,34 Sea Drive,Amesbury,MA 01881,879-0900

        可以执行这个脚本并使用下面的命令指定字段分隔符为逗号,执行的命令行及其结果如下(print 语句自带换行符 \n):

$ awk -F, -f blocklist.awk names

John Robinson
Koren Inc.
978 4th Ave.
Boston MA 01760

Phyllis Chapman
GVE Corp.
34 Sea Drive
Amesbury MA 01881

        在脚本中指定字段分隔符是一个好习惯并且很方便,它可以用系统变量 FS 来定义。因为必须在读取第一个输入行之前执行,所以必须在由 BEGIN 规则控制的操作中指定这个变量。下面的脚本使用它来打印姓名和电话号码。

# phonelist.awk -- 打印姓名和电话号码
# 输入文件 -- 姓名、公司、街道、城市、州和邮编、电话
BEGIN { FS = "," } # 用逗号分隔字段

{ print $1 ", " $6 }

        脚本中使用空行改善可读性,在 print 语句的两个输出字段之间插入一个逗号和一个空格。执行的命令行及其结果如下:

$ awk -f phonelist.awk names
John Robinson, 696-0987
Phyllis Chapman, 879-0900

        使用(~)操作符可以测试一个字段的正则表达式,如仅选择居住在特定州的人名:

$5 ~ /MA/ { print $1 ", " $6 }

        可以使用组合符号(!~)来反转这个规则的意义,如下面的规则将与所有其第五个字段不包含“MA”的记录相匹配:

$5 !~ /MA/ { print $1 ", " $6 }

        下面的正则表达式查找一个区域代码:

$6 ~ /1?(-| )?\(?[0-9]+\)?( |-)?[0-9]+-[0-9]+/

        这个规则和下面的形式相匹配:

707-724-0000
(707) 724-0000
(707)724-0000
1-707-724-0000
1 707-724-0000
1(707)724-0000

2. 字段的划分

        FS 的默认值为一个空格,这是通常情况下 awk 将记录划分为字段的方法。在这种情况下,记录的前导空白字符和结尾空白字符(空格和/或制表符)将被忽略,并且字段用空格和/或制表符来分隔。

        还可以使用其它单个字符来分隔字段。当 FS 被设置为任何单个字符时,在这个字符出现的任何地方都将分隔出另外一个字段。如果出现两个连续的分隔符,在它们之间的字段值为空串。

        如果设置了不止一个字符作为字段分隔符,它将被作为一个正则表达式来解释。也就是说,字段分隔符将是与正则表达式匹配的“最左边最长的非空的不重叠的”子串。

        将每个制表符作为一个字段分隔符:

FS = "\t"

        用一个或多个制表符分隔字段:

FS = "\t+"

        使用一个正则表达式指定几个字符作为分隔符,在括号中的任何三个字符之一都可以被解释为字段分隔符:

FS = "[':\t]"

        一个具体的应用是查询哨兵模式 redis 的当前 master 的 name 和 IP。假设哨兵端口是 30001,从 info 命令返回的 master 信息如下:

$ redis-cli -p 30001 info
...
master0:name=redis6,status=ok,address=172.18.10.36:20006,slaves=2,sentinels=3
master1:name=redis22,status=ok,address=172.18.10.37:20022,slaves=2,sentinels=3
master2:name=redis23,status=ok,address=172.18.10.37:20023,slaves=2,sentinels=3
master3:name=redis2,status=ok,address=172.18.10.37:20002,slaves=2,sentinels=3
master4:name=redis5,status=ok,address=172.18.10.36:20005,slaves=2,sentinels=3

        这种情况下就可以将字段分隔符设置为逗号、冒号或等号,然后打印 master 的 name 和 IP:

$ redis-cli -p 30001 info | egrep master[0-9]+ | awk -F"[:,=]" '{print $3,$7}'
redis6 172.18.10.36
redis22 172.18.10.37
redis23 172.18.10.37
redis2 172.18.10.37
redis5 172.18.10.36

四、表达式

        一个表达式通过计算返回一个值。表达式由数字和字符串常量、变量、操作符、函数和正则表达式组成。常量有字符串和数字两种类型。字符串在表达式中必须用引号括起来。在字符串中可以使用下表中列出的转义序列。

序列

描述

\a

报警字符,通常是ASCII BEL 字符

\b

退格键

\f

走纸符

\n

换行符

\r

回车

\t

水平制表符

\v

垂直制表符

\ddd

将字符表示为1到3位八进制值

\xhex

将字符表示为十六进制值

\c

任何需要字面表示的字符c(例如,\" for ")

        变量是引用值的标识符。定义变量只需要为它定义一个名字并将数据赋给它即可。变量名区分大小写,只能由字母、数字、下划线组成,并且不能以数字开头。变量不必进行说明,不必告诉 awk 什么类型的数据将存储在一个变量中,awk 能够根据表达式的前后关系来选择合适的值。变量不必初始化,awk 自动将它们初始化为空字符串,如果作为数字,它的值为 0。下面是一些表达式的例子。

# 将数字常量 1 赋给变量 x
x = 1

# 将字符串“Hello”赋给变量 z
z = "Hello"

# 将两个字符串连接在一起,并将结果“HelloWorld”赋给变量 z。空格是字符串连接操作符。
z = "Hello" "World"

# 把当前输入记录的第一个字段的值赋予变量 w。美元符号($)是引用字段操作符。
w = $1

        多种操作符可以用在表达式中。下表列出了算术操作符。

操作符

描述

+

-

*

/

%

取模

^

取幂

**

取幂

        一旦变量被赋予了一个值,那么就可以用这个变量名来引用这个值。

# 将变量 x 的值和 1 相加并将结果赋给变量 y
y = x + 1

        赋值操作符组合了两个操作符。下表列出了 awk 表达式中的赋值操作符。

操作符

定义

++

变量加1

--

变量减1

+=

将加的结果赋给变量

-=

将减的结果赋给变量

*=

将乘的结果赋给变量

/=

将除的结果赋给变量

%=

将取模的结果赋给变量

^=

将取幂的结果赋给变量

**=

将取幂的结果赋给变量

        下面的例子用于计数一个文件中空行的数目。

# 统计空行数
/^$/ {print x += 1}

        虽然这里没有为变量 x 赋初值,但在遇到第一个空行之前它的值一直为 0。表达式 x += 1 在每次遇到空行时进行求值并将 x 的值加 1。print 语句打印表达式返回的值。因为在遇到每个空行时都执行 print 语句,所以得到了空行数的一个连续值。

        一种更简洁的写法是:

/^$/ {print ++x}

        如果不想在每次遇到空行时打印数值,而是计数所有空行后打印空行的总数,可以在 END 模式中放置 print 语句。

/^$/ {++x} END {print x}

        再看一个计算平均值的例子。输入文件的具体数据如下:

john 85 92 78 94 88
andrea 89 90 75 90 86
jasper 84 88 80 92 84

        在学生姓名后有 5 个成绩,下面的脚本将给出每个学生的平均成绩:

# 求 5 个成绩的平均值
{ total = $2 + $3 + $4 + $5 + $6; print $1, total / 5 }

        在样本数据上的执行结果如下:

$ awk '{ total = $2 + $3 + $4 + $5 + $6; print $1, total / 5 }' grades.txt 
john 87.4
andrea 86
jasper 85.6

五、系统变量

1. FS、OFS、RS、ORS

        FS 定义输入的字段分隔符。它的默认值为一个空格,表示 awk 可以用若干个空格和/或制表符来分隔字段。FS 可以被设置为任何单一字符或一个正则表达式。OFS 定义输出的字段分隔符,默认值为一个空格。RS 和 ORS 分别定义输入和输出的记录分隔符,默认值都是一个换行符。

        通常情况下希望在读入第一个输入行之前设置字段和记录分隔符的值,因此可以在 BEGIN 过程中定义它们。然而也可以在脚本的任何位置重新定义它们的值。在 POSIX awk 中为 FS 赋值不影响当前的输入行,它仅影响下一个输入行。

2. NF

        NF 定义为当前输入记录的字段个数。改变 NF 的值会有副作用。当 $0 和 NF 被改变时将产生令人费解的相互作用,尤其时当 NF 减小时。增加 NF 值会创建新的空字段,并重新建立 $0,字段由 OFS 的值来分隔。在 NF 减小的情况下,gawk 和 mawk 重新建立记录,超过新的 NF 值的字段被设置为一个空字符串。

        可以用 NF 来测试一个记录的字段个数是否与期望的相同,也可以用 NF 来引用每个记录的最后一个字段。使用“$”字段操作符和 NF 可以实现该引用。当不同记录的字段数可能不同时可以使用这种方法。

3. NR、FILENAME、FNR

        NR 为当前输入记录的编号,可以用来给列表中的记录编号。变量 FILENAME 中包含了当前输入的文件名。当应用多个输入文件时,变量 FNR 被用来表示与当前输入文件相关的当前记录的编号。下面是对计算平均值例子的修改,每行输出打印行号:

$ awk '{ total = $2 + $3 + $4 + $5 + $6; print NR ".", $1, total / 5 }' grades.txt
1. john 87.4
2. andrea 86
3. jasper 85.6

        当读入最后一行后,NR 的值是读入的输入记录的个数。它可用在 END 过程中以产生汇总值。下面是 phonelist.awk 脚本修改过的版本。

# phonelist.awk -- 打印姓名和电话号码
# 输入文件 -- 姓名、公司、街道、城市、州和邮编、电话
BEGIN { FS = ", *" } # 用逗号分隔字段
{ print $1 ", " $6 }
END { print ""
      print NR, "records processed." }

        这个程序修改了默认的字段分隔符,并使用 NR 打印记录的总数。注意这个程序使用了一个正则表达式来表示 FS 的值(逗号后面跟 0 个、1 个或多个空格)。程序执行的输出结果如下:

$ awk -f phonelist.awk names
John Robinson, 696-0987
Phyllis Chapman, 879-0900

2 records processed.

        当在 print 语句中用逗号分隔参数时,将产生输出字段分隔符(OFS)。默认情况下,逗号将在输出中产生一个空格(OFS 的默认值)。如果输入字段由制表符分隔,并且希望产生相同的输出时,可以使用 BEGIN 过程将 OFS 重定义为制表符。

4. CONVFMT、OFMT

        POSIX 增加了一个新的 CONVFMT,用来控制数字到字符串的转换。例如:

str = (5.5 + 3.2) " is a nice value"

        这里的数字表达式 5.5 + 3.2 的值,必须在它被用于字符串的连接之前转换为一个字符串。CONVFMT 控制这种转换,它的默认值为“%.6g”,这是一个用于浮点型数据的 printf 风格的格式说明。如将 CONVFMT 改为“%d”,将使所有的数字作为整数变为字符串。在 POSIX 标准之前,awk 使用 OFMT 来实现这个功能。OFMT 可以做相同的工作,但是控制执行 print 语句时进行数据的转换。注意,整数转换为字符串时总是作为整数看待,而不管 CONVFMT 和 OFMT 的值是什么

5. 两个例子

(1)处理多行记录

        本例演示如何读入一个记录,而记录中的每个字段都是单独一行。下面是一个记录样本:

John Robinson
Koren Inc.
978 Commonwealth Ave.
Boston
MA 01760
696-0987

Phyllis Chapman
GVE Corp.
34 Sea Drive
Amesbury
MA 01881
879-0900

        这个记录有 6 个字段,记录之间用空行分隔。为了处理这种包括多行数据的记录,可以将字段分隔符定义为换行符,用“\n”来表示,并将记录分隔符设置为空字符串,它代表一个空行。可以使用下面的脚本来打印第一个和最后一个字段:

# block.awk - 打印第一个和最后一个字段
# $1 = name; $NF = phone number
BEGIN { FS = "\n"; RS = "" }
{ print $1, $NF }

        例子的运行结果如下:

$ awk -f block.awk phones.block
John Robinson 696-0987
Phyllis Chapman 879-0900

        这两个字段输出在同一行是因为默认的输出字段分隔符(OFS)仍然是一个空格。如果希望将这些字段输出在不同行上,可以将 OFS 的值改为一个换行符。如果希望用空行将记录分开,可以将输出记录分隔符 ORS 设置为两个换行符,脚本如下:

# block.awk - 打印第一个和最后一个字段
# $1 = name; $NF = phone number
BEGIN { FS = "\n"; RS = ""; OFS = "\n"; ORS = "\n\n" }
{ print $1, $NF }

执行结果:

$ awk -f block.awk phones.block
John Robinson
696-0987

Phyllis Chapman
879-0900
 

        但这种实现有个问题是,最后一行后也有一个换行符。可以用下面的脚本解决这个问题:

# block.awk - 打印第一个和最后一个字段
# $1 = name; $NF = phone number
BEGIN { FS = "\n"; RS = ""; OFS = "\n"; ORS = "\n" }
{ if(NR>1) print ""; print $1, $NF }

执行结果:

$ awk -f block.awk phones.block
John Robinson
696-0987

Phyllis Chapman
879-0900

(2)支票簿的结算

        假设已经输入了一个如下的文本:

1000
125    Market           125.45
126    Hardware Store    34.95
127    Video Store        7.45
128    Book Store        14.32
129    Gasoline          16.10

        第一行是初始余额,其它每一行提供了单个支票的信息:支票号,使用支票的场所和支票金额。这 3 个字段由一至多个制表符分隔。需求是在每条支票信息下面增加一行,打印从当前余额中减去支票金额的剩余金额。脚本如下:

BEGIN { FS = "\t+" }
#1 第一条记录为初始余额
NR == 1 { print "Beginning Balance: \t" $1
        balance = $1
        next # 取下一条记录并重新开始执行
}

#2 处理每条支票信息,将当前余额与支票金额相减
{ print $1, $2, $3
  print balance -= $3
}

        运行这段程序得到的结果如下:

$ awk -f checkbook.awk checkbook.test
Beginning Balance:     1000
125 Market 125.45
874.55
126 Hardware Store 34.95
839.6
127 Video Store 7.45
832.15
128 Book Store 14.32
817.83
129 Gasoline 16.10
801.73

六、关系操作符和布尔操作符

        关系操作符用于在两个表达式之间进行比较。下表列出了关系操作符。

操作符

描述

<

小于

>

大于

<=

小于或等于

>=

大于或等于

==

相等的

!=

不等的

~

匹配

!~

不匹配

        关系表达式可用在模式中来控制特殊的操作。例如,只有具有 6 个字段的记录才被打印。

NF == 6 { print $1, $6 }

        关系表达式经常用在 if 语句中,通过计算来决定是否执行特殊的操作。当使用关系操作符 ~(匹配)或 !~(不匹配)时,右边的表达式可以是 awk 中的任意表达式,awk 将它作为一个字符串并用来指定一个正则表达式。例如,下面的语句将第五个字段的值与正则表达式“MA”比较,如果匹配则执行打印。

$5 ~ /MA/ { print $1 ", " $6 }

        正则表达式可以用变量来提供,例如可以用 state 来代替“/MA/”,并编写一个过程来定义 state 的值。

{state = "MA"}
$5 ~ state { print $1 ", " $6 }

        这使得程序代码更加通用,因为在脚本执行过程中可以动态改变模式。

        布尔操作符可以将一系列的比较组合起来。下表列出了布尔操作符。

操作符

定义

||

逻辑或

&&

逻辑与

!

逻辑非

        优先级从高到低依次为非、与、或,可以用圆括号改变优先规则。! 操作符与 awk 的 in 操作符结合起来,可以用来判断某个下标是否在数组中

        下面是几个处理 ls 命令输出的脚本。fls 文件内容如下:

ls -l $* | awk '{
    print $5, "\t", $9
}'

        这个脚本从命令行接收参数,打印匹配文件的字节数和文件名,如果没有命令行参数,则打印当前目录下所有文件的字节数和文件名。

$ fls test* sample*
323     sample
39      sampleLine
29      test
21      test1
13      test2
76      testsed

        filesum 文件内容如下:

ls -lR $* | awk '
# filesum: 列出文件和总的字节数
# 输入: 由命令“ls -l”生成的长列表

#1 输出列标题
BEGIN { sum=0; filenum=0; dirnum=0; linknum=0; print "BYTES", "\t", "FILE" }

#2 测试 9 个字段,文件以“-”开始
NF == 9 && /^-/ {
    sum += $5 # 累加文件大小
    ++filenum # 累加文件个数
    print $5, "\t", $9 # 打印大小和文件名
}

#3 测试 9 个字段,目录由“d”开始
NF == 9 && /^d/ {
    print "<dir>", "\t", $9 # 打印 <dir> 和名字
    ++dirnum # 累加目录个数
}

#4 测试 ls -lR 行 ./dir:
$1 ~ /^\..*:$/ {
    print "\t" $0 # 打印用制表符处理的行
}

#5 测试 11 个字段,符号链接由“l”开始
NF == 11 && /^l/ {
    print "<link>", "\t", $9 # 打印 <link> 和名字
    ++linknum # 累加目录个数
}

#6 所有工作已完成
END {
    # 打印所有文件总的大小和文件数目
    print "Total: ", sum, "bytes (" filenum " files, " dirnum " dirs, " linknum " links)"
}'

        BEGIN 过程初始化计数器变量的值,并给输出增加标题;主循环中增加文件数量、目录数量、符号链接数量、文件大小计数器;END 过程输出总计。输出如下所示:

$ filesum test* sample*
BYTES      FILE
323     sample
39      sampleLine
29      test
21      test1
13      test2
76      testsed
Total:  501 bytes (6 files)

        由 ls -l 产生的列表包含文件的 9 个字段。awk 在系统变量 NF 中给出了一个记录中的字段的个数。因此 #2 和 #3 测试 NF 是否等于 9,这可以避免与空行或表示块总量的行匹配。在 #2 中测试行的第一个位置的字符“-”,这表示一个文件,如果匹配则递增与之相关的文件计数器并累计文件的大小。在 #3 中测试一个目录,用“d”作为第一个字符来标识,相关的操作是在文件大小的位置上打印“<dir>”。

        #4 用于测试 ls -lR 产生的列表的特殊情况(如“./old:”)。可以使用正则表达式或关系表达式编写匹配这一行的模式:

  • NF == 1             如果字段个数等于 1 ...
  • /^\..*:$/               如果行以句点开头,其后跟有任意数量的字符,并且以冒号结束 ...
  • $1 ~ /^\..*:$/       如果字段 1 匹配正则表达式 ...

        这里采用最后一个,因为它似乎是最具有针对性的。相关操作只由一个 print 语句组成。

        #5 测试一个符号链接,用“l”作为第一个字符来标识,相关的操作是在文件大小的位置上打印“<link>”。注意符号链接的 ls -lR 输出包含 11 个字段。

        #6 是 END 模式,它的操作只被执行一次,用于打印出文件总的大小和数量。

七、格式化打印

        awk 提供的 printf 可以代替 print 语句,printf 是借用了 C 程序设计语言。printf 没有提供自动换行功能,必须明确地为它指定“\n”。printf 语句的完整语法由两部分组成:

printf ( format-expression [, arguments] )

        第一部分是一个用来描述格式的表达式,通常以引号括起的字符串常量的形式提供。第二部分是一个列表,例如变量名列表,它和格式说明相对应。在格式说明前面有一个百分号(%),而格式说明符号为下表列出的字符之一。两个主要的格式说明符是 s 和 d,s 表示字符串,d 表示十进制整数。

字符

定义

c

ASCII字符

d

十进制整数

i

十进制整数(在POSIX中增加)

e

浮点格式([-]d.precisione[+-]dd)

E

浮点格式([-]d.precisionE[+-]dd)

f

浮点格式([-]ddd.precision)

g

e或f的转换形式,长度最短,末尾的0被去掉

G

E或f的转换形式,长度最短,末尾的0被去掉

O

无符号的八进制

s

字符串

u

无符号的十进制

x

无符号的十六进制,用a-f表示10-15

X

无符号的十六进制,用A-F表示10-15

%

字面字符%

        下例在程序 filesum 的 #2 中用 printf 产生一个输出:

printf("%d\t%s\n", $5, $9)

        该语句用十进制整数格式输出 $5 的值,后面是制表符 \t,然后用字符串格式输出 $9 的值,最后输出一个换行符(\n)。对每个格式说明必须提供一个相应的参数

        printf 语句可以规定输出字段的宽度和对齐方式。一个格式表达式由 3 个可选的修饰符组成,跟在“%”后面,并出现在格式说明符之前。

%-width.precision format-specifier

        描述输出字段宽度的 width 是一个数值。当指定字段宽度时,这个字段的内容默认为向右对齐,可以指定“-”来设置左对齐,用空格来补齐。例如:

# 右对齐文本
$ awk 'BEGIN {printf("|%10s|\n", "hello")}' names
|     hello|

# 左对齐文本
$ awk 'BEGIN {printf("|%-10s|\n", "hello")}' names
|hello     |

        precision 修饰符用于控制十进制或浮点数小数点右边的数字位数。对于字符串型的值,它用于控制要打印的字符的最大数量。数值的默认 precision 值为“%.6g”。

        可以根据 print 或 printf 的参数列表中的值,动态指定宽度 width 和精度 precision。通过用 * 代替实际的值实现此功能,例如指定宽度是 5,精度为 3,要打印的值来自 myvar:

printf("%*.*g\n", 5, 3, myvar);

        print 语句输出数值的默认精度可以通过设置系统变量 OFMT 来改变。例如,如果使用 awk 打印报告,其中包含美元($)数值,可以将 OFMT 设置为“%.2f”。        使用格式表达式的完整语法可以解决 filesum 中各个字段和标题的对齐问题。上节的脚本中,在文件名前输出文件大小的一个原因,就是以这种顺序输出字段对齐的可能性更大,在很大程度上它们可以自己对齐。printf 的解决办法是固定输出字段的宽度。希望得到的最小字段宽度使得第二个字段在相同的位置开始,修改后的脚本如下:

ls -lR $* | awk '
# filesum: 列出文件和总的字节数
# 输入: 由命令“ls -l”生成的长列表

#1 输出列标题
BEGIN { sum=0; filenum=0; dirnum=0; linknum=0; printf("%-20s\t%10s\n", "FILE", "BYTES")}

#2 测试 9 个字段,文件以“-”开始
NF == 9 && /^-/ {
    sum += $5 # 累加文件大小
    ++filenum # 累加文件个数
    printf("%-20s\t%10d\n", $9, $5) # 打印大小和文件名
}

#3 测试 9 个字段,目录由“d”开始
NF == 9 && /^d/ {
    print "<dir>", "\t", $9 # 打印 <dir> 和名字
    ++dirnum # 累加目录个数
}

#4 测试 ls -lR 行 ./dir:
$1 ~ /^\..*:$/ && $1 !~ /^\.:$/ {
    print $0 # 打印用制表符处理的行
}

#5 测试 11 个字段,符号链接由“l”开始
NF == 11 && /^l/ {
    print "<link>", "\t", $9 # 打印 <link> 和名字
    ++linknum # 累加目录个数
}

#6 所有工作已完成
END {
    # 打印所有文件总的大小和文件数目
    printf("Total: %d bytes (%d files, %d dirs, %d links)\n", sum, filenum, dirnum, linknum)
}'

执行结果如下:

$ filesum test* sample*
FILE                         BYTES
sample                         323
sampleLine                      39
test                            29
test1                           21
test2                           13
testsed                         76
Total: 501 bytes (6 files, 0 dirs, 0 links)

八、向脚本传递参数

        在 awk 中可以向脚本传递参数,参数将值赋给一个变量,这个变量可以在 awk 脚本中访问。变量可以在命令行上设置,放在脚本后、文件名前:

awk 'script' var=value inputfile

        等号两边不允许出现空格。也可以用这个方法传递多个参数。例如,如果想在命令行定义变量 high 和 low,可以用下面的代码调用 awk:

$ awk -f scriptfile high=100 low=60 datafile

        在脚本中,这两个变量可以作为 awk 的任何变量来访问。如果要将这一脚本写入一个 shell 脚本的实现中,则可以以数值的形式传递 shell 的命令行参数(shell 按位置提供了命令行参数变量:$1 表示第一个参数,$2 表示第二个参数,依此类推)。例如 shell 脚本为:

awk -f scriptfile "high=$1" "low=$2" datafile

如果这个 shell 脚本被命名为 awket,则可以如下调用它:

$ awket 100 60

“100” 对应于 $1,其值将赋给变量 high。

另外,环境变量或命令的输出结果也可以作为变量的值来传递,例如

awk '{ ... }' directory=$cwd file1 ...
awk '{ ... }' directory=`pwd` file1 ...

        “$cwd”返回变量 cwd 的值。第二个例子使用反引号执行 pwd 命令,并将它的结果赋予变量 directory。

        也可以使用命令行参数定义 awk 系统变量,例如将输出分隔符定义为句点跟一个空格:

$ awk '{ print NR, $0 }' OFS='. ' names
1. Tom 656-5789
2. Dale 653-2133
3. Mary 543-1122
4. Joe 543-2211

        命令行参数的一个重要限制是它们在 BEGIN 过程中是不可用的。也就是说,直到首行输入完成以后它们才可用。这是因为从命令行传递的参数像文件名一样被处理。参阅下面的脚本,该脚本将变量 n 设置为一个命令行参数。

awk 'BEGIN { print n }
{
if (n == 1) print "Reading the first file"
if (n == 2) print "Reading the second file"
}' n=1 test n=2 test2

        这里有 4 个命令行参数:“n=1”、“test”、“n=2”和“test2”。在处理所有这些参数之前执行 BEGIN 过程,此时 n 还没有被赋值,因此会打印一个空行。然后,第一个参数为变量 n 赋初值 1,第二个参数提供了文件名。因此,对于 test 中的每一行,条件“n==1”都为真。在读完 test 中的所有行之后,计算第三个参数,并将 n 赋值为 2。最后,第四个参数提供了第二个文件名。这时在主过程中的条件“n==2”为真。

        以这种方法对参数求值的后果是不能用 BEGIN 过程测试或检验命令行提供的参数,只有当输入一行后它们才能使用。POSIX awk 提供了一个解决这个问题的方法,即在任何输入被读入前定义参数。用 -v 选项指定要在执行 BEGIN 过程之前得到变量赋值(即在读入第一个输入行之前)。-v 选项必须在一个命令行脚本前说明。例如,下列命令使用 -v 选项为多行记录设置记录分隔符。

$ awk -F"\n" -v RS="" '{ print }' phones.block

        每个传递给程序的变量赋值都需要一个不同的 -v 选项。

        awk 程序可以用于检索文本文件中的信息,文本文件的结构化越好处理越容易。下面是一个首字母缩写词列表的文本文件,字段分隔符是制表符。

$ cat acronyms
BASIC   Beginner's All-Purpose Symbolic Instruction Code
CICS    Customer Information Control System
COBOL   Common Business Oriented Language
DBMS    Data Base Management System
GIGO    Garbage In, Garbage Out
GIRL    Generalized Information Retrieval Language

        名为 acro 的 shell 脚本内容如下:

$ cat acro
#! /bin/bash
# 将 shell 的 $1 赋给 awk 的 search 变量
awk '$1 == search' search=$1 acronyms

        它将首字母缩写词作为输入并选择文件中对应的行作为输出。在 shell 命令行中的第一个参数($1)被赋值给变量 search,这个变量作为参数传递给 awk 程序。$1 == search 将参数作为字符串来检测,这里也可以将其写成一个正则表达式匹配 $1 ~ search。脚本执行结果如下所示:

$ acro CICS
CICS    Customer Information Control System

        下面的例子基于 awk 的多记录功能,将需要查找的字符串作为第一个命令行参数进行传递,命令行的第二个参数是文件名。shell 脚本 info1 内容如下:

awk 'BEGIN { FS = "\n"; RS = "" }
$0 ~ search { print $0 }' search=$1 $2

        例如要在多条目测试文件中 glitch.test 查找单词 “glitch”:

$ info1 glitch glitch.test
machine    Sun 3/75
           8 meg memory
           Prone to memory glitches
           more info
           more info


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2205502.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Alas配置更新器自动更新

之前我的部署方法有些问题&#xff0c;不应该下载源码再上传到服务器&#xff0c;这样会导致无法使用更新器&#xff0c;只能手动更新&#xff0c;比较麻烦&#xff0c;最近改用git方式获取源码&#xff0c;解决了无法使用更新器的问题&#xff0c;亲测有效 以下操作均基于雨云…

探索未知,惊喜连连 —— 盲盒小程序开发文案

在这个充满惊喜与好奇的时代&#xff0c;盲盒已经成为了一种独特的文化现象&#xff0c;它不仅仅是一种商品&#xff0c;更是一种心灵的慰藉和乐趣的源泉。为了满足广大盲盒爱好者的需求&#xff0c;我们精心打造了一款盲盒小程序&#xff0c;让惊喜触手可及&#xff0c;随时随…

数据库——创立表和库

数据库&#xff08;Database&#xff09;是一个用于存储、管理和检索数据的系统。它可以组织结构化数据&#xff0c;支持高效的存取和操作。数据库通常由一个数据库管理系统&#xff08;DBMS&#xff09;来支持&#xff0c;常见的DBMS包括&#xff1a; 关系数据库&#xff08;R…

如何正确的用引用作返回值?

错误一&#xff1a;引用作函数返回值&#xff0c;但函数中没用static修饰 下面代码输出什么结果&#xff1f; 输出结果&#xff1a; Q&#xff1a;ret应该是3&#xff0c;为什么再调用一次Add函数后&#xff0c;ret变成了7&#xff1f; 解释&#xff1a; ①&#xff1a;在第二…

steam上传游戏问题汇总

问题 首先是Library Logo 必须是png图片&#xff0c;还必须带上游戏名字你的宣传图不能使用游戏内部的截图。Library_Hero必须是空白的&#xff0c;不能有任何文字。他是和Library_logo合并在一起的。这个法律其实没必要填写。然后我错误的把EULA填写在这里了也报错了 如果你在…

《Linux从小白到高手》综合应用篇:详解Linux系统调优之服务器硬件优化

List item 本篇介绍Linux服务器硬件调优。硬件调优主要包括CPU、内存、磁盘、网络等关键硬件组。 1. CPU优化 选择适合的CPU&#xff1a; –根据应用需求选择多核、高频的CPU&#xff0c;以满足高并发和计算密集型任务的需求。CPU缓存优化&#xff1a; –确保CPU缓存&#x…

电容补偿功率因数不标准会怎样

电容补偿功率因数如果不符合标准&#xff0c;可能会对电力系统和设备运行造成多种负面影响。以下是功率因数补偿不当可能引发的问题&#xff1a; 1、欠补偿或不足补偿的影响 功率因数偏低&#xff1a;如果补偿量不足&#xff0c;功率因数未达到预期值&#xff08;通常在0.9至…

【C++】C++入门基础

一. 第一个C程序 #include<iostream> using namespace std;int main() {cout << "hello world" << endl;return 0; } 二.命名空间 1.namespace的价值 在C/C中&#xff0c;变量、函数和后⾯要学到的类都是⼤量存在的&#xff0c;这些变量、函数…

数据结构修炼——栈和队列是什么?如何实现?从入门到实战

目录 一、栈1 栈的概念及结构2 栈的实现 二、队列1 队列的概念及结构2 队列的实现 三、栈和队列OJ题1 有效的括号2 用队列实现栈3 用栈实现队列4 循环队列 四、概念选择题 一、栈 1 栈的概念及结构 栈&#xff1a;一种特殊的线性表。栈只允许在固定端进行插入和删除操作。进行…

专业的客服话术快捷回复软件

在当今快节奏的工作环境中&#xff0c;客服行业面临着前所未有的挑战。尤其是对于刚入行的新手小白来说&#xff0c;如何快速提升响应速度、保证回复质量&#xff0c;成为了他们亟待解决的问题。而今天&#xff0c;我要向大家推荐的这款“客服宝”快捷回复软件&#xff0c;就非…

advanced skeleton绑定模型无法返回修改按钮

有时候出现问题 adv插件没有Toggle Fit返回修改部分的按钮&#xff0c;这个通常是命名造成的 解决方式&#xff0c;把骨骼模型最上层的组名重改为Group&#xff0c;然后重开插件就行了 参考https://www.reddit.com/r/Maya/comments/xcgvcq/does_anyone_know_why_advanced_ske…

美畅物联丨破解养老难题:视频汇聚平台助力银发经济蓬勃发展

​一、引言 今天是重阳佳节&#xff0c;我们就来聊一聊视频汇聚平台在智慧养老中的应用与前景。 近年来&#xff0c;中国老龄化态势愈发严峻&#xff0c;已成为社会各界高度关注的重大课题。随着时间的推移&#xff0c;老年人口数量呈现出大规模增长的趋势&#xff0c;且独居老…

HE染色:揭示细胞细节,助力病理诊断|文献速递·24-10-11

好文分享 这篇文章是一篇关于苏木精-伊红&#xff08;H&E&#xff09;染色在诊断外科病理学中重要性的综述。 角色姓名单位第一作者John K. C. Chan香港特别行政区中国女王伊丽莎白医院 文章首先强调了尽管分子医学取得了显著进展&#xff0c;但显微镜仍然是外科病理学家日…

初阶数据结构(2):空间复杂度和复杂度算法题

Hello~,欢迎大家来到我的博客进行学习&#xff01; 目录 1.空间复杂度2. 常见复杂度对比3. 复杂度算法题3.1 旋转数组解法一解法二解法三 1.空间复杂度 根据摩尔定律&#xff0c;每隔一段时间晶体管的数量会增加一倍&#xff0c;即内存会增加&#xff0c;价格会降低。内存就不…

Windows CSC服务权限提升漏洞(CVE-2024-26229)

一、漏洞描述 csc.sys驱动程序中带有METHOD_NEITHER I/O控制代码的IOCTL地址验证不正确&#xff0c;导致任意地址写零漏洞。攻击者在Windows上获得较低权限的任意代码执行后&#xff0c;可以利用该漏洞将低权限提升至system权限。 二、漏洞详情 该漏洞源于 csc.sys 驱动程序…

Zilliz获Forrester报告全球第一;OB支持向量能力;Azure发布DiskANN;阿里云PG发布内置分析引擎

重要更新 1. Azure发布PostgreSQL向量索引扩展DiskANN&#xff0c;声称在构建HNSW/IVFFlat索引上&#xff0c;速度、精准度都超越pg_vector&#xff0c;并解决了pg_vector长期存在的偶发性返回错误结果的问题( [1] )。 2. 阿里云RDS PostgreSQL 发布AP加速引擎&#xff08;rds…

【python实操】python小程序之继承

引言 python小程序之继承 文章目录 引言一、继承1.1 概念1.1.1 基本语法1.1.2 关键点解释1.1.3 示例1.1.4 总结 1.2 题目1.3 代码1.4 代码解释1.4.1 类定义1.4.2 对象创建与方法调用1.4.3 总结 二、思考 一、继承 1.1 概念 python 中的继承是一种强大的机制&#xff0c;它允许…

如何防止webpack打包被逆向?

webpack打包后的js代码&#xff0c;看起来很混乱&#xff0c;似乎源码得到了保护&#xff1f; 不然&#xff0c;因为webpack只是将多个文件合并到了一起&#xff0c;并没有多少保护代码的功能。 比如下面这个例子&#xff0c;该网站的js文件是经webpack打包编译后生成的&…

TextView把其它控件挤出屏幕的处理办法

1.如果TextView后面的控件是紧挨着TextView的&#xff0c;可以给TextView添加maxWidth限制其最大长度 上有问题的布局代码 <?xml version"1.0" encoding"utf-8"?> <layout xmlns:android"http://schemas.android.com/apk/res/android&qu…

动态爬虫管理平台构建与实现(论文+源码)_kaic

摘 要 随着互联网的迅速发展&#xff0c;Web的信息量越来越大。人们往往通过搜索引擎去从互联网上搜索想要的信息&#xff0c;比如:百度&#xff0c;谷歌&#xff0c;搜狗等。这类搜索引擎称之为通用搜索引擎&#xff0c;其为所有的用户所需的内容&#xff0c;但目前互联网上的…