【问题标题】:Using awk, is there a simple way to group fields with spaces使用awk,有没有一种简单的方法可以用空格对字段进行分组
【发布时间】:2020-03-27 22:51:35
【问题描述】:

我有一个包含如下数据的文件:

New York  100 2 17 12
California 200 10 8 3
Montana   50 25  3 0

我希望将州名视为单个字段,然后计算字段 2 占字段 3 的百分比,并忽略其他字段。

所以我希望输出是

New York  2%
California 5%
Montana   50%

这样就可以得到州名awk -F [0-9] '{print $1}'

但随后其余字段完全无法使用。

如果我不使用字段分隔符,New 和 York 将获得单独的字段编号,而其他字段编号为“减一”。

我可以在 awk 中执行此操作,还是应该切换到我知道一点的 ruby​​?

【问题讨论】:

  • 您的示例中的空格是全部空白字符还是其中一些是制表符?不,你不应该切换到 Ruby。
  • 所有空格,没有制表符。
  • 空白包括水平制表符、垂直制表符、空白、换行符、换页符等。我想您的意思可能是“所有空白,没有制表符或其他空白”。
  • 是的,都是“空格”字符。 ASCII 32。抱歉命名不当
  • 是的,“空白”和“空格”这两个词有这么多不同的含义,这很烦人也很不幸。我倾向于认为blank character 是ASCII 32(通常在代码中由" " 表示),blank[:blank:] POSIX 字符类中的任何字符集(通常在代码中表示为\b) 和 space[:space:] POSIX 字符类中的任何字符集(通常在代码中由 \s 表示)。例如,请参阅gnu.org/software/gawk/manual/gawk.html#Bracket-Expressions,了解这些字符类中的字符定义。

标签: awk


【解决方案1】:

您可以在awk 中轻松完成。诀窍是找到第一个以数字开头的字段,这样您就可以容纳像"New York" 这样的名称。例如

awk '{
    n=0; name=""
    for(i=1;i<=NF;i++)
        if($i ~ /^[0-9]/) {
            n=i; break
        }
        else
            name=name?name" "$i:$i
    print name, $(n+1)/$n*100"%"
}' file

其中变量n 用于通过循环遍历每个字段并将第一个字符与[0-9] 进行比较来捕获以数字开头的第一个字段的字段编号。如果测试为真,则n 设置为i 并且循环中断,否则字符字段与name 连接。(假设您有2 个带数字的字段)

您可以选择-复制上面的脚本,然后用鼠标中键将其粘贴到保存文件的目录中的 xterm 中(更改文件名以匹配您的数据文件之后)将其与您的数据放在一起,您将获得:

$ awk '{
>     n=0; name=""
>     for(i=1;i<=NF;i++)
>         if($i ~ /^[0-9]/) {
>             n=i; break
>         }
>         else
>             name=name?name" "$i:$i
>     print name, $(n+1)/$n*100"%"
> }' file
New York 2%
California 5%
Montana 50%

【讨论】:

  • 你能解释一下这条线是如何工作的吗? name=name?name" "$i:$i
  • 当然,name 只是州名。为了处理一个州名中的多个单词,我们从一个空的name="" 开始。然后我们遍历字段for (i = 1; i &lt;= NF; i++)。如果字段以数字/^[0-9]/ 开头,我们找到了第一个数字字段,否则如果它不以数字开头,我们知道它是名称的一部分,并将字段与名称name = name ? name" "$i : $i 连接起来。 test ? if_true : if_false 子句称为 三元 运算符。如果name(表示非空)?,那么我们添加一个空格和当前字段name" "$i,否则只需先添加$i
【解决方案2】:

您可以使用最后一个字段作为参考点。需要 gawk/mawk 来丢弃最后四个字段:

$ awk '{p=$(NF-2)*100/$(NF-3); NF-=4; print ($0"\t"p"%")}' file
New York   2%
California 5%
Montana    50%

便携式替代方案是:

awk '{p=$(NF-2)*100/$(NF-3); sub(/( +[^ ]+){4}$/,""); print ($0"\t"p"%")}' file

【讨论】:

  • 好的,倒数很聪明:) 唯一的缺点是你被这个数量的字段困住了——但通常这不是问题。
  • @David 真正困扰我的是根据标准降低 NF 会产生未定义的结果。谢谢顺便说一句
  • 如果您使用NF &gt; 5 然后split 记录到一个数组中,然后将第一个NF - 4 字段作为名称,接下来的2 个字段来计算百分比呢? (我也比我最初的猜测更喜欢这个)
  • @David 需要通过在循环中加入 NF - 4 字段来构建第一个字段,因此不会是单行的。不过好主意,如果您愿意将其添加到您的答案中作为替代解决方案,我肯定会支持
【解决方案3】:

假设最后总是有固定数量的字段,您可以根据以下记录使用该信息动态调整字段:

pax> echo; printf 'New York 100 2 17 12\nCalifornia 200 10 8 3\nMontana 50 25 3 0\n' | awk '
+++> {while(NF>5){$1=$1" "$2;for(i=2;i<NF;i++){$i=$(i+1)};$NF="";NF=NF-1};print $1","$2","$3","$4}'

New York,100,2,17
California,200,10,8
Montana,50,25,3

您可以通过 , 分隔符看到字段 1 已由 NewYork 两个字段组合而成。详细检查该脚本:

while (NF > 5) {                 # Loop until entire name combined into field 1.
    $1 = $1" "$2                 # Join field 1 and 2.
    for (i = 2; i < NF; i++) {   # For every field 2 onward.
        $i = $(i+1)              # Copy following field to this field,
    }                            #     includes blanking last field.
    NF = NF - 1                  # Reduce field count.
}
# At this point field1 is whole name and fields 2-5 are values.

【讨论】:

    猜你喜欢
    • 2015-08-27
    • 2013-03-08
    • 1970-01-01
    • 2022-01-23
    • 1970-01-01
    • 1970-01-01
    • 2011-06-29
    • 2013-02-21
    • 2010-12-05
    相关资源
    最近更新 更多