AWK语言第二版 2.4摘要 2.5个人数据库

2.4 摘要

Awk可用于从包含表格数据的文件中快速获取摘要信息:最大值、最小值、列值的和,及其他类似的内容。这些也能用于数据校验:每个域里面有什么值,是否有空值等等。本节包含了几个这样的例子,下一章会有更多(下一章会讨论探索性数据分析)

下面的 addup 脚本将输入的每列值加起来,并在末尾输出总和。它也是个数组下标的简单例子:

# addup: 将每个域的值分别加起来

{ for (i = 1; i<= NF; i++)
    field[i] += $i
  if (NF > maxnf)
    maxnf = NF
}

END {
  for (i = 1; i <= maxnf; i++)
    printf("%6g\t", field[i])
  printf("\n")
}

如第一章所述,第二行的 += 操作符将左边的变量加上右边表达式的值。它被称为“赋值操作符”,而该语句是 field[i] = field[i] + $i  的简写。所有的算术运算符都允许这种简写方式。

如果某列的一部分,甚至整列都不是数值的话会怎么样?完全没问题。Awk将字符串开头的数值部分当作是数值,如果字符串不是由数值开始的,则它的数值为0。比如字符串 "50% off" 的数值是 50。 

在作者们自己使用的脚本中,包含了 addup 的各种变体,比如计算每个域的最大最小值,计算简单的统计值如平均值和方差,统计非空值的数量,打印出现频率最高和最低的项,等等。所有这些都能用来快速了解数据的属性,或者数据中的异常或潜在的错误。

一些电子表格工具也能提供类似的功能,比如Google Sheets,还有Python的Panda库。用Awk的优点,是你能按自己的要求来定制,当然对应的缺点是你要自己写点儿代码。

2.5 个人数据库

Awk的另一个应用领域是维护个人数据库。如果你有健身,你可能已经保存了每天走或跑了多远,每天的体重,以及其他感兴趣的数值。有大量的app都包含这些功能,还带有不错的界面,漂亮的图表。而潜在的问题是某些app会侵犯隐私,而且它们也不一定总是能准确满足你的需求。

一个替代方案是把数据保存在纯文本文件里面,并用Awk和其他工具来处理。下面有个简单的例子。假如你记录了每天走过的步数,而目标是每天一万步。创建一个叫 steps 的文件,每行有两列,分别是日期和步数:

6/24/23 9342
6/25/23 4493
6/26/23 4924
6/27/23 16611
6/28/23 8762
6/29/23 15370
6/30/23 17897
7/1/23  6087
7/2/23  7595
7/3/23  14347
7/4/23  15762
7/5/23  20021
...

上面这些其实是我们其中一个作者的真实数据,他去了一个很棒的地方度假,天气好的时候每天都散步。

你可以根据难易程度来决定,按时间顺序还是倒序来写文件,我们用的是顺序。

不管顺序倒序,都能用脚本来计算一段时间内的平均步数。下面这个写得稍微复杂的版本,可以计算出周期是7天、30天、90天、1年和一生的滑动平均值。

awk '
{ s += $2; x[NR] = $2 }

END {
  for (i = NR-6; i <= NR; i++) w += x[i]
  for (i = NR-30; i <= NR; i++) m += x[i]
  for (i = NR-90; i <= NR; i++) q += x[i]
  for (i = NR-365; i <= NR; i++) yr += x[i]
  printf(" 7: %.0f 30: %.0f 90: %.0f 1yr: %.0f %.1fyr: %.0f\n",
    w/7, m/30, q/90, yr/365, NR/365, s/NR)
} ' $*

输出为

7: 9679  30: 11050  90: 11140  1yr: 10823  13.7yr: 10989

文本文件很适用于医疗数据(体重、血糖、血压),个人理财(股票价格、投资价值)及其他很多领域。用Awk或类似的工具来处理简单的平面文件,有着实在的好处:数据是你的,而不是别人的;很容易用你喜爱的文本编辑器来更新数据;可以用你最初没想到的方式来处理数据。

比如,我们可以对上面的程序做个扩展,用柱状图方式输出所走的不同步数的频率,以便知道你的锻炼习惯是否规律:

awk '
{ s += $2; x[NR] = $2; dist[int($2/2000)]++ }

END {
  for (i = NR-6; i <= NR; i++) w += x[i]
  for (i = NR-30; i <= NR; i++) m += x[i]
  for (i = NR-90; i <= NR; i++) q += x[i]
  for (i = NR-365; i <= NR; i++) yr += x[i]
  printf("  7: %.0f  30: %.0f  90: %.0f  1yr: %.0f  %.1fyr: %.0f\n",
    w/7, m/30, q/90, yr/365, NR/365, s/NR)

  scale = 0.05
  for (i = 1; i <= 10; i++) {
    printf("%5d: ", i*2000)
    for (j = 0; j < scale * dist[i]; j++)
      printf("*")
    printf("\n")
  }
} ' $*

这个程序打印星号组成的行,星号的个数与走了对应步数的天数成正比。

 2000:  ****
 4000:  *********************
 6000:  ************************************
 8000:  *****************************************
10000:  *******************************************
12000:  **************************************
14000:  **********************************
16000:  *********************
18000:  *******
20000:  *

如果步数大于20000的话,这个程序会有问题。另外要有足够的数据输入,否则那些更长时间范围的平均步数就没有意义。有时输出行也可能太长,所以程序用了比例因子来使它们保持在范围内。

现实中,通常很少人会用上面这种图表,因为有太多优秀的图表库/包可用了。在7.2节,我们会展示一个程序,它生成的Python程序能画出漂亮的图表。另外,大家的标准实践都先是生成CSV文件,接着用Excel、Google Sheets或其他类似的程序来创建高质量的图表,虽然这样需要多几次手工操作。不过,在把数据传给这些工具之前,用Awk来管理和清洗数据,是不错的做法。

股票价格

还有一种很多人都非常感兴趣的个人数据:投资数据。我的钱放哪里了?收益如何?本小节给出了一个专用的例子,即从网页抓取股票价格的脚本。

网页抓取是个通用的应用。有些网站有你需要的信息,但格式不对,而你想要把这些信息一次性或周期性地提取出来。这里演示的是股票价格的抓取,但同样的方法也能用在其他类型的数据上。

网页为人类读者做了格式化,因此程序的任务是在保留所需信息的情况下去掉格式化信息。如果有个不错的HTML解析器(如Python优秀的BeautifulSoup库),这个操作会非常简单,不过Awk的优势是已经随操作系统装好了,而且很容易上手。

这里使用的网站是 bigcharts.marketwatch.com,查询地址如下(网络变化很快,有可能当你读到这篇文章时,这个地址已经不能用了)

bigcharts.marketwatch.com/quotes/multi.asp?view=q&msymb=tickers

其中的 tickers 是股票代码,多个股票代码用加号分隔,比如

$ quote aapl+amzn+fb+goog
  AAPL  134.76
  AMZN  98.12
    FB  42.75
  GOOG  92.80
$

这里我们使用Unix上的宝贵工具 curl 来获取网页,并用Awk去掉所有的HTML。找到有用的数据需要实验:研究输出样本,并使用正则表达式清除掉没用的东西。幸运的是这个网站很干净且很有条理,所以做起来不难。程序如下。注意,程序刚开始用了反斜杠来实现超长行的续行。

# quote - 根据股票代码获取股价

curl "https://bigcharts.marketwatch.com/quotes/\
multi.asp?view=q&msymb=$1" 2>/dev/null |
awk '
  /<td class="symb-col"/ {
     sub(/.*<td class="symb-col">/, "")
     sub(/<.*/, "")
     symb = $0
     next
  }
  /<td class="last-col"/ {
     sub(/.*<td class="last-col">/, "")
     sub(/<.*/, "")
     price = $0
     gsub(/,/, "", price)
     printf("%6s %s\n", symb, price)
  }
'

使用这个程序时,命令行参数即股票代码的列表,被放在shell参数 $1 中 传给了curl。而 2>/dev/null 是shell的惯用结构,用于丢弃 curl 程序在处理过程中的输出报告;在这是可选的。

练习2-4 自己写个股价跟踪程序,让它输出CSV格式,这样输出结果就能用Excel或其他程序来生成图表了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值