您现在的位置是:网站首页> 编程资料编程资料

shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)_linux shell_

2023-05-26 339人已围观

简介 shell脚本学习指南[四](Arnold Robbins & Nelson H.F. Beebe著)_linux shell_

回忆起一件事情:之前用linux寻找中文输入法的时候,在百度输入了fcitx,然后结果上边有个,您要找的是不是: 讽刺腾讯 。本来一直记不住这个输入法名字,不过以后哥就记住这个输入法的名字是怎么拼了,感谢百度。


第九章awk的惊人表现


awk的调用可以定义变量、提供程序并且指定输入文件,语法:

复制代码 代码如下:

awk [ -F fs ] [ -v var=value ... ] 'program' [ -- ] [ var=value ... ] [file(s) ]
awk [ -F fs ] [ -v var=value ... ] -f programfile [ -- ] [ var=value ... ] [ file(s) ]


短程序通常直接在命令行上提供,而比较长的程序则委托-f选项指定,可以重复使用此选项。如果命令行未指定文件名,则awk会从标准输入读取。 -- 是特殊选项,指出awk本身已经没有更进一步的命令行选项。任何接下来的选项都可被你的程序使用。
-F选项是用来重新定义默认字段分隔字符,且一般惯例将它作为第一个命令选项。紧接-F选项后的fs参数是一个正则表达式或是被提供作为下一个参数。字段分隔字符也可以设置使用内建变量FS所指定。如:
awk -F '\t' '{ ... }' files FS="[\f\v]" files
上边例子-F选项设置的值,应用到第一个文件组,而由FS指定的值,则应用到第二个组。初始化的-v选项必须放在命令行上直接给定的任何程序之前,他们会在程序启动前生效。在一命令行程序之后-v选项会被解释为一个文件名。在命令行上其他地方的初始化会在处理参数时完成,并且会带上文件名,如:
awk '{...}' Pass=1 *.tex Pass=2 *.tex
处理文件的列表两次,第一次Pass设为1,第二次为2。使用字符串值进行初始化无须用引号框起来,除非shell要求这样的引用以保护特殊字符或空白。

特殊文件名-(连字符)表示标准输入。大部分现代的awk实现(不包括POSIX)都认定特殊名称/dev/stdin为标准输入,即使主机操作系统不支持该文件名。同样:/dev/stderr与/dev/stdout可用于awk程序内,分别表示标准错误输出与标准输出。

一般awk命令模式或操作可省略一个,如果模式省略,则每条输入都被操作;如果操作省略,则默认操作为输出匹配模式的记录。虽然模式多半是数字或字符串表达式,不过awk以保留自BEGIN与END提供两种特殊模式。

与BEGIN关联的操作只会执行一次,在任何命令行文件或一般命令行赋值被处理之前,但是在任何开头的-v选项指定已完成之后。它大部分是用来处理程序所需要的任何特殊初始化工作。END操作也是只执行一次。用于所有输出数据已被处理完之后。BEGIN和END模式可以是任意顺序,可以存在awk程序内任何位置。当指定多个BEGIN或END模式,则他们将按照在awk程序里的顺序执行。

awk提供了标量与数组两种变量以保存数据、数字与字符串表达式,还提供了一些语句类型以处理数据:赋值、注释、条件、函数、输入、循环及输出。awk表达式许多功能与c语言相似。awk里注释是从#开始到行尾。跨行语句需要在结尾处加上反斜杠。

awk里的字符串常数是以引号定界,字符串可包含任何8bit的字符除了控制字符NUL以外。因为NUL在底层实现语言(C)里,扮演的是一个字符串中断字符的角色。awk字符串长度视内存而定。反斜杠转义序列允许非打印字符的表示。

awk提供了许多内建函数,可以在字符串上执行,之后再详细说,这会说两个length(string)返回string内的字符数。字符串的比较用的是传统的关系运算符:==、!=、<、<=、>、>=。比较不同长度的字符串,且其中一个字符串为另一个的初始子字符串时,较短的定义为小于较长的那个。在shell里字符串连接可以直接进行,不需要连接符号。

awk功能强大的地方大多来自于它对正则表达式的支持。有两个运算符:~(匹配)与!~(不匹配)让awk更容易使用正则表达式:"ABC" ~ "^[A-Z]+$"结果为真,正则表达式常量可以用引号或斜杠加以定界:/^[A-Z]+$/。注意如果有字面意义的符号,需要反斜杠来转义。

awk里的数字,都以双精度浮点值表示,如1/32 写成0.03125、3.125e-2等,awk里没有提供字符串转数字的函数,不过想做到也很简单,只要加个零到字符串里,如:s = "123" , n = 0 + s 。这样123便赋值给n了。一般"+123ABC"转化为123,而"ABC123"与""都转化为0。即使awk里所有的数值运算都是在浮点算术内完成,整数值还是可以表示的,只要值不太大,这个值限定在53位,即2^53即9千万亿的样子。awk的数值运算符没有位运算符,多一个指数运算符(^ 或 ** 或 **=,但是避免使用**和*=,它不是POSIX awk的一部分)它是右结合性的,且与赋值运算符是仅有的右结合性运算符。比如a^b^c^d运算顺序是a^(b^(c^d))。awk里的取余运算测试了 5 % 3 是2 ; 5 % -3 是2; -5 % 3 是-2; -5 % -3是-2;发现取余的结果取决于被取余的数的正负。还有一个内建函数:
int(x) 对x取整
rand 取 0到1之间的随机数
srand(x) 设置x为rand的新输入值
cos(x) 给出x的余弦值
sin(x) 给出x的正弦值
atan2(x,y) 给出y/x的正切值
exp(x) 给出e的x次幂
log(x) 给出x的常用对数值(基为e)
sqrt(x) 给出x的正平方根值
exit(x) 结束awk程序,若有x值,则返回x,否则返回0.
index(s,t) 返回t在s中的第一个开始位置,如t不是s的子串,则返回0]
length(x) 求x的长度(字符个数)
substr(s,x,y) 在字符串s中取得从x个字符开始的长度为y的子字符串.

awk内置字符串函数
gsub(r,s) 在整个$0中用s替代r
gsub(r,s,t) 在整个t中用s替代r
index(s,t) 返回s中字符串t的第一位置
length(s) 返回s长度
match(s,r) 测试s是否包含匹配r的字符串
split(s,a,fs) 在fs上将s分成序列a
sprint(fmt,exp) 返回经fmt格式化后的exp
sub(r,s) 用$0中最左边最长的子串代替s
substr(s,p) 返回字符串s中从p开始的后缀部分
substr(s,p,n) 返回字符串s中从p开始长度为n的后缀部分

awk提供许多内建变量,都是大写名称,时常用到的几个有:
FILENAME 当前输入文件的名称
FNR 当前输入文件的记录数
FS 字段分隔字符(正则表达式)(默认为:" ")
NF 当前记录的字段数
NR 在工作中的记录数
OFS 输出字段分隔字符(默认为:" ")
ORS 输出记录分隔字符(默认为:"\n")
RS 输入记录分隔字符(仅用于gawk与mawk里的正则表达式)(默认为:"\n")

awk允许的测试:
x==y x等于y?
x!=y x不等于y?
x>y x大于y?
x>=y x大于或等于y?
x x<=y x小于或等于y?
x~re x匹配正则表达式re?
x!~re x不匹配正则表达式re?

awk的操作符
= 、+=、 -=、 *= 、/= 、 %=
|| && > >= < <= == != ~ !~
xy (字符串连结,'x''y'变成"xy")
+ - * / % ++ --

awk没有提供位操作符,但是提供了相关的函数:
and(v1, v2) Return the bitwise AND of the values provided by v1 and v2.
compl(val) Return the bitwise complement of val.
lshift(val, count) Return the value of val, shifted left by count bits.
or(v1, v2) Return the bitwise OR of the values provided by v1 and v2.
rshift(val, count) Return the value of val, shifted right by count bits.
xor(v1, v2) Return the bitwise XOR of the values provided by v1 and v2.

awk的数组变量允许数组名称之后,以方括号将任意数字或字符串表达式括起来作为索引。以任意值为索引的数组称之为关联数组。awk将应用于数组中,允许查找插入和删除等操作,在一定时间内完成,与存储多少项目无关。(说了这么多其实就是hash数组)。delete array[index]会从数组中删除元素。delete array删除整个数组。awk数组还可以这么用:
print maildrop[53, "Oak Lane", "T4Q 7XV"]
print maildrop["53" SUBSEP "Oak Lane" SUBSEP "T4Q 7XV"]
print maildrop["53\034Oak Lane", "T4Q 7XV"]
print maildrop["53\034Oak Lane\034T4Q 7XV"]
以上输出结果都是一样的。内建变量SUBSEP默认值是\034,可以更改它。如果稍后更改了SUBSEP的值,将会使已经存储数据的索引失效,所以SUBSEP其实应该在每个程序只设置一次,在BEGIN操作里。

awk对于命令行的自动化处理,意味着awk程序几乎不需要关心他们自己。awk通过内建变量ARGC(参数计数)与ARGV(参数向量,或参数值),让命令行参数可用。给出例子说明其用法:

复制代码 代码如下:

$ cat >showargs.awk
 BEGIN{
       print "ARGC = ",ARGC
       for ( k = 0 ; k < ARGC ; k++)
          print "ARGV[" k "] = [" ARGV[k] "]"
   }

$ awk -v One=1 -v Two=2 -f showargs.awk Three=3 file1 Four=4 file2 file3
ARGC =  6
ARGV[0] = [awk]
ARGV[1] = [Three=3]
ARGV[2] = [file1]
ARGV[3] = [Four=4]
ARGV[4] = [file2]
ARGV[5] = [file3]

正如C/C++中,参数存储在数组项目0、1....、ARGC-1中,第0个项目是awk程序本身的名称。不过与-f 和 -v选项结合性的参数是不可使用的。同样的,任何命令行程序也不可使用:

复制代码 代码如下:

$ awk 'BEGIN{for(k=0;kprint "ARGV["k"] = ["ARGV[k]"]"}' a b c
ARGV[0] = [awk]
ARGV[1] = [a]
ARGV[2] = [b]
ARGV[3] = [c][/c][/c]

是否需要显示在程序名称里的目录路径,则看实际情况而定。awk程序可修改ARGC和ARGV,注意保持俩个的一致性。
awk一见到参数含有程序内容或是特殊--选项时,它会立即停止将参数解释为选项。任何接下来的看起来像是选项的参数,都必须由你的程序处理,并接着从ARGV中被删除或设置为空字符串。

awk提供访问内建数组ENVIRON中所有的环境变量:

复制代码 代码如下:

$ awk 'BEGIN{ print ENVIRON["HOME"]; print ENVIRON["USER"]}'
/home/administrator
administrator

ENVIRON数组并无特别之处,可以随意修改删除。然而,POSIX要求子进程继承awk启动时生效的环境,而我们也发现,在现行实现下,并无法将对于ENVIRON数组的变更传递给子进程或者内建函数。特别地,这是指你无法通过对EVNIRON["LC_ALL"]的更改控制字符串函数,例如tolower(),在特定locale下的行为模式。因此你应将ENVIRON看成一个只读数组。如果要控制子进程的locale,则可通过在命令行字符串里设置适合的环境变量达成。如:
system("env LC_ALL=es_Es sort infile > outfile")#以Spanish的locale排序文件。
system()函数稍后说明。

模式与操作构成awk程序的核心。模式为真则进行操作。一般模式是正则表达式,就会被拿来与整个输入记录进行匹配,比如:
NF == 0 #选定空记录
NF > 3 #选定拥有三个字段以上的记录
NR < 5 #选定第一到第四条记录
$1 ~ /jones/ #选定字段1中有jones的记录
/[xX][mM][lL]/ #忽略大小写选定含xml的记录

awk在匹配功能上,还可以使用范围表达式,以逗点隔开的两个表达式。比如:
(FNR == 3) , (FNR == 10) #选定每个输入文件按里记录3到10
/<[Hh][Tt][Mm][Ll]>/ , /<\/[Hh][Tt][Mm][Ll]>/ #选定html文件里的主体

在BEGIN操作里,FILENAME、FNR、NF与NR初始都未定义;引用到他们时,会返回null。

通过模式的匹配,就要把为真记录的传给操作。给出一些实例:
#unix单词计数程序wc:
awk '{ C += length($0) + 1 ; W += NF } END { print NR, W, C}'
注意:模式/操作组并不需要以换行字符分隔,一般换行是为了阅读方便。我们也可以使用BEGIN{ C = W =0} 来初始化,但是awk具有默认的初始化保证。
#将原始数据值及他们的对数打印为单栏数据文件:
awk ' { print $1 , log($1) }' file(s)
#要从文本文件里随机打印5%行左右的样本:
awk 'rand() < 0.05 ' file(s)
#以空白分隔字段的表格中,报告第n栏的和:
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum } ' file(s)
#产生字段n栏的平均值
awk -v COLUMN=n '{ sum += $COLUMN } END { print sum / NR } ' file(s)
#统计文件最后一个字段的总数
awk '{ sum += $NF; print $0 , sum }' file(s)
#三种查找文件内文本的方式:
egrep 'pattern|pattern' file(s)
awk '/pattern|pattern/' file(s)
awk '/pattern|pattern/ { print FILENAME ":" FNR ":" $0 }' file(s)
#仅查找100-150行 的匹配信息
sed -n -e 100,150p -s file(s) | egrep 'pattern'
awk '(100<=FNR)&&(FNR<=150)&& /pattern/ { print FILENAME":"FNR":"$0}' file(s)
#要在四栏表格里调换二三栏,假设制表符分隔:
awk -F'\t' -v OFS='\t' '{ print $1,$3,$2,$4}' old > new
awk 'BEGIN {FS=OFS='\t' } {print $1,$3,$2,$4 }' old > new
awk -F'\t' '{ print $1 "\t"$3"\t"$2"\t"$4} ' old > new
#将格栏分隔符由制表符替换成&:
sed -e 's/\t/\&/g' file(s)
awk 'BEGIN { FS="\t"; OFS="&" } {$1 = $1; print }' file(s)
#删除排序后的重复行:
sort file(s) | uniq
sort file(s) | awk 'Last != $0 { print } {Last = $0} '
#将回车字符/换行符的行终结,一致转换为以换行字符为行终结:
sed -e 's/\r$//' file(s)
sed -e 's/^M$//' file(s)
mawk 'BEGIN { RS="\r\n" } { print } ' file(s)
#找出长度超过72个字符的行:
egrep -n '^.{73,}' file(s)
awk 'length($0) > 72 { print FILENAME":"FNR":"$0}' file(s)

awk支持语句的连续执行。支持条件语句,if else 类似C语言,支持循环 while(){} 或do{} while()或for( ; ; ){] 类似c语言。还有一个for(key in array) { } 。
如 awk 'BEGIN { for( x=0; x<=1;x+=0.05) print x}' 。虽然很多类似C,但是注意awk中是缺乏逗点运算符的。循环同样可以使用break和continue 。

awk直接处理命令行上标明的输入文件,一般不用用户自己打开与处理文件,但是也可以通过awk的getline语句做这些事情。用法:
getline 从当前输入文件读取下一条记录存入$0,并更新NF、NR、FNR
getline var 从当前输入文件中,读取下一条记录存入var并更新NR、FNR
getline < file 从fle中读取下一条记录,存入$0,并更新NF
getline var < file 从file读取下条记录存入var
cmd | getline 从外部命令cmd读取下条记录存入$0,并更新NF
cmd | getline var 从外部命令读取下条记录,存入var
如果像确保来自控制终端的输入则:getline var < "/dev/tty"

在awk里可以通过管道与外部的shell命令混写:

复制代码 代码如下:

tmpfile = "/tmp/telephone.tmp"
comman = "sort > " tmpfile
for ( name in telephone)
    print name "\t" telephone[name] | command
close (command)
while((getline < tmpfile) > 0)
   print
close(tmpfile)

close可以关闭打开的文件以解约可用资源。awk里也没有排序函数,以为它只需要复制功能强大的sort命令即可。

getline语句以及awk管道里的输出重定向都可与外部程序通信,system(command)函数提供的是第三种方式:其返回值是命令的退出码。所以上边的例子可以写成:

复制代码 代码如下:

tmpfile = "/tmp/telephone.tmp"
for ( name in telephone)
    print name "\t" telephone[name] | > tmpfile
close (tmpfile)
system("sort < " tmpfile)
while((getline < tmpfile) > 0)
   print
close(tmpfile)

对于被system()执行的命令并

-六神源码网