【问题标题】:Awk solution for table formatting表格格式化的awk解决方案
【发布时间】:2014-01-02 01:10:05
【问题描述】:

有以下格式的表格。是否有可能有一个 AWK 脚本以排除仅包含数字“1”的列的方式来格式化表格?

ST L1 L2 L3 L4 L5
ST2 1 1 1 1 1
ST2 1 0 1 0 1
ST3 1 0 1 0 1
ST3 0 0 1 1 1
ST4 1 0 1 0 1
ST5 1 0 1 0 1
ST6 1 0 1 0 1
ST7 0 0 1 1 1
ST8 0 0 1 0 1
ST9 1 0 1 0 1

输出应该如下:

ST L1 L2 L4
ST2 1 1 1
ST2 1 0 0
ST3 1 0 0
ST3 0 0 1
ST4 1 0 0
ST5 1 0 0
ST6 1 0 0
ST7 0 0 1
ST8 0 0 0
ST9 1 0 0

我可以理解如何打印列的逻辑,就像结束块中的 NR 值一样,如果它等于每次找到 1 时应该递增的变量,对于给定列(标题 NR==1 和列 $1 除外),打印该列。我的麻烦在于实际上试图打印最后块中的列,因为我正在尝试使用数组并且我仍在学习 AWK 和数组。我确信有一些聪明的方法可以做到这一点,尽管甚至不使用数组并且只是改变 AWK 查看数据的方式。

【问题讨论】:

  • 感谢您的回答,工作良好!现在只是把我的头绕在代码上;)

标签: arrays bash shell unix awk


【解决方案1】:
awk '
NR==FNR {
    if (NR > 1) {
        for (i=1;i<=NF;i++) {
            if ($i != 1) {
                nonOnes[i]
            }
        }
    }
    next
}
{
    ofs=""
    for (i=1;i<=NF;i++) {
        if (i in nonOnes) {
            printf "%s%s", ofs, $i
            ofs=OFS
        }
    }
    print ""
}
' file file
ST L1 L2 L4
ST2 1 1 1
ST2 1 0 0
ST3 1 0 0
ST3 0 0 1
ST4 1 0 0
ST5 1 0 0
ST6 1 0 0
ST7 0 0 1
ST8 0 0 0
ST9 1 0 0

如果您不想在命令行上两次列出同一个文件,您可以调整以添加此 BEGIN 部​​分:

BEGIN { ARGV[ARGC] = ARGV[ARGC-1]; ARGC++ } 

【讨论】:

  • 嗨,Ed - 如何编写代码以仅处理一个文件而不是多个文件?还是依赖多个文件才能工作?
  • 它只适用于 1 个文件。您只需将相同的文件名传递两次。第一次解析文件时,它会找出哪些列不是全一的,接下来它会打印不是全一的列。如果您不想将相同的文件名两次传递给脚本,请参阅我刚刚在帖子底部所做的编辑,以添加与文件名 arg 重复的 BEGIN 部​​分。
【解决方案2】:

这应该可以解决问题:

    {
        # store current line
        line[FNR] = $0

        if (FNR > 1) # skip header
        {
            # select columns
            for (i = 1 ; i <= NF ; i++)
            {
                if ($i != 1) selected[i] = 1
            }
        }
    }

END {
        for (li = 1 ; li <= FNR ; li++)
        {
            # parse current line
            $0 = line[li]

            # pick selected fields
            for (i = j = 1 ; i <= NF ; i++)
            {
                if (selected[i]) $(j++) = $i
            }

            # trim record to selection
            NF = j-1
            print
        }
    }

在埃德莫顿的话之后:

  • l 更改为不那么模棱两可的东西
  • printf 确实是一个语句,但是添加括号也不会造成伤害,或者会不会?
  • 同意print "" 优于printf "\n"
  • 分号是可选的,但不会造成伤害。我觉得看起来像 C 的东西更舒服
  • NR 是一个没有引起注意的错字(因为它产生了预期的输出纯属运气)。我是说 NF。
  • 更改了逻辑,不再添加尾随空格(不再使用 printf)

第二批发言后:

  • 更改了输出记录生成,以避免多余的分隔符。

非常感谢您的校对。距离我上一次认真编写鹰派编程已经快 15 年了,可悲的是,锈迹已经开始了。

【讨论】:

  • 永远不要使用字母 l (el) 作为变量,因为它看起来太像数字 1 (one)。 printf 是一个内置函数,而不是一个函数,所以括号并没有像它看起来那样做。 print "" 是打印 ORS 的正常方式,因此您无需对其进行硬编码。虚假的尾随分号是不可取的。有时使用 FNR 而使用其他 NR 令人困惑。 printf "%s" 将为每个输出行添加尾随空白字符。您在最后一个打印块中使用的是 NR 而不是 NF。
  • 您可以在任何表达式周围添加括号,就像在 C 中一样(因此 return(foo)sizeof(bar))并且它不应该导致损坏,它只会让阅读您的代码的人想知道目的是什么这样做。与在语句末尾添加不必要的 ;s 相同。我希望您的新解决方案会失败,因为每次您遇到想要跳过的 $i 时都会重新编译 $0,因此之后字段编号的值与您最初读入的值不同。或者它可能只是留下额外的字段之间的空白。
  • 我同意你的 cmets 关于风格的看法。我使用 C/C++ 的时间比使用 awk 的时间多得多,所以我只是继承了这些习惯。但是,我认为将 $i 设置为 "" 不会重新编译 $0。如果您为 $0 分配一个新值或添加字段,则 $0 将被重新编译,而不是如果您更改现有值的值。这就是医生所说的,也是经验告诉我的(至少使用 gawk)。将 $i 设置为 "" 确实可能会在输出字段之间留下几个空白,但 OP 并没有明确要求结果在每列之间只有一个分隔符,所以这对我来说似乎可以接受。
  • 将 $i 设置为任何值确实会导致 $0 被重新编译(尝试echo "a b c" | awk -v OFS=',' '{$2=""; print}'),但看起来它仍然留下一个空字段而不是折叠它两侧的 FS,即使那将是最初读取该记录时的行为,所以是的,它只是您最终得到的字段之间的额外空格(在这种情况下是在行尾)。通常这是不可取的,但我想是 YMMV。
  • 你又是对的。 $0 确实是从 $i 记录合成的,但我的意思是,NF 不会改变(将 $i 设置为 "" 不会破坏 $i,除非您之后执行类似 $0 = $0 的操作来强制重新解析记录)。不过,关于行尾分隔符的要点很好。这最终说服了我改变我的代码:)。感谢您的耐心和乐于助人,感谢您对 awk 的掌握。
猜你喜欢
  • 2019-06-12
  • 2020-03-08
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 2011-06-01
相关资源
最近更新 更多