计算化学公社

 找回密码 Forget password
 注册 Register
Views: 1717|回复 Reply: 1
打印 Print 上一主题 Last thread 下一主题 Next thread

[综合交流] shell编程-awk语法小结

[复制链接 Copy URL]

432

帖子

11

威望

3430

eV
积分
4082

Level 6 (一方通行)

跳转到指定楼层 Go to specific reply
楼主
本帖最后由 丁越 于 2024-6-12 21:39 编辑

shell 编程—awk语法小结


一、前言
  这两天遇到一个给原子添加后缀的需求,比如把“C”后面添加"_1"的后缀为"C_1",以便区分体系不同位置的C原子然后为其赋予不同类型的基组。起初利用简单的shell语法虽然能实现这一需求,但是总觉得可以用awk编程做一些改进,使其变得更为精简。于是借着写脚本之余把awk编程的一些基本语法回顾了一下,在此做个小总结以方便查阅使用一些基本语法。更复杂的awk编程请出门左转向"shell小王子" 钟叔(ggdh)拜师学艺(俺就不献丑啦)。

二、基础用法
  awk基本语法结构如下所示:
awk [选项参数] 'pattern1 {action1} pattern2 {action2}…' file

  awk后面接两个单引号并加上大括号括来设置对数据进行的操作,file是所要处理的文本文件,但awk也可以接受前面命令的标准输出。awk主要是处理每一行 (也叫每一条记录) 的字段内的数据,默认的字段分隔符是"tab"或者"空格"。

常用的选项参数:
-F: 指定文件内容的分隔符,等价语法可以用BEGIN{FS="分隔符"},但是一般情况下前者使用更加方便所以推荐使用。
-v: 设置用户自定义变量,但常用于读取外部变量到awk中。比如自定义变量 awk -v num=1 'BEGIN{print num}',这里自定义了num变量值为1,注意awk中的内部变量不需要用“$”符号引用,其次字符串内容一定要用" "括起来,否则会误把字符串当作变量处理。
再比如读取外部变量:awk -v myvar="${global_var}" '{action}' file。假如有多个变量要读取那就写多次-v myvar="${global_var}"。

常用的pattern:
pattern的内容可以是以下这些:
* 正则表达式,其内容要放在两个斜杠之间,比如"/^[0-9]+/"。
* $n,表示所处理的当前行中的第几个字段, 其中$0表示整个所处理的当前行。
*  一些运算符,比如逻辑运算符与("&&"),或("||"), 非("!");判断正则表达式匹配和不匹配的"~"和"!~";关系运算符"<, >, >=, !=, =="。
* BEGIN{ } 和END{ }分别表示处理file之前和之后所要执行的动作

下面看几个简单的小例子来帮助理解。假如现在有一个名为test.dat文件,其文件内容如下:
  1. Alpah   30   50   90
  2. beta     35  55   88
  3. Gamma  55      88      100
  4. tHeTa   ww  44  u8
复制代码
例1:我需要找出第一个字段是“Beta”的这一行
  1. awk '$1=="Beta"' test.dat
复制代码
输出结果是:beta    35    55      88

例2:我需要找出第一个字段中名字全部是小写字母的一行
  1. awk '$1 ~ /^[a-z]+$/' test.dat
复制代码
输出结果是:beta    35    55      88
其中,正则表达式中“^”表示开头,“$”表示末尾,“+”至少匹配一次,”[ ]”表示匹配方括号里面字符之一

例3:我需要找到第三个字段中值大于50的所有行
  1. awk '$3 > 50' test.dat
复制代码
输出结果是:beta    35    55      88
           Gamma   55      88    100

例4:输出整个文件内容,但在处理test.dat文件前用BEGIN 打印“File processing is beginning..”, 处理完后打印“All work has been done~”
结果如下所示:
  1. awk 'BEGIN{print "File processing is beginning.."}{print}END{print "All work has been done~"}' test.dat
复制代码
结果如下所示:
File processing is beginning..
Alpah 30      50      90
beta  35      55      88
Gamma 55      88      100
tHeTa ww      44     u8
All work has been done~

常用的内置变量:
$n: 表示第几个字段。 其中$0表示整个所处理的当前行。
FILENAME: 当前文件名。
NR: 当前awk已经处理过的记录数,也就是处理到第几行,默认从1开始。假如有多个文件(awk后面可以接多个file),那么这个处理过的记录数就会一直累积直至最后一个文件的最后一行。
FNR: 各文件分别计数的已经处理过的记录数(行号,从1开始)。也就是上面NR提到的当有多个文件时,每次开始处理新的文件FNR的记录数就会从1重新开始计数。
NF: 每一行的字段数目,也就是被分隔符隔开后得到的列的数目。
FS: 字段分隔符,默认空格,支持正则表达式。
RS:记录分隔符,默认是“\n”。
OFS、ORS:这两个分别控制输出字段和记录的分隔符,但是一般用处不大,习惯上用print或者printf控制输出格式。

了解了上述这些后,下面为刚才的test.dat文件各字段内容添加标签“name math writing game”,并且只输出前三行的内容,最后打印各个字段的均值:
  1. awk 'BEGIN{printf "%s\t%s\t%s\t%s\n","name","math","writing","game"}NR<4{m2+=$2;m3+=$3;m4+=$4;print}END{printf "%s\t%d\t%d\t%d\n","Mean",m2/NR,m3/NR,m4/NR}'test.dat
复制代码
得到下面结果:
name  math    writing game
Alpah 30      50      90
beta  35      55      88
Gamma 55      88      100
Mean  24      39      56

常用的函数:
* 数学函数
sin(x)、cos(x)、atan2(y,x)、exp(x)、log(x)(返回x的自然对数)、sqrt(x) (返回x的平方根)、int(x) (将x取整)
* 字符串操作函数
sub(regexp, replacement [,string]): 将正则表达式”regexp”匹配的字串用”replacement”替换,注意只替换一次。第三个参数”String”是可缺省参数,默认是$0。
gsub(regexp, replacement [,string]):含义同sub,但区别是会将匹配的所有字串全部替换。
例如:
  1. awk 'BEGIN{var="The result is wrong!";sub("wrong", "right", var);print var}'
复制代码
输出:The result is right!

substr(string, start [, length]):返回string中第start个字符开始,长度为length的子字串。当长度参数缺省时是start开始一直到最后一个字符的子字串。
例如:
  1. awk 'BEGIN{var="The result is wrong!";out=substr(var,2,5);print out}'
复制代码
输出:he re

asort(source)、asorti(source):这两个都是对数组进行排序的操作函数。区别在于前者对数组值进行排序,排序后会丢掉原始索引。后者仅对数组索引进行排序,但是与数组值之间的映射关系会改变。
例如:
  1. awk'BEGIN{a["first"]="d"; a["second"]="b";a["third"]="c"; asort(a);for (i in a) print i, a[i]}'
复制代码
输出:
1 b
2 c
3 d

  1. awk 'BEGIN{a[3]="a"; a[1]="b";a[2]="c"; asort(a); for (i in a) print i, a[i]}'
复制代码
输出:
1 a
2 b
3 c

index(in, find):查找并返回字符串 in中find字符第一次出现的位置, 从1开始编号。
例如:
  1. awk 'BEGIN{print index("apple","pp")}'
复制代码
输出:2

length([string]):返回字符串中字符的数目。来看下面三个例子加深理解:
  1. awk 'BEGIN{print length("aa")}'
复制代码
输出:2 ,这个好理解,因为“aa”中一共两个a字符。
  1. awk 'BEGIN{print length("3 *2")}'
复制代码
输出:5
  1. awk 'BEGIN{print length(3 * 2)}'
复制代码
输出:1
咦,后两个是怎么回事呢?注意前者3 * 2被打上了双引号,因此它表示这样的一个字符串,三个非空字符加上两个空字符一共5个字符。最后一个它实际上进行了数学运算,得到的数字6然后被转化成了字符“6”,所以就1个字符啦。

match(string, regexp):在字符串中搜索与正则表达式 regexp 匹配的最长、最左侧的子字符串,并返回该子字符串开始的字符位置(索引)(从1开始编号)。如果未找到匹配项,则返回零。
例如:
  1. awk 'BEGIN{str="hTdTdTdww";print match(str,"Td")}'
复制代码
输出:2

split(string, array, [seps]):将 String 参数指定的参数分割为数组元素 A[1], A[2], . ..,A[n],并返回 n 变量的值。此分隔可以通过 seps 参数指定的扩展正则表达式进行,或用当前字段分隔符来进行。
例如:
  1. awk 'BEGIN{str="a,b,c,d"; n=split(str,a,","); for (i in a) print i, a[i]; printf "string has been split into %d pieces\n", n}'
复制代码
输出:
1 a
2 b
3 c
4 d
string has been split into 4 pieces

tolower(string):返回字符串的副本,字符串中的每个大写字符替换为其相应的小写字符。非字母字符保持不变。
toupper(string):类似tolower(string),小写字符替换为大写字符。

常用的action:
* print和printf:二者都是打印输出,区别在于后者可以格式化输出,并且print输出字串后面自动包含“\n”换行符,而对于printf则没有。
%d:   打印十进制整数。例如awk 'BEGIN{printf "%5d","33"}'
%f:打印浮点数。例如 awk'BEGIN{printf "%-5.3f", "33"}'
%e: 以科学计数法形式打印数字。例如awk 'BEGIN{printf "%.2e", "33"}'
%s: 打印字符串。例如 awk'BEGIN{printf "%3s", "abcd"}'
上面中比如"%-5.3f"的含义是:共5个字符长度,小数部分占3个,并且左对齐(不加"-"默认右对齐)

* 条件控制
1.   If语句:
if (condition) { action }         #如果condition为真,则执行action

2.   if-else语句:
if (condition) { action } else { action}  #如果condition为真,则执行第一个action,否则执行第二个action

例如:打印第一个字段大于10的行awk '{if($1 > 10) {print $1}}' file
在编程时为了体现书写格式的优美以及层次性,我们可以将if-else语句如下书写:
  1. awk '{if ($1 > 10) {
  2.     Print "it is"
  3. } else {
  4.     Print "it’s not"
  5. }
  6. }' file
复制代码
这样一来我们可以变相实现if-elif-else这样的多条件判断语句,因为像“elif”在awk中是不支持的:
  1. awk 'BEGIN {
  2.     a=30
  3.     if (a==10) {
  4.         print "a = 10"
  5.     } else if (a == 20) {
  6.         print "a = 20"
  7.     } else if (a == 30) {
  8.         print "a = 30"
  9.     }
  10. }'
复制代码
此外,假如“{ }”中的action就只有一个时,花括号可以不用写,只需要在action后面写上“;”即可,如下所示,避免了大括号的数量一多时造成眼花缭乱导致看错括号层级。还有注意在“{ }”内要是同一行内存在多个action时,每个action之间需要用“;”隔开。
  1. awk 'BEGIN {
  2.     a=30
  3.     if (a==10)
  4.         print "a = 10";
  5.     else if (a == 20)
  6.         print "a = 20";
  7.     else if (a == 30)
  8.         print "a = 30";
  9. }'
复制代码
3. C条件表达式(?:)是实现if-else的简洁途径,案例如下:
  1. awk '{max=($1 > $2) ? $1 : $2; print max}' file
复制代码
如果$1 > $2 成立,则把$1赋值给max,否则把$2赋值给max。

* while (condition) { action }: 只要condition为真,则一直执行action
例如:awk '{while($1 > 10) {print; $1--}}'file: 打印第一个字段大于10的行,并将第一个字段减1,直到第一个字段小于或等于10。
再比如while循环判断奇偶:
  1. awk 'BEGIN{
  2.         num=10  
  3.         while (num > 0) {
  4.                 if (num%2 == 0)
  5.                         printf "%s is even\n",num;   
  6.                 else
  7.                         printf "%s is odd\n", num;
  8.                 num--
  9.         }
  10. }'
复制代码
* for (variable=start; condition;increment) { action }: 从start开始循环,只要condition为真,则执行action,然后执行increment操作
  1. awk '{for (i=1; i<=NF; i++) {print $i}}' file    # 打印每个字段
复制代码
* break: 跳出当前循环
* continue: 跳过当前循环的剩余部分,进入下一次循环
* next:强制awk停止处理当前记录并进入下一条记录的处理
例如:
  1. awk '{if ($1 ~ /^[A-Z]/) next; print}' test.dat
复制代码
输出:
    beta   35     55      88
   tHeTa   ww      44    u8
上式的含义是第一个字段假如匹配到带大写字母开头的字串那么就会跳过该记录,然后只输出第一个字段开头字母全为小写字母的记录。

awk中的数组:
1.   定义数组,格式例如a[索引]="value"。
awk的数组在使用时不需要提前对数组进行声明,所以比较灵活。数组索引可以为整数、小数甚至字符串,如果是字符串,要在字符串上添加双引号。注意只支持1维数组,但通过巧用索引书写规则可以模拟多维数组,比如a[1,1] ="11";a[0,1] = "01"。
  1. awk 'BEGIN{a[1]="x"; a[2]="y"; a[3]="z"; print a[1], a[2], a[3]}'
复制代码
2.   判断数组中的索引成员是否存在
  1. awk 'BEGIN{a[1]="x";a[2]="y"; a[3]="z"; if (1 in a) print "In"; else print "Not in"}'
复制代码
其中if (1 in a)中判断的是a的索引中是否包含1,而不是a的数组值value是否含有1,这一点和python是有很大区别的。
3. 遍历数组
  1. 1)awk 'BEGIN{a[1]="x"; a[2]="y"; a[3]="z"; for (i in a) print i,a[i]}’         #推荐使用
  2. 2)awk 'BEGIN{a[1]="x";a[2]="y"; a[3]="z"; for (i=1; i<=length(a);i++) print i,a[i]}'
复制代码
4. 删除数组中元素
  1. awk 'BEGIN{a[1]="x";a[2]="y"; a[3]="z"; delete a[2]; for (i in a) print i,a[i]}'
复制代码

一些常用语法:
1.   提取cp2k的restart文件中原子速度
  1. awk '/&VELOCITY/, /&END VELOCITY/{if (!/&VELOCITY/ && !/&END VELOCITY/) print}' test-1.restart
复制代码
2.   awk中字符串拼接
  1. echo "A" | awk '{b=$1"_1"; print b}'
复制代码
3.   查询第一列的最大值(最小也类似)
  1. awk 'BEGIN{max=0}{if ($1 > max) max=$1}END{print max}' test.dat
复制代码
4.   对某列内容求和
  1. awk 'BEGIN{sum=0}{if ($1 != "#!") sum+=$1}END{print sum}' test.dat  #if的作用是开头的注释行不会被纳入计算
  2. 当然有时可以直接写为:awk '{sum+=$1}END{print sum}' test.dat
复制代码
5.   awk中可将变量放在后面
  1. awk '{if($1!="#!") print $2, exp(($4-bmax)/kbt)}' kbt=2.494339 bmax=23 file.dat
复制代码
6.   把每列相加,然后在最后打印每列的总和
  1. awk '{for(i=1; i<=NF; i++) sum[i] += $i}END {for(i=1; i<=NF; i++) print sum[i]}' file
复制代码
7.   打印第一个字段匹配正则表达式的行
  1. awk '$1 ~ /regex/{print}' file
复制代码
8.   把每个字段中所有匹配的"old"替换为"new",然后打印当前行
  1. awk '{gsub(/old/, "new", $0);print}' file
复制代码
9.   打印内容及其行号
  1. awk '{print NR, $0}' file
复制代码
10.  打印每个字段
  1. awk '{for(i=1;i<=NF;i++) {print $i}}' file
复制代码
11.  以逗号为分隔符,打印第二个字段
  1. awk -F ',' '{print $2}' file
复制代码

11. 按行合并两个文件
  1. awk 'NR==FNR{a[FNR]=$0;next}{print a[FNR],"\t",$0}' file1 file2 > merged.dat
复制代码


综合案例-给xyz格式文件中原子添加后缀:
  1. #!/bin/bash

  2. function help() {
  3.         echo "Usage: atm_suffix.sh [.xyz format file]"
  4. }

  5. if [[ $# -lt 1 ]]; then
  6.         help; exit 1
  7. fi

  8. echo 'Input atom indices, e.g. 2,3,5-9'
  9. read num

  10. if [[ -z ${num} ]]; then
  11.         help ;exit 1
  12. fi

  13. echo "Input symbol to add the suffix, default '_1' by press enter:"
  14. read symbol

  15. if [[ -z ${symbol} ]]; then
  16.         symbol="_1"
  17. fi

  18. awk -v N="${num}" -v sym="${symbol}" '
  19.         BEGIN{
  20.         split(N,a,",")
  21.         for (i in a) {
  22.                 if (a[i] ~ /^[0-9]+$/) {
  23.                         b[a[i]] = 1
  24.                 } else {
  25.                         split(a[i],num,"-")
  26.                         for (j=num[1];j<=num[2];j++) {
  27.                                 b[j] = 1
  28.                         }
  29.                 }
  30.         }
  31. }
  32. {
  33.         if (NR == 1) {
  34.                 print $1 >> "coord_new.xyz"
  35.         } else if (NR ==2) {
  36.                 print "" >> "coord_new.xyz"
  37.         } else if ((NR-2) in b) {
  38.                 sub($1,$1sym)
  39.                 printf "%-5s %18.6f %18.6f %18.6f\n", $1,$2,$3,$4 >> "coord_new.xyz"
  40.         } else {
  41.                 printf "%-5s %18.6f %18.6f %18.6f\n", $1,$2,$3,$4 >> "coord_new.xyz"
  42.         }
  43. }' $1

  44. echo "Done~"
复制代码
三、 总结
  上面只是简单的总结了一下awk的一些基本语法,也是我平常所使用到的一些语法结构。如果大家觉得平常用的比较多但帖子中有没有具体写到的一些语法,欢迎在下方评论区展示出来,我将及时地更新到本贴内容当中。如果是第一次学awk语法感到很困惑、费劲的话,不要因这点难度放弃了这个好工具,因为你一旦熟悉这些语法结构,就会体验到当面对一些文件处理时awk将会展现出多么强大的功能性和灵活性。所以平常将常遇到的语法刻意记一记,多用上几次慢慢就会了。

评分 Rate

参与人数
Participants 20
威望 +1 eV +82 收起 理由
Reason
RandomError + 5
秋心 + 5 牛!
mxh + 5 赞!
Puying + 4 GJ!
Yjc + 5 赞!
wsz + 5 好物!
hdhxx123 + 3 好物!
kimariyb + 3 好物!
Acee + 4 好物!
伍度零 + 4 赞!
adam121 + 1 赞!
Freeman + 5 好物!
卡开发发 + 5 好物!
Daniel_Arndt + 5 赞!
noodles的困惑 + 5 赞!
zsu007 + 5 赞!
sobereva + 1
WilliamH + 5 赞!
ABetaCarw + 3 欢迎讨论
含光君 + 5 好物!

查看全部评分 View all ratings

自由发挥,野蛮生长

417

帖子

1

威望

2200

eV
积分
2637

Level 5 (御坂)

2#
发表于 Post on 2023-4-30 11:43:46 | 只看该作者 Only view this author
awk的system函数我偶尔用一下,就是让awk调用shell里的命令,但我个人并不喜欢这种方式,里面有些格式相当反直觉。要留意一些数字的隐式转换问题,想要搞带余除法的话,就要用int函数。用awk做数值计算时,要小心,我常常用gawk里的strtonum函数来规避一些问题。gawk在4.1.0版本之后引入了MPFR,可以通过”-M“来开MPFR,但需要留意的是有些Linux发行版里面的gawk版本号高于4.1.0,却没有在编译时开MPFR特性。处理浮点数的时候,有时候要留心CONVFMT、PREC这两个变量。自己在awk脚本里面写函数时要注意,默认情况下变量是全局变量,因此,常常要在一个函数的参数列表最后,先多打几个空格(方便阅读时区分)再加上该函数里的中间变量,防止这些中间变量被其他的函数调用。我头一次用awk里的数组时头疼死了,当时不清楚awk里面是关联数组。

关于FNR和NR的区别,我这里有个小脚本,用来从多帧的xyz文件中输出最后一帧(其实很容易用bash来完成同样的工作,这里只是个示例而已)。
  1. FNR == NR {
  2. if (FNR == 1)
  3.     numAtom = $1;
  4. numLine = FNR;
  5. }

  6. FNR < NR {
  7. if (FNR >= numLine - numAtom -1 && FNR <= numLine)
  8.     print $0;
  9. }
复制代码
用的时候很简单,把上述脚本保存成lastframe.awk,放到跟xyz文件一个目录下,然后运行如下命令。
  1. awk -f lastframe.awk file.xyz file.xyz
复制代码
更复杂的利用FNR和NR的技巧,可以浏览这个网页 http://datafix.com.au/BASHing/2019-07-12.html

评分 Rate

参与人数
Participants 1
eV +5 收起 理由
Reason
丁越 + 5 谢谢老师分享经验

查看全部评分 View all ratings

本版积分规则 Credits rule

手机版 Mobile version|北京科音自然科学研究中心 Beijing Kein Research Center for Natural Sciences|京公网安备 11010502035419号|计算化学公社 — 北京科音旗下高水平计算化学交流论坛 ( 京ICP备14038949号-1 )|网站地图

GMT+8, 2024-11-27 10:38 , Processed in 0.239594 second(s), 22 queries , Gzip On.

快速回复 返回顶部 返回列表 Return to list