【问题标题】:[[ Coding in AWK ]][[ Coding in AWK ]]
【发布时间】:2014-09-29 11:26:57
【问题描述】:

第一个:

  1. 这是我第一次写一个只有 AWK 的代码。
  2. 我的背景是 BASH 思维。
  3. 我的目标是弄清楚 AWK 的想法,然后开始编写我自己的 AWK 代码。

所以,我想知道如何编写这样的 AWK 代码:

  1. \n_ _ _ 替换为;_(每个_ 都是一个空格)。
  2. 找到一种模式并仅打印具有该模式的所有行。
  3. 将每个分号替换为\nC1 \[,并将前一行末尾的换行符替换为]
  4. 用下一行补充信息更新前一行。
  5. 将每行最后一个单词的模式与包含国家/地区名称列表的外部文件进行比较。

到目前为止我所做的是(这只涵盖到第三行):

awk '
    {gsub(/\n   /,"; ")} 
    /^C1 \[/ 
    {gsub(/; /,"\nC1 ")}
    ' file 

非常感谢任何线索。

【问题讨论】:

  • 我创建了一个文件File 2,其中包含一个国家/地区名称列表,并将其放入
  • 在介绍中阅读第五步
  • 一个问题是awk 读取行,所以$0 永远不会在换行后包含空格。您可以使用/^ / 在行首搜索三个空格,但您已经打印了上一行。这表明您需要累积输入行以进行处理。或者,您需要更深入地了解用于检测记录结束 (RS) 的模式。
  • 非常感谢@JonathanLeffler,是的,我正在学习如何积累输入行,就像在 bash 编码中一样,但在 awk 编码中(如果可能的话)。我的理由是 awk 绝对是处理大数据文件最快的语言,我想学习它。最大的“但是”是我正在尝试用 bash 编写一个 awk 代码。当然,答案是用 awk 思考,但对我来说就像开始用法语思考......首先我看到了结构,否则我会窒息,就像从法语开始处女一样。非常感谢:)

标签: regex awk


【解决方案1】:

首先,正常的 awk 代码假定逐行处理。事实上,如果没有额外的命令行选项,awk 会删除所有"\n"。因此,您尝试用 "; " 替换 "\n " 没有多大意义,因为您的代码永远不会看到“\n”。

要将多行合并为一行,您可以使用printf 而不是print

/^[AC]\w/ {
            printf "\n%s",$0
            next
        }
/^\s/ {
            printf " %s",$0
            next
        }

上面的代码说,当遇到以AC 开头的行后跟一个单词字符(例如U1i)然后打印一个换行符后跟该行.当它遇到以空格字符(空格或制表符)开头的行时,打印没有换行符的行。

当然,这有一些小问题。例如,它在输出的开头打印一个换行符,最后没有换行符。而且它也不会去除以空格开头的行的前导空格。但我们暂时先把它留在这里。

这回答了您的第一步问题。

但这不是你想要的。您想要的是规范化 C1 值。所以首先让我们编写忽略除C1 值之外的所有内容的代码:

/^C1/ {print}

好的,现在我们需要处理所有属于C1 的选项卡行,所以我们需要稍微修改一下代码:

BEGIN {c1_found=0}

/^C1/ {
        c1_found=1
        print
        next
    }

/^\s/ {
        if (c1_found == 1) {
            print "C1 " $0
        }
        next
    }

    {
        c1_found = 0
    }

所以。我们创建一个变量c1_found 来标记我们在C1 块中。我们在 BEGIN 块中将其初始化为 0,并在遇到 C1 块时将其设置为 1。现在,每当我们看到以空格开头的行时,我们会检查我们是否在 C1 块中,并通过在其前面附加 "C1 " 来打印该行。

现在我们要处理标准化。这是困难的一点。让我们为此编写一个函数,因为它将在两个地方调用:

function normalize (c1_line) {
    split(c1_line,a,"[[]]")
    c1 = a[1]
    names = a[2]
    location = a[3]

    split(names,b,";")

    for (nam in b) {
        print c1 b[nam] location
    }
}

此函数首先将其输入拆分为[],这会将行分成三个组成部分。然后它用; 分割第二部分。最后,它遍历名称并为每个名称打印一行。

现在让我们将其包含到我们的代码中:

function normalize (c1_line) {
    split(c1_line,a,"[[]]")
    c1 = a[1]
    names = a[2]
    location = a[3]

    split(names,b,";")

    for (nam in b) {
        print c1 b[nam] location
    }
}

BEGIN {c1_found=0}

/^C1/ {
        c1_found=1
        normalize($0)
        next
    }

/^\s/ {
        if (c1_found == 1) {
            normalize("C1 " $0)
        }
        next
    }

    {
        c1_found = 0
    }

上面的代码生成file1。现在,要生成 file2,最简单的方法是编写另一个仅打印国家/地区名称的脚本,然后通过sortuniq(或sort -u,如果您的排序可以做到这一点)进行管道传输。但是,如果您坚持在 awk 中执行,则可以(实际上它非常简单易读)。首先,我们使用关联数组来跟踪出现的所有国家/地区:

function normalize (c1_line) {
    split(c1_line,a,"[[]]")
    c1 = a[1]
    names = a[2]
    location = a[3]

    split(names,b,";")

    for (nam in b) {
        print c1 b[nam] location
    }
    countries[$NF] = 1
}

然后我们将国家数组打印到一个文件中:

END {
    for (c in countries) {
        print c >> file2.txt
    }
}

我将由您来组装完整的程序。但是正如您所看到的,与 C 或 perl 或汇编的 bash 或 pascal 相比,在 awk 中编程是完全不同的。它更像是在正则表达式中编程——因为 awk 有一个内置循环,当找到匹配项时会不断扫描输入并执行代码。

所以,作为一个例子,下面的程序:

BEGIN {print "starting"}
/^mango/ {print "ha"}
/^monkey/ {print $0}
END {print "done"}

完全等同于:

/^monkey/ {print $0}
END {print "done"}
BEGIN {print "starting"}
/^mango/ {print "ha"}

当匹配是彼此的子字符串时,存在细微差别。例如,当您想与/^HELLO/ 分开触发/^HEL/ 时。但一般执行不会自上而下线性读取。

另外,请注意 awk 只有全局变量。所以使用好的变量名来避免覆盖其他地方的一些其他变量。

【讨论】:

  • 请记住,在函数中,您可以通过 function funcname(arg1, arg2, local1, local2, local3) 创建“本地”名称(通常用多个空格分隔 arg1, arg2local1, local2, local3 是一种非正式指示,表明未使用的参数实际上是局部变量. 但这只是在一个函数中;其他变量实际上是全局的。
  • 非常感谢 slebetman 和 @JonathanLeffler 的建议,祝您有美好的一天:D
  • 修复了我在数组迭代中发现的一个错误。我忘了for x in y 给出的是索引而不是值。
猜你喜欢
  • 2013-12-16
  • 2012-06-11
  • 2020-12-31
  • 1970-01-01
  • 2013-05-01
  • 2010-12-27
  • 2010-12-20
  • 2012-12-09
  • 2012-02-26
相关资源
最近更新 更多