Linux文本处理三剑客之awk学习笔记06:输出操作


输出操作

awk可以通过print或者printf将数据输出到标准输出或者重定向到文件中。

print

print我们已经使用过很多次了。其实它本质是一个输出函数,即有小括号。

print (elem1,elem2,elem3...)
print elem1,elem2,elem3...

输出的每一个东西,我们可以称之为字段/元素,在书写时使用逗号分隔多个字段。一个print当中的所有字段组合起来就是一条记录。在输出的时候,print会使用预定义变量OFS来分隔多个字段,多个print语句的输出就是多条记录,它们之间使用预定义变量ORS来分隔。这也是为什么默认情况下,print输出的多个字段使用空格分隔,多个print语句的输出结果使用换行分隔的原因了。

# awk 'BEGIN{OFS=":";ORS="!!!\n";print "a","b","c";print "d","e","f"}'
a:b:c!!!
d:e:f!!!
# awk 'BEGIN{OFS=":";ORS="!!!\n";print("a","b","c");print("d","e","f")}'
a:b:c!!!
d:e:f!!!

每个输出的字段都会被print转换成字符串格式后输出,即便是纯数字。若不是纯数字,则要将输出的字段使用双引号包裹,否则会被认为是变量而进行变量替换。

# awk 'BEGIN{print abc}'

# awk 'BEGIN{print "abc"}'
abc

如果输出的内容包含特殊符号(例如重定向字符),则需要使用小括号包裹。

[root@c7-server ~]# ls -l 4
ls: cannot access 4: No such file or directory
[root@c7-server ~]# awk 'BEGIN{print 5>4}'
[root@c7-server ~]# ls -l 4
-rw-r--r-- 1 root root 2 Jan  9 20:28 4
[root@c7-server ~]# cat 4
5
[root@c7-server ~]# awk 'BEGIN{print(5>4)}'
1

print除了在输出数字时会将其转换为字符串再输出以外,当输出小数的时候,会按照预定义变量OFMT(Output ForMaT)定义的格式进行格式化后输出。该变量的值定义的格式与printf()定义的格式相同。格式化输出的格式在讲解printf()时会再详述。OFMT的默认值为“%.6g”表示整数部分+小数部分最多6位。

# awk 'BEGIN{print 3.12432623}'
3.12433

同理,可修改OFMT来修改print对于小数的输出行为。

# awk 'BEGIN{OFMT="%.2f";print 3.99989}'
4.00
# awk 'BEGIN{OFMT="%d";print 3.99989}'
3
# awk 'BEGIN{OFMT="%.0f";print 3.99989}'
4

printf

print的输出格式是由ORS和OFS所决定的,而printf是格式化输出,其输出格式由其自身所决定。

printf format,item1,item2,...

format:指定了要输出的格式,其中包含了多个“%+控制字母”的东西表示占位符,其会被format之后出现的各个item所依次(默认)替换。

# awk 'BEGIN{printf "My name is %s,My age is %d\n","alongdidi",29}'
My name is alongdidi,My age is 29

如果format当中没有任何占位符的话,那么直接打印format字面内容,format后续的所有item都可以无视。

# awk 'BEGIN{printf "alongdidi\n",29,"male","single"}'
alongdidi

常见的占位符(%+控制字母)。

%c:根据编码表(ASCII)打印字符。

# awk 'BEGIN{printf "%c\n",65}'
A

%d, %i:打印十进制整数,如果遇到小数则直接丢弃小数部分保留整数部分。

# awk 'BEGIN{printf "%d\n%d\n",30,30.3}'
30
30

%e, %E:使用科学计数法打印一个数字。

# awk 'BEGIN{printf "%e\n",10000000000}'
1.000000e+10

%f:打印浮点数(小数)。小数位足够长时会取近似值。

# awk 'BEGIN{printf "%f\n",3.1415926}'
3.141593

%g, %G:使用科学计数法或者浮点数来显示,具体使用哪种取决于这两种表示法谁占用的字符数较少。

# awk 'BEGIN{printf "%g\n",3.14}'
3.14
# awk 'BEGIN{printf "%g\n",33333333333333333333333.14}'
3.33333e+22

%o:将十进制数转换成八进制数并以字符串格式打印出来。

# awk 'BEGIN{printf "%o\n",8}'
10

%x:将十进制数转换成十六进制数并以字符串格式打印出来。

# awk 'BEGIN{printf "%x\n",16}'
10

%s:打印字符串。

%%:打印百分号。

# awk 'BEGIN{printf "%d%%\n",100}'
100%

还可以使用修饰符,修饰符位于%和控制字母之间。

N$:N是一个整数。默认情况下各item按位(出现次序)与format中的占位符替换。而该修饰符可以改变这个顺序。

# awk 'BEGIN{printf "%s %s %s\n","I","love","u"}'
I love u
# awk 'BEGIN{printf "%3$s %2$s %1$s\n","I","love","u"}'
u love I

可重复。

# awk 'BEGIN{printf "%2$s %2$s %2$s\n","I","love","u"}'
love love love

width:指定最短字符宽度。宽度不足时使用空格(这里使用下划线“_”表示)在字符串左边填充,超出宽度则该修饰符无效。

# awk 'BEGIN{printf "%4s\n","foo"}'
_foo
# awk 'BEGIN{printf "%4s\n","foobar"}'
foobar

-:减号。默认情况下字符串右对齐(所以空格在左边填充),该修饰符使其左对齐。

# awk 'BEGIN{printf "%5s\n%-5s\n","ni","ni"}'
___ni
ni___

space:空格。针对于数值表示其正负性。如果是正数在其前添加一个空格,对于负数则不改变。

# awk 'BEGIN{printf "% d\n% d\n",1,-1}'
_1
-1

+:加号。与空格功能相同,使用正负号来表示正负数。

# awk 'BEGIN{printf "%+d\n%+d\n",1,-1}'
+1
-1

#:换一种表示形式,例如在八进制前加上“0”,在十六进制前加上“0x”。

# awk 'BEGIN{printf "%#o\n%#x\n",8,16}'
010
0x10

0:数字0,当需要使用空格填充时以零代替,针对数字。0只会在确保不会改变数字值大小的情况才使用。

# awk 'BEGIN{printf "%05d\n",3}'
00003
# awk 'BEGIN{printf "%-05d\n",3}'
3    # 在左对齐情况下,如果在数字3的右边补齐0会改变数字值大小。

':单引号。当系统的区域设置(locale)支持时,可以以千分位表示法表示。这里需要注意为了使单引号修饰符正确被解释,原本awk的单引号用双引号替代,原本printf的双引号使用反斜线转义。

# awk 'BEGIN{printf "%d\n",1000000000}'
1000000000
# awk "BEGIN{printf \"%'d\n\",1000000000}"
1,000,000,000
# LC_ALL=C awk "BEGIN{printf \"%'d\n\",1000000000}"
1000000000

.prec:一个小数点后面跟着一个非负整数来表示精度(precision),不同的控制字符下精度有不同的含义。

%d, %i, %o, %x, %X:至少打印多少个数字。不够以0填充,超出没事。

# awk 'BEGIN{printf "%.5d\n",300}'
00300
# awk 'BEGIN{printf "%.5d\n",300000}'
300000

%e, %E, %f, %F:小数点后保留多少位小数。超出则取近似值,不够则以0填充。

# awk 'BEGIN{printf "%.3f\n",3.1415926}'
3.142
# awk 'BEGIN{printf "%.9f\n",3.1415926}'
3.141592600

%s:指定最长字符宽度,超出部分丢弃。

# awk 'BEGIN{printf "%.3s\n","alongdidi"}'
alo
# awk 'BEGIN{printf "%.3s\n","a"}'
a

%g, %G:最大有效数字位数(整数+小数)。超出部分会丢弃,如何判断丢弃的部分取决于如何丢弃会使得结果更接近于原值。

# awk 'BEGIN{printf "%.3g\n",3.1415926}'
3.14
# awk 'BEGIN{printf "%.3g\n",333.14}'
333
# awk 'BEGIN{printf "%.3g\n",33333333.14}'
3.33e+07

sprintf

printf将格式化后的结果输出而sprintf将格式化后的结果返回。因此可以将返回值赋值给变量或者使用print将返回值输出。

sprintf是一个字符串函数。

# awk 'BEGIN{sprintf("%s","alongdidi")}'
# awk 'BEGIN{print sprintf("%s","alongdidi")}'
alongdidi
# awk 'BEGIN{a=sprintf("%s","alongdidi");print a}'
alongdidi

重定向输出

awk支持四种重定向输出。

1、覆盖重定向输出至文件。

print[f] "something" > "filename"

类似于bash中的覆盖重定向。如果文件不存在则创建,如果文件存在的话则先清空原文件的数据再重定向数据。

对于同一个文件,awk只在首次操作文件时才将其打开。因此,该重定向会将随后的数据追加至文件,这和bash的覆盖重定向机制不同。

# ls -l name.txt
ls: cannot access name.txt: No such file or directory
# awk 'NR>1{print $2 > "name.txt"}' a.txt 
# cat name.txt 
Bob
... ...
Bruce    # 除了第一个$2,后续的每一个$2都是以追加的形式。

2、追加重定向输出至文件。

print[f] "something" >> "filename"

与覆盖重定向的区别在于,当文件存在时,追加重定向不会清空文件,每一条记录都追加。

# cat redirection.txt
abc
def
#    上面是测试文件,自行尝试执行对比区别。
awk 'BEGIN{for(i=1;i<=3;i++){print "alongdidi">>"redirection.txt"}}'
awk 'BEGIN{for(i=1;i<=3;i++){print "alongdidi">"redirection.txt"}}'

3、通过管道重定向输出其他命令。

print[f] "something" | shellCmd

示例。

awk 'NR>1{print $2>"name.unsort";cmd="sort>name.sort";print $2|cmd}END{close(cmd)}' a.txt

4、重定向输出给协程。

print[f] "something" |& shellCmd

至于重定向输入,我们在讲解getline时基本都有涉及到了。

stdin, stdout, stderr

awk在重定向时可以直接使用/dev/stdin、/dev/stdout和/dev/stderr。

awk 'BEGIN{print "something OK" > "/dev/stdout"}'
awk 'BEGIN{print "something OK" > "/dev/stderr"}'
awk 'BEGIN{getline<"/dev/stdin";print $0}'
awk 'BEGIN{print "something OK" | "cat >&2"}'

使用文件描述符重定向数据。

exec 4<> a.txt    # 为文件a.txt分配一个文件描述符4
awk 'BEGIN{while(getline<"/dev/fd/4"){print}}'    # 从文件描述符4中读取数据