• AWK语言第二版 2.3转换


    2.3 转换

    把输入转换到输出是计算机做的事,但Awk做的是一种特定的转换:输入文本,对所有或部分行做些适当的修改,然后退出。

    回车

    对于如何结束一行,Windows令人遗憾地选择了与macOS和Unix不一样的实现方式(这完全没有必要)。在Windows上,每个文本行以一个回车符 \r 和一个换行符 \n 结束,而macOS和Unix只要一个换行符。出于各种充分的理由(包括作者们从Unix早期以来就开始积累的经验和接受的熏陶),Awk使用了“仅换行符”的模型,尽管输入是Windows格式,它还是能正确处理。

    我们可以使用Awk的文本替换函数 sub,来将每行末尾的 \n 干掉。函数 sub(re, repl, str) 会将 str 中匹配第一个正则表达式 re 的内容,替换成文本 repl。如果没有 str 参数,则会对 $0 进行匹配和替换操作。因此下面的程序

    { sub(/\r$/, ""); print }

    会将所有行末的回车符号去掉,并输出该行。

    还有个函数 gsub 也类似,但会将所有匹配 re 的内容都进行替换,而不仅仅替换第一个; g 代表“全局的”(global)。sub 和 gsub 都会返回替换的次数,这样你就能知道字符串 str 是否有改变。

    反过来的话,也很容易在每个换行符前面加上回车,如果本来没有的话:

    { if (!/\r$/) sub(/$/, "\r"); print }

    正则表达式 /\r$/ 如果不匹配的话,说明行末没有回车符,就会进行替换操作。正则表达式在附录 A.1.4中会详细说明。

    多列

    下一例子是将输入内容用多列的方式来输出,当然这里假定输入的每行都很短,像是文件名或者人名。比如下面有一列的人名

    1. Alice
    2. Archie
    3. Eva
    4. Liam
    5. Louis
    6. Mary
    7. Naomi
    8. Rafael
    9. Sierra
    10. Sydney

    会转换成

    1. Alice Archie Eva Liam Louis Mary Naomi
    2. Rafael Sierra Sydney

    这个程序展现出在程序设计的方案选择上的多样性,接下来我们会探究其中的两个。其他的可以当作很好的练习,特别是当你的需求和作者不一样时。

    一个基本的选择,是将所有输入都读完,以便搞清楚输入范围?还是边读边输出(流处理的概念),而不管后面的输入会是什么样?

    另一个选择,是以行的顺序(作者选了这个),还是以列的顺序进行输出呢?如果是后者,那么在读完所有输出之前,不能进行任何输出。

    先来写流的版本。假定输入行不超过10个字符宽,那么算上每列之间加2个空格,一行放5列的话,不会超过60个字符(5*10+4*2= 58)。针对太长的行,我们的选择也很多,比如截断(可以给提示也可以不给),或者在中间加省略号,或者可以拆成多列来打。还能把这些选择作为程序的参数,供用户选择,不过对这个简单的例子来说就实在过头了,没有必要。

    下面这个流版本,会默默地截断超长行;可能是最简单的版本了:

    1. # mc: 多列打印的流版本
    2. { out = sprintf("%s%-10.10s ", out, $0)
    3. if (n++ > 5) {
    4. print substr(out, 1, length(out)-2)
    5. out = ""
    6. n = 0
    7. }
    8. }
    9. END {
    10. if (n > 0)
    11. print substr(out, 1, length(out)-2)
    12. }

    sprintf 是 printf 的一个特殊版本,它不会将格式化的字符串打印到输出流,而是把它作为函数返回值。其中的格式化字符串 %-10.10s,用来将字符串截断在10个字符宽度内并保持左对齐。

    第二行  if (n++ > 5)   展示了 ++ 操作符一个细微但非常重要的特性。如果 ++ 作为变量的后缀,则对应表达式的值是该变量递增之前的值;表达式求值之后,变量才会递增。而如果作为前缀,则会先进行递增,再返回(递增后的)值。【所以老爷子是故意在这里写个有缺陷的代码?前面明明说是想输出5列的,这里却输出了7列】

    下面这个版本,会收集所有的输入,以计算出最宽的域,并使用该宽度值来构造合适的 printf 字符串,将字符格式化输出到保证够宽的列中。注意其中 %% 在格式化字符串中表示 %本身。

    1. # mc:多行打印
    2. { lines[NR] = $0
    3. if (length($0) > max)
    4. max = length($0)
    5. }
    6. END {
    7. fmt = sprintf("%%-%d.%ds", max, max) # 构造格式化字符串
    8. ncol = int(60 / max + 0.5) # int(x) 会返回 x 的整数值
    9. for (i = 1; i <= NR; i += ncol) {
    10. out = ""
    11. for (j = i; j < i+ncol && j <= NR; j++)
    12. out = out sprintf(fmt, lines[j]) " "
    13. sub(/ +$/, "", out) # 删掉行尾的空格
    14. print out
    15. }
    16. }

    其中这三行会将格式化后的原行以及两个空格加入到 out 的后面,这样就构成了输出行。

    1. for (j = i; j < i+ncol && j <= NR; j++)
    2. out = out sprintf(fmt, lines[j]) " "
    3. sub(/ +$/, "", out) # 删掉行尾的空格

    循环结束后,sub 将行末的空格去掉,利用 / +$/ 这个正则表达式匹配行末的一个或多个空格。当然你也许会觉得一开始就不应该加上行末的空格,但这样你得在for循环里加判断代码,还更麻烦。所以我们在两个版本中都采用这种方法。【这个版本的ncol计算也有问题,没把行之间的2个空格算上,难道也是老爷子留的彩蛋?】

    练习2-3. 实现多行程序的参数化(即 全部读完再处理还是流式处理,按行顺序还是列顺序, 针对超长和的处理,比如截断(可以给提示也可以不给),或者在中间加省略号,或者可以拆成多列来打)

  • 相关阅读:
    DIY USB3.0 SM2246XT+双贴闪迪15131颗粒256G固态U盘
    diy arduino 逻辑分析仪
    football 篮球数据集-目标检测548张
    自然语言处理 - 字词嵌入
    Linux 基础(一)——Linux简介、目录管理、文件管理
    【大数据之Kafka】十二、Kafka之offset位移及漏消费和重复消费
    多线程之线程池
    MATLAB线性函数拟合并预测
    ​力扣解法汇总808. 分汤
    工厂设备管理维护系统开发
  • 原文地址:https://blog.csdn.net/baluzju/article/details/132823664