awk 学习笔记

awk 使用教程

awk - pattern-directed scanning and processing language (模式定位 扫描和处理语言)

Awk scans each input file for lines that match any of a set of patterns specified literally in prog or in one or more files specified as -f progfile. With each pat- tern there can be an associated action that will be performed when a line of a file matches the pattern.【使用pattern(正则) 可以使只有匹配上pattern 的行进行 prog 程序处理】 Each line is matched against the pattern portion of every pat- tern-action statement; the associated action is performed for each matched pattern. The file name - means the standard input. Any file of the form var=value is treated as an assignment, not a filename, and is executed at the time it would have been opened if it were a filename【这句话没有理解 之后找demo】. The option -v followed by var=value is an assignment to be done before prog is executed; any number of -v options may be present.【可以多个 -v 标签 】 The -F fs option defines the input field separator to be the regular expression fs. An input line is normally made up of fields separated by white space, or by regular expression FS. The fields are denoted $1, $2, ..., while $0 refers to the entire line. If FS is null, the input line is split into one field per character.

awk 语法

awk [ -F fs ] [ -v var=value ] [ 'prog' | -f progfile ] [ file ...  ]

简单的讲 awk 有三种调用方式

  1. pattern
  2. {prog}
  3. pattern{prog}

一行一行的解析 根据分隔符进行拆分一行的数据 $0 指的是这一行 $1 是第一个区域 $2,$3... 依次往上长

-F 分隔符 比如 : , 等等 默认的是空格

DEMO:
我的一个文件
dingmac@cos-git$ cat awk.txt 
2017-08-19 14:15:02.xml
2017-08-19 14:20:02.xml

dingmac@cos-git$ awk -F : '{print $1}' awk.txt
2017-08-19 14
2017-08-19 14

-f 脚本文件

demo:
awk -f script_file files

脚本文件的写法直接看文章后面的例子

参考 http://www.jellythink.com/archives/1252

最重要的是 prog 的部分

pattern { action }

  • 正则匹配

    模式 pattern

    模式即正则表达式 使用// 两个斜杠包裹 正则表达式就是先对一行的数据先经过正则匹配之后,再对匹配的这行数据进行后面的prog 程序处理

    支持的列表如下

    参考 https://www.math.utah.edu/docs/info/gawk_5.html

    符号意义
    ^ 一行匹配开始
    $ 匹配一行的结束
    [a-b]或者[abc] 某个区域的一个值
    [:alnum:] 字母数字字符。 相当于 w 去掉 下划线
    [:alpha:] 字母字符 相当于
    [:blank:] Space and tab characters.
    [:cntrl:] Control characters.
    [:digit:] Numeric characters.
    [:graph:] Characters that are printable and are also visible. (a space is printable, but not visible, while an 'a' is both.)
    [:lower:] Lower-case alphabetic characters. 小写字母字符
    [:print:] Printable characters (characters that are not control characters.)
    [:punct:] Punctuation characters (characters that are not letter, digits, control characters, or space characters). 标点符号
    [:space:] Space characters (such as space, tab, and formfeed, to name a few).
    [:upper:] Upper-case alphabetic characters.
    [:xdigit:] Characters that are hexadecimal digits. 16 进制数
    [^ ...] 不包含括号里面的内容
    * 任何数量
    . 任何字符【不包括换行】当然这个不用考虑,awk就是一个行
    取消转义 比如 $ 需要转义 $
    | 或者操作符 ^P|[0-9]
    (...) 分组
    + 一个或者多个
    ? 非饥饿匹配
    {n,}或者{n,m}或者{n} 匹配数量
gawk 还支持 其他的一些 正则可以参考gawk 的文档 这里不做介绍,只要知道awk 最基本的正则表达式就可以了 gawk 不是所有的linux/unix distribution都支持 需要另外的下载

##### DEMO
```
dingmac@cos-git$ ls | awk '/^u.*txt$/' 
upload_back.txt
upload_backup.txt
upload_error.txt
upload_succ.txt
```
同 
```
ls | awk '/^u.*txt$/{print}'
ls | awk '/^u.*txt$/{print $0}' 
```

测试下来【Mac】 还支持 /i 忽略大小 demo /john/i 查找含有john 的行并且忽略大小写

  • prog语句语法 即{action} 部分

    可以使用的语句如下
语句说点啥
if( expression ) statement [ else statement ]  
while( expression ) statement  
for( expression ; expression ; expression ) statement  
for( var in array ) statement 在 statement 中使用 array[var] 访问值
do statement while( expression )  
break  
continue  
{ [ statement ... ] }  
expression # commonly var = expression  
print [ expression-list ] [ > expression ]  
printf format [ , expression-list ] [ > expression ]  
return [ expression ]  
next # skip remaining patterns on this input line  
nextfile # skip rest of this file, open next, start at top  
delete array[ expression ]# delete an array element  
delete array # delete all elements of array  
exit [ expression ] # exit immediately; status is expression  

这里用到了另外一个小命令 col

man awk | col -b >file.txt RLF和HRLF 是linux 里面使用到的一些看不见的控制符了解就可以

Linux col命令用于过滤控制字符。 在许多UNIX说明文件里,都有RLF控制字符。当我们运用shell特殊字符">"和">>",把说明文件的内容输出成纯文本文件时,控制字符会变成乱码,col指令则能有效滤除这些控制字符。
语法 col [-bfx][-l<缓冲区列数>]
参数:
-b 过滤掉所有的控制字符,包括RLF和HRLF。
-f 滤除RLF字符,但允许将HRLF字符呈现出来。
-x 以多个空格字符来表示跳格字符。
-l <缓冲区列数> 预设的内存缓冲区有128列,您可以自行指定缓冲区的大小。

注意点
  • 语句使用 ; 换行符或者 } 结束 空语句默认$0, 例子 ls | awk '//'

  • 操作符

    操作符意义
    + - * / % ^ 语句中的操作符 /整除 % 求余数 ^ 幂运算 
    ! ++ -- += -= *= /= %= ^= > >= < <= == != ?: 这些操作符都可以再awk 的语句中使用
    参数可以使用 scalar 标量 或者数组 初始是 null string
    ! || && 逻辑运算符
    ~ (matches) or !~ (does not match) 匹配 demo : $0 !~ /^hello/ 不匹配
  • 内置函数

    函数名称意义
    exp, log, sqrt, sin, cos, and atan2 内置数学函数 exp 按照e为底的指数 log 函数 这些都是三角函数
    length the length of its argument taken as a string, or of $0 if no argument. 参数的长度 如果没有参数 就是$0 的长度
    rand random number on (0,1)
    srand sets seed for rand and returns the previous seed. 设置rand的种子 参考c的srand
    demo ---
    #include<time.h>
    #include<stdlib.h>
    #include<stdio.h>
    
    int main()
    {
    	int i,j;
    	srand((int)time(0));
    	for(i=0;i<10;i++)
    	{
    		j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
    		printf("%d ",j);
    	}
    }
    
    保存运行 
    gcc file.c -o file
    ./file 运行
    如果去掉 srand 将会每次执行的都是一样的
    函数名称意义
    int 转换成int 类型 1.2 变成 1
    substr(s, m, n) s 从m 位置开始 n个数 如果没有n 的话就是到最后
    index(s, t) s中t字符串位置 没找到为0
    match(s, r) 如果正则表达式r在s中出现,则返回出现的起始位置;如果在s中没有出现r,则返回0.
    split(s, a, fs) 使用fs拆分s结果放在a 可以通过 a[1],a[2] 访问
    sub(r, s, t) 在字符串t中用s替换正则表达式r的首次匹配。如果成功则返回1,否则返回0,如果没有给出t,默认为$0 只替换第一个位置
    gsub(r,s,t) 和sub 的使用方式一样 只不过是全部替换
    sprintf(fmt, expr, ... ) 格式化输出
    system(cmd) executes cmd and returns its exit status 返回退出状态
    tolower(str) returns a copy of str with all upper-case characters translated to their corresponding lower-case equivalents.
    toupper(str) returns a copy of str with all lower-case characters translated to their corresponding upper-case equivalents.
  • 常量

说明:[A][N][P][G]表示第一个支持变量的工具,[A]=awk、[N]=nawk、[P]=POSIXawk、[G]=gawk

标识符含义
$n 当前记录的第n个字段,比如n为1表示第一个字段,n为2表示第二个字段。
$0 这个变量包含执行过程中当前行的文本内容。
[N] ARGC 命令行参数的数目。
[G] ARGIND 命令行中当前文件的位置(从0开始算)。
[N] ARGV 包含命令行参数的数组。
[G] CONVFMT 数字转换格式(默认值为%.6g)。
[P] ENVIRON 环境变量关联数组。
[N] ERRNO 最后一个系统错误的描述。
[G] FIELDWIDTHS 字段宽度列表(用空格键分隔)。
[A] FILENAME 当前输入文件的名。
[P] FNR(file No. recode) 同NR,但相对于当前文件。
[A] FS (filter seperator) 字段分隔符(默认是任何空格)。
[G] IGNORECASE 如果为真,则进行忽略大小写的匹配。
[A] NF(No. field) 表示字段数,在执行过程中对应于当前的字段数。
[A] NR(No recode) 表示记录数,在执行过程中对应于当前的行号。
[A] OFMT 数字的输出格式(默认值是%.6g)。
[A] OFS 输出字段分隔符(默认值是一个空格)。
[A] ORS 输出记录分隔符(默认值是一个换行符)。
[A] RS 记录分隔符(默认是一个换行符)。
[N] RSTART 由match函数所匹配的字符串的第一个位置。
[N] RLENGTH 由match函数所匹配的字符串的长度。
[N] SUBSEP 数组下标分隔符(默认值是34)【这个只要知道在多维数组里面 split(str,result,SUBSET);然后在对result 访问 for in 访问方式】。
变量名描述举例
$0 当前记录内容 awk ‘{print $0}’
$1~$n 分别保存着当前记录的字段1到字段n的内容 awk ‘{print $1, $2, $3}’
FS 字段的分隔符,默认是空格或Tab awk ‘BEGIN{FS=”:”}{print $1,$3,$6}’ /etc/passwd
NF 记录当前记录中的字段个数 awk ‘{print $0} END{printf(“Total Field(s):%d ”, NF)}’ 201509.log
NR 已经读出的行数,从1开始计数;对于多个文件的情况下,该值会持续累加 awk ‘{print NR}’ 201508.log 201509.log
FNR 对于当前处理的文件来说,已经读出的行数;对于多个文件的情况下,该值是各个文件独自对应的行号 awk ‘{print FNR}’ 201508.log 201509.log
RS 输入的记录分隔符, 默认为换行符 awk ‘BEGIN{RS=” “}{print FNR}’ 201508.log
OFS 输出字段分隔符, 默认也是空格 awk ‘BEGIN{OFS=” ”}{print 1,2, $3}’ 201509.log
ORS 输出的记录分隔符,默认为换行符 一般用的很少,此处不举例说明了
FILENAME 当前输入文件的名字 awk ‘{print FILENAME}’ 201509.log
  • 函数 function foo(a, b, c) { ...; return x }

我的例子

  1. 查找 /etc/man.conf 文件中包含 字符数大于72 的并且标记是否包含is 这个关键字
dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72 { if($0 ~ /is/) print $0 , "---->   contains is" ; else print $0 , "---->   not contains is"}'
# This file is also read by man in order to find how to call nroff, less, etc., ---->   contains is
# and to determine the correspondence between extensions and decompressors. ---->   not contains is
# NOAUTOPATH keeps man from automatically adding directories that look like ---->   not contains is
# (Maybe - but today I need -Tlatin1 to prevent double conversion to utf8.) ---->   not contains is
# This is mainly for the benefit of those that think -a should be the default ---->   contains is
# Note that some systems have /usr/man/allman, causing pages to be shown twice. ---->   not contains is

注意点

  • awk 里面函数的使用,比如length函数 是在pattern{ action } 中的 pattern 部分 如果使用了函数 正则表达式无法使用 【需要进一步研究】 如果要使用多个函数进行判断 使用 && 连接 [这个在文章的后面做了解释]

    demo

    dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72 && index($0,"is")'
    # This file is also read by man in order to find how to call nroff, less, etc.,
    # This is mainly for the benefit of those that think -a should be the default

    同样的 || 或运算符 还有非运算符 ! 看下面一个有趣的例子

    dingmac@cos-git$ cat /etc/man.conf  | awk '! index($0,"is")' | wc -l
         118
    dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72' | wc -l
           6
    dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72 ! index($0,"is")' | wc -l
           9
    dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72 && ! index($0,"is")' | wc -l
           4
    dingmac@cos-git$ cat /etc/man.conf  | awk 'length($0)>72 || ! index($0,"is")' | wc -l
         120

    多个条件一定要加 && 或者 ||

  • if 语句等写在{} 里面 多个语句一定要用 ; 隔开 否则会出错 最后一个分号可以省略 在awk 脚本文件中推荐使用换行 不要写在一行 if 语句等 使用 if{}else{} 这样的方式 可以使用{} 的 

DEMO2

时间按照 ":" split 字段然后 打印出来

awk.txt 文件如下
dingmac@cos-git$ cat awk.txt 
2017-08-19 14 15 02.xml
2017-08-19 14 20 02.xml
2017-08-21 11:31:47.xml
2017-08-21 11:32:27.xml
2017-08-21 11:33:13.xml
2017-09-10 21:10:01.xml
desc2017-08-28_00:01:50.xml
desc2017-08-28_00:01:51.xml
desc2017-08-28_00:01:52.xml

dingmac@cos-git$ cat awk.txt | awk '/^201*/{ split($0,a,":") ; print a[0],a[1],a[2],a[3]}'
 2017-08-19 14 15 02.xml
 2017-08-19 14 20 02.xml
 2017-08-19 14 25 02.xml

print 函数如果没有参数 默认是 $0 即打印整个行

DEMO3 定义自己的函数

# list_info.awk

BEGIN{
	FS = ":"
}

function insert(string, pos, ins,before,after)
{
    before = substr(string, 1, pos)
    after = substr(string, pos + 1)
    return before ins after
}

# 脚本主体
{
    if(match($0,/root/)){
        print insert($0, 5, "JellyThink")
        print before   #输出:<空>
        print after    #输出:<空>
        print $0       #输出:HelloWorld
    }
}

如果 before 和after 没有在函数中参数定义的话 可以全局访问

awk中,函数中定义的变量,默认是全局的,而传递的参数都是值传递,也就是说即使在函数内部修改了传递进来的参数的值,在函数外部,该参数的值是不会发生改变的。这到和Lua有几分相像。

参考 http://www.grymoire.com/Unix/Awk.html

注意

在 awk 中数组叫做关联数组(associative arrays),因为下标记可以是数也可以是串。awk 中的数组不必提前声明,也不必声明大小。数组元素用 0 或空串来初始化,这根据上下文而定
使用if ( key in array) 判断数组中是否包含 键值
delete array[key]可以删除,对应数组key的,序列值。

DEMO3

查找 awk.txt 文件 包含john或者John 的工资记录

Tom:2012-12-11:car:53000
john:2013-01-13:bike:41000
viviJohn:2013-01-18:car:42800
Tom:2013-01-20:car:32500
John:2013-01-28:bike:63500
上面内容 一月份的工资数统计

#list_salary.awk

BEGIN{
	FS=":"
}
/John/i {
	split($2,a,"-")
	if(a[1] = "01"){
		b[$1] = b[$1] + $4
	}
}
END{
	for(i in b){
		print i  "	" b[1]
	}
}

结果

dingmac@cos-git$ awk -f ~/Desktop/linux_test/list_user.awk  awk.txt 
viviJohn	42800
John	63500

注意两点 1. 数组不需要初始化就可以使用 2. 使用的模式可以再BEGIN 和END 中间的{} 之前使用 /i 表示ignore case 忽略大小写

  1. BEGIN 和 END 只会执行一次

进一步甚至可以这样

awk '
BEGIN { actions } 
/pattern/ { actions }
/pattern/ { actions }
...
END { actions } 
' filenames

cool

还是上面的例子只不过函数发生了变化

BEGIN{
	FS=":"
}
/John/ {
	split($2,a,"-")
	if(a[1] = "01"){
		b[$1] = b[$1] + $4
	}
}
/Tom/ {
	split($2,a,"-")
	if(a[1] = "01"){
		b[$1] = b[$1] + $4
	}
}
END{
	for(i in b){
		print i  "	" b[i]
	}
}

执行结果的

dingmac@cos-git$ awk -f ~/Desktop/linux_test/list_user.awk  awk.txt 
Tom	85500
viviJohn	42800
John	63500

相当于合并了两结果 如果两个pattern的结果有重合区,不会有重复的数据出现

DEMO 二位数组

awk 不支持二维数组 但是可以使用下面的方式

SUBSEP 用来区分
awk使用一个特殊的字符串SUBSEP作为分割字段,使用在二维数组上面

awk 'BEGIN{                                        
    for(i=1;i<=9;i++)
    {                
      for(j=1;j<=9;j++)  
      {                  
        tarr[i,j]=i*j;
      }           
    }                       
    for(m in tarr)              
    {
        split(m,tarr2,SUBSEP);
        print tarr2[1],"*",tarr2[2],"=",tarr[m];
    }
}'

SUBSEP 用来分割这种伪二维数组的 怎么检测是否在二维数组使用 if( (i,j) in Array )

demo

if( (1,11) in tarr){
	print "in";
}else{
	print "not in";
}

awk的多维数组在本质上是一维数组,更确切一点,awk在存储上并不支持多维数组。awk提供了逻辑上模拟二维数组的访问方式。例如,array[2,4]=1这样的访问是允许的。awk使用一个特殊的字符串SUBSEP作为分割字段。 类似一维数组的成员测试,多维数组可以使用if ( (i,j) in array)这样的语法,但是下标必须放置在圆括号中。类似一维数组的循环访问,多维数组使用for ( item in array )这样的语法遍历数组。与一维数组不同的是,多维数组必须使用split()函数来访问单独的下标分量 split(m,tarr2,SUBSEP)

参考 http://www.cnblogs.com/quincyhu/p/5884390.html 上面文章有个错误 不匹配 应该是 !~

http://www.zsythink.net/archives/tag/awk/page/2/

  • 传入参数

参数是var="value" 的形式 不能有空格

两种方式

awk -F : -v  var1="xiaoming"  -f ~/Desktop/linux_test/list_user.awk file

上面的 -v 还有

awk  -f  ~/Desktop/linux_test/list_user.awk  var1="xiaoming" file

两者的区别是 不加 -v 在BEGIN 区块压根找不到 但是可以再{} 里面找到

多个参数使用多个 -v 参数的位置一定要到在 -f 之前 demo

awk -v a="a" -v b="b" -f list_user.awk /Applications/AMPPS/www/cos-git/awk.txt

可以放在 -F 之前或者之后都可以

总结

  1. 我测试下来,pattern部分不仅 是一个正则表达式 还可以是一个返回boolean 值的表达式 demo

    awk '$0 !~ /^hello/' file
    
    ls -al ../ | awk "/[0-9.]+M/" 
    -rwxrwxrwx   1 dingmac  staff   178M  5 13 02:29 afie.rar
    
    # 看这个例子 [:digit:]这种缩写方式使用的是要注意一点 最好使用 '' 单引号将语句括起来 使用"" 有问题 具体原因没有深究
    
    比如下面的列出文件大小是 M 单位的文件
    
    # 使用单引号  正确
    dingmac@linux_test$ ls -al ../ | awk ' ! match($0 ,/[[:digit:].]M/) ' | wc -l
          32
          
    # 使用双引号 出错
    dingmac@linux_test$ ls -al ../ | awk "! match($0 ,/[[:digit:].]M/) " | wc -l 
          38
    
    # [:digit:] 没有正确使用的demo 错误
    dingmac@linux_test$ ls -al ../ | awk ' ! match($0 ,/[:digit:.]M/) ' | wc -l
          38

    同样的使用 ~ 和 !~ 一样的实现

    dingmac@linux_test$ ls -al ../ | awk  ' $0 !~ /[[:digit:]]M/ ' | wc -l
          32
    dingmac@linux_test$ ls -al ../ | awk  ' $0 ~ /[[:digit:]]M/ ' | wc -l
           6
    dingmac@linux_test$ ls -al ../ | wc -l
          38

    还有可以使用的函数 index(s,"substr"),match($0,regex),!=,== 等等 多个语句可以使用 && || ! 连接在一起

    甚至可以这样写

    dingmac@linux_test$ ls -al ../ | awk  ' $0 ~ /[[:digit:]]M/ {print $0} '
    
    但是我在脚本文件中这样写 就会报错 所有 我说的上面的 pattern 最好在只使用pattern 的时候可以这样的写 一旦有{另外的处理} 最好还是写成if() 这样的语句加以判断

    有一个小问题 如果 文件是下面的内容

    rwxrwxrwx   1 dingmac  staff   178M  5 13 02:29 file1.rar
    -rwxrwxrwx   1 dingmac  staff    43M  4  7  2015 file2.sql
    -rwxrwxrwx   1 dingmac  staff   270M  5 13 02:31 file3 wowo.rar

    只要求输出文件名 在$9 上面 但是有点尴尬的是 我的一个文件名字包含空格 咋办? 我有两个解决方案

    1. print $9 $10 【print 函数使用空格表示 字符串联结 中间没有空格和 python 不一样】 2.观察发现文件名在最后 我们可以借用 NF 这个const量

    demo

    dingmac@linux_test$ ls -al ../ | awk  ' $0 ~ /[[:digit:]]M/ { for(i = 9; i <=NF; i++ ){ b[$0] = b[$0] $(i) } }  END { for(c in b){ print b[c] } }  ' | sort
    aixxx.rar
    aigxxxx.sql
    asxxx.rar
    duxxx.rar
    mexxxx.zip
    vxxx.png
  2. 突然一拍脑袋TMD awk 语法超级像 switch case 语法 哈哈哈 有点意思

    awk '
        NR < 2    {}  # case 1
        /^xyzzy:/ {}  # case 2
                  {print}' # 相当于default
  3. continue 和next 的区别 continue 是在循环体里面使用的 ,next 可以使用在任何地方 用来跳过后面的语句 看下面例子
    文件夹详情

    dingmac@linux_test$ ls -al
    total 8
    drwxr-xr-x   5 dingmac  staff   170B  9 13 17:23 .
    drwx------+ 37 dingmac  staff   1.2K  9 13 10:12 ..
    -rw-r--r--   1 dingmac  staff     0B  9 13 17:23 file1.txt
    -rw-r--r--   1 dingmac  staff     0B  9 13 17:23 file2.txt
    -rwxr-xr-x   1 dingmac  staff    34B  9 13 16:31 list_user.awk

    下面是一个简单的pattern{prog} 形式的语句

    dingmac@linux_test$ ls -al | awk 'NR % 2 == 0 {print NR,$9 }'
    2 .
    4 file1.txt
    6 list_user.awk

    我们做一些修改

    dingmac@linux_test$ ls -al | awk 'NR % 2 == 0 {print NR,$9 }{ print NR,$9}'
    1 
    2 .
    2 .
    3 ..
    4 file1.txt
    4 file1.txt
    5 file2.txt
    6 list_user.awk
    6 list_user.awk

    如果我们修改成下面

    dingmac@linux_test$ ls -al | awk 'NR % 2 == 0 {next}{print NR,$9 }{ print NR,$9}'
    1 
    1 
    3 ..
    3 ..
    5 file2.txt
    5 file2.txt

    next 会作用到之后所有的{prog} 里面 一个pattern 只会作用到之后的一个 {prog} 里面 甚至你可以尝试一下下面的语句 看看出来啥

    ls -al | awk 'NR % 2 == 0 {}{print NR,$9 }{ print NR,$9}'
    ls -al | awk 'NR % 2 == 0 {}{next}{print NR,$9 }{ print NR,$9}'
  4. printf 格式化输出 参考C语言

找了一篇文章 http://www.cnblogs.com/thefirstfeeling/p/5667053.html

慢慢沉淀自己
原文地址:https://www.cnblogs.com/martinding/p/7516256.html