我在 Ubuntu 上使用 Bash,我的问题如下:
我有一个大文本文件,带有标题和分隔符#|#
。
我正在尝试使用 AWK 来获取有关此文件的一些信息。现在我想使用以下表达式计算第 2 列组按第 1 列值分组的总和:
awk 'BEGIN { FS="\\#\\|\\#" }{arr[$1]+=$2} END {for (i in arr) {print i,arr[i]}}' myfile.txt
我得到的输出有两个问题:
首先,如果假设第 1 列采用两个唯一值 value1 和 value2,则 AWK 形成的不是 2 个而是 3 个组:value1、value2 以及 name_column1。
好像它不明白文件的第一行是标题......
第二个问题是我的输出是:
value1 0 value2 0 name_column1 0
所以我们知道输出的最后一行是意外的(如前所述),所以让我们关注前两行。在这里,两个总和都为空,但我知道其中至少一个应该严格大于 0,因为命令
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=2;}END{print sum1;}' myfile.txt
给我
251597850
。
因此,要么是我的最后一个命令(常规求和)有问题,要么是前一个命令(求和+分组依据)有问题。
有人知道如何解决这个问题吗?
编辑:我的文件文本看起来像这样:
Column1#|#Column2#|#Column3
0300#|#0.00#|#0000
其中 0300 是value1
前面提到的(它不是数字而是类别)。
编辑2:
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=2;}END{print sum1;}' myfile.txt
给我 2*(文件中的行数),这显然不是我想要的,所以命令应该是:
awk 'BEGIN { FS="\\#\\|\\#" }{sum1+=$2;}END{print sum1;}' myfile.txt
编辑3:
事实证明,由于分隔符的原因,我的两个命令都是错误的。因此,分组依据的正确命令是:
awk 'BEGIN { FS="#[|]#" } FNR>1 {arr[$1]+=$2} END { for (i in arr) print i,arr[i] }' file.txt
答案1
简单的回答是,在这种情况下 FS 变量是 RE(正则表达式或模式)。因此,如果任何实际数据字符在 RE 上下文中是“特殊”的,则需要在 RE 中对其进行转义,以确保将其视为自身,而不是运算符。
在这种情况下,罪魁祸首是|
,它是交替运算符。它两侧的项目都是替代 RE,其中任何一个都将被视为匹配项。例如,字段分隔符a|u|o|i|e
将在每个元音处拆分字段。
因此, RE#|#
有点多余:它指定#
为字段分隔符两次,并忽略重复。
解决方法是逃避|
.我的首选方法是将 转换|
为括号表达式(字符类),[|]
从而将 降级|
以表示自身。
或者,可以通过 转义字符\
,因此分隔符可以写为#\\|#
。
我说逃避是\
——为什么我写了两遍?这是另一个奇怪的规则(也是反斜杠经常导致 awk 模式出现问题的原因)。
有两种编写 awk RE 的方法:作为模式,如/myRE/
,或作为字符串,如"myRE"
。
该/myRE/
表单(默认情况下)作为布尔值工作,可以单独用作pattern { action }
awk 源模型中的模式,或者在{ if (/myRE/) ...}
.在这种情况下,它与整行匹配,因为没有语法指示表明它还应该应用什么。它还可以与更具体的目标(如字段$6 ~ /myRE/
或变量)进行匹配myVar ~ /myRE/
。在这种形式中,字符由单个 转义\
。
然而,当 RE 被写为字符串时,awk 不知道它稍后可能会作为 RE 被调用。已解析两次:在原始源代码中第一次进行通常的字符串转义(例如\t
制表符、\n
换行符和\\
反斜杠):然后当它与~
运算符或在match()
orsplit()
函数中一起使用时再次进行转义。
声明 FS 被视为字符串,因此任何反斜杠都必须加倍。无论您是在命令行上使用-F
或声明 FS,还是像 那样声明 FS,都是如此。-v FS=
BEGIN { FS = "myRE" }
我提到了“简要回答”,这样的事情几乎总是错误的。有一个例外,还有那个例外的例外。
编写一个单字符正则表达式是很困难的,因为特殊运算符需要一些东西来操作。因此,FS 的任何单字符值都被视为字面上的本身。您可以编写'-F|'
or-v 'FS=|'
或BEGIN { FS = "|" }
并让字段由管道符号分割。
单字符规则的例外是由单个空格组成的 FS(这是默认设置)。这只是将行中的每个单词变成一个字段。作为 awk,简单是一个比较术语:
(1) 分隔符是“空白”,定义为 ASCII 空格、水平制表符和换行符的任意连续混合序列。 (如果替代记录分隔符有效,您只会看到换行符。)
(2) 整个行中的前导和尾随空白不是字段分隔符。 (如果任何其他 FS 位于行的开头或结尾,则在其之前或之后分别有一个隐含的额外空白字段。)
我的首选参考是GNU/awk 在线手册。
尽管这个答案本身长得离谱且复杂,但该手册在第 3 节——正则表达式中用了大约 600 行,在第 4.5 节——指定如何分隔字段中又用了 250 行。