awk双文件处理时为何异常打印首个输入文件的首行?
Awk双文件关联匹配脚本问题解答
问题场景
编写bash调用awk的双文件匹配脚本,预期以姓名为键关联两个文件的信息输出,原脚本内容如下:
#!/bin/bash awk 'FNR > 1 && NR==FNR { f_name = tolower($1) # first name l_name = tolower($2) # last name full_name = f_name":"l_name # full name used as array index address_city_county_state_zip = $4","$5","$6","$7","$8 address[full_name] = address_city_county_state_zip next } { first_n = tolower($1) # first name last_n = tolower($2) # last name full_n = first_n":"last_n # full name used as array index printf ("%-10s %-10s %-55s %06.2f %-10s\n", $1, $2, address[full_n], $4, FILENAME) }' $1 $2
执行命令:
./ch10_challenge.awk addresses.txt nameemailavg.tab
测试文件构造命令
- 构造
nameemailavg.tab(无表头,存储姓名、邮箱、浮点数值):
printf 'Art\tVenere\tart@venere.org\t256.62394383\nLenna\tPaprocki\tlpaprocki@hotmail.com\t259.688783099\nDonette\tFoller\tdonette.foller@cox.net\t282.32979844\n' > nameemailavg.tab
- 构造
addresses.txt(首行为字段表头,后续为地址数据):
printf 'first_name\tlast_name\tcompany_name\taddress\tcity\tcounty\tstate\tzip\tphone1\tphone2\temail\tweb\nAbel\tMaclead\tRangoni Of Florence\t37275 St Rt 17m M\tMiddle Island\tSuffolk\tNY\t11953\t631-335-3414\t631-677-3675\tamaclead@gmail.com\thttp://www.rangoniofflorence.com\nArt\tVenere\tChemel, James L Cpa\t8 W Cerritos Ave #54\tBridgeport\tGloucester\tNJ\t08014\t856-636-8749\t856-264-4130\tart@venere.org\thttp://www.chemeljameslcpa.com\nDonette\tFoller\tPrinting Dimensions\t34 Center St\tHamilton\tButler\tOH\t45011\t513-570-1893\t513-549-4561\tdonette.foller@cox.net\thttp://www.printingdimensions.com\nSimona\tMorasca\tChapman, Ross E Esq\t3 Mcauley Dr\tAshland\tAshland\tOH\t44805\t419-503-2484\t419-800-6759\tsimona@morasca.com\thttp://www.chapmanrosseesq.com\nKiley\tCaldarera\tFeiner Bros\t25 E 75th St #69\tLos Angeles\tLos Angeles\tCA\t90034\t310-498-5651\t310-254-3084\tkiley.caldarera@aol.com\thttp://www.feinerbros.com\n' > addresses.txt
异常现象
预期仅输出第二个文件中姓名对应的地址信息,实际运行时首行永远打印第一个传入文件的首行表头,内容为first_name、last_name、000.00、addresses.txt;调换两个文件传入顺序后输出整体错乱,首行变为新的第一个传入文件的首行。
疑问解答
1. 现有代码为什么会打印首个传入文件addresses.txt的第一行?
第一个代码块的匹配规则是FNR > 1 && NR==FNR,需要同时满足两个条件才会进入块执行:
- 当前处理行在所属文件的行号
FNR大于1(跳过首行) - 全局累计行号
NR等于当前文件行号FNR(即正在处理第一个传入文件)
处理addresses.txt第一行时FNR=1,不满足FNR>1的判断,根本不会进入第一个代码块,块内的next语句自然不会触发。这一行会直接匹配后续无pattern限制的第二个代码块(无pattern默认匹配所有行),被printf打印出来,就出现了首行输出表头的问题。
2. 认为第二个无pattern的action块会因为前一个块的next语句不被执行,这个理解错在哪?
next语句的生效前提是当前行成功匹配前一个块的pattern、进入第一个块执行。如果行不满足第一个块的匹配规则,根本不会进入第一个块,块内的next对这些行完全无效。awk会按顺序匹配后续的pattern-action对,没有pattern的块默认匹配所有行,自然会接住所有没被第一个块处理的行。
3. 把第二个action块的pattern改成FS = "\t"为什么没有改善?
这个写法从根上就错了:
- 首先
FS = "\t"是赋值操作,不是相等判断,awk会把赋值的返回值(非空字符串"\t",等价于布尔真)作为pattern的匹配结果,相当于第二个块还是匹配所有行,和没加pattern没有任何区别。 - 就算写的是相等判断
FS == "\t",FS是全局字段分隔符变量,只要处理全程不主动修改,值不会变,所有行都满足判断条件,还是会接住所有没被第一个块处理的行,问题不会有任何改善。 - 如果要严格按制表符切分字段,必须在awk读取第一行之前的
BEGIN块里设置FS="\t",写在普通块的pattern位置完全起不到预期作用。
4. 调换两个文件传入顺序后首行始终是第一个传入文件首行、输出整体错乱的根本原因是什么?
首行异常的根本原因和第一个问题完全一致:第一个块的pattern用FNR>1跳过第一个文件的首行,但这行因为不满足匹配规则,不会触发next,会直接落入第二个无pattern的块被打印。不管把哪个文件放在第一个参数位置,它的首行FNR都是1,都不满足FNR>1,都会被第二个块打印。
输出整体错乱是因为把无表头的nameemailavg.tab放在第一个位置时,第一个块会把该文件里的邮箱、浮点数值错误存入地址映射数组,后续处理addresses.txt时拿姓名去数组查询,自然匹配不到正确的地址,输出就会完全混乱。
修正后可运行脚本
#!/bin/bash awk ' BEGIN { FS = "\t" } # 处理第一个传入的地址文件,跳过表头存储地址映射 NR == FNR { if (FNR == 1) next full_name = tolower($1) ":" tolower($2) address[full_name] = $4 "," $5 "," $6 "," $7 "," $8 next } # 处理第二个传入的姓名数值文件,匹配地址输出 { full_n = tolower($1) ":" tolower($2) printf ("%-10s %-10s %-55s %06.2f %-10s\n", $1, $2, address[full_n], $4, FILENAME) } ' "$1" "$2"
内容的提问来源于stack exchange,提问作者Backspace Mild
相关产品推荐
相关产品推荐

