[TOC] # 4.2 数据验证 > 数据验证也是Awk程序比较常用的功能之一,通过验证可以确保数据格式的合法有效性(至少是合理的)。这节包含一些检验输入数据有效性的小示例,通过了解这些示例,我们可以掌握更多Awk的用法。 ## 示例: 每列数据类型检验 ``` #!/usr/bin/awk -f # colcheck.awk - 检验每列数据类型是数字还是字符串类型 # 输入: 数字或者字符串 # 输出: 不符合第一行格式类型的行数据 NR==1{ nfld = NF for(i=1; i<= NF; i++) type[i] = isnum($i) } { if(NF != nfld) printf("第 %d 行有 %d 列数据而不是 %d 列\n", NR, NF, nfld) for(i=1;i<=NF;i++) if(isnum($i) != type[i]) printf("第 %d 行的第 %d 列值为: %s ,与第一行类型不符合\n", NR, i, $i) } function isnum(n){ return n~/^[+-]?[0-9]+$/} ``` 验证结果: ``` 输入数据: 1 a 1 a 1 1 1 1 1 1 输出结果: 第 2 行的第 2 列值为: 1 ,与第一行类型不符合 第 2 行的第 4 列值为: 1 ,与第一行类型不符合 ``` ## 示例:定界符的检验 > 有时候我们编写内容包含语句块(例如有开始标记`.START`和结束标记`.END`),但无法通过执行或者自动语法检查工具验证正确性时,我们就只能通过人工方式阅读验证,这非常低效,所以我们可以利用Awk来自动检验编写的内容是否有效。 ``` /^\.START/{ if( p != 0) printf("错误:在第 %d 行的.START 标记出现在了 .START 标记之后\n", NR); p = 1 } /^\.END/{ if( p == 0) printf("错误:在第 %d 行的.END 标记前少了 .START 标记\n", NR); p = 0 } END{ if( p != 0) printf("错误:缺失 .END 标记" } ``` 思考问题: 这个示例的定界符没有考虑定界符嵌套问题,那么如何扩展以下支持嵌套定界符的检验呢? 下面是匹配语句块语法检验的Awk程序,可以找到最近缺失匹配的位置: ``` #!/usr/bin/awk -f # p12check.awk 定界符有效性检验 # 检验Awk的语句块{}定界符有效性(适用于C语言或Bash等语法检验) /{/{ srow[p++] = NR } /}/{ if( p == 0 ) printf("语法错误:第%d行前缺少了{\n", NR) else erow[p--] = NR } END{ if( p != 0) printf("语法错误:第%d行的}之后缺少与第%d行的{匹配\n", erow[p+1] , srow[p]) } ``` ## 示例: 密码文件检验 > 对Linux/Unix系统熟悉的人都知道 `/etc/passwd`文件是保存用户的密码文件,存储了每个用户的名称、ID、组和登录Shell等信息。 passwd文件格式固定为冒号(:)分隔的7个字段。 ``` 用户名:加密的密码串:用户ID:组ID:全名:home目录:登录默认运行的程序 ``` 下面编写一段`pwdcheck.awk`检查`passwd`文件正确性脚本: ``` # pwdcheck.awk 检查passwd文件 BEGIN{ FS=":"} NF != 7{ printf("第%d行不是7列数据:%s\n",NR,$0)} $1~/[^A-Za-z0-9]/{ printf("第%d行用户名不是字母或数字组合:%s\n",NR,$0)} $2 == ""{ printf("第%d行用户密码为空:%s\n",NR,$0)} $3 ~/[^0-9]/{ printf("第%d行用户ID非数字:%s\n",NR,$0)} $4 ~/[^0-9]/{ printf("第%d行组ID非数字:%s\n",NR,$0)} $6 !~/^\//{ printf("第%d行用户HOME目录不是绝对路径:%s\n",NR,$0)} ``` 这是一个密码文件检验的简单示例,当我们需要时可以继续修改或添加新的规则条件,每日执行检验保证密码文件的合法性。 ## 示例:数据校验程序生成器 > 刚才密码文件检验程序是手工编写的,那么我们接下来看一个简单的自动生成Awk检验程序的示例。 ``` BEGIN{ FS = "\t+" } { printf("%s {\n\tprintf(\"line %%d, %s: %%s\\n\", NR,$0)\n}\n",$1,$2)} ``` 在终端执行结果: ``` ## 执行方法 $ ./checkgen.awk rule.list ## 输入内容: $ cat rule.list NF != 7 不包含7列数据 $1 ~ /[^A-Za-z0-9]/ 用户ID非数字 $2 == "" 没有设置密码文件 ##输出结果: NF != 7 { printf("line %d, 不包含7列数据: %s\n", NR,$0) } $1 ~ /[^A-Za-z0-9]/ { printf("line %d, 用户ID非数字: %s\n", NR,$0) } $2 == "" { printf("line %d, 没有设置密码文件: %s\n", NR,$0) } ``` 这个示例是一个抽象提取规则的示例,提取出模式串和提示信息串两部分作为输入数据,编写通用的生成检验脚本规则,这样可以达到编写一个脚本可以重复利用的效果,这样可以减少我们很多重复性劳动工作。