【问题标题】:Count number of column in a pipe delimited file计算管道分隔文件中的列数
【发布时间】:2013-07-07 15:56:04
【问题描述】:

我有一个管道 | 分隔文件。

文件:

106232145|"medicare"|"medicare,medicaid"|789

我想计算每行中的字段数。我试过下面的代码

代码:

awk -F '|' '{print NF-1}'

这会将结果返回为 5 而不是 4。这是因为 awk 将“medicare|medicaid”作为两个不同的字段而不是一个字段

【问题讨论】:

  • 我能想到你得到 5 而不是 4 的唯一方法是,如果你真的做了awk -F\" ... (或者代替双引号,出现4次的其他字符之一在字符串中,例如 di...)。事实上,使用NF-1,你应该得到 3 代替...
  • 但是您的示例不包含“medicare|medicaid” ??
  • 如果您的数据可以在引用的字段值中包含分隔符,那么您需要一个专门的 CSV 样式解析器。很有可能csvfix 可以满足您的需求。另见Linux tool to parse CSV files。有 Perl 模块可以提供帮助; Python 和 Ruby 很可能也有可以提供帮助的模块。

标签: linux perl shell awk


【解决方案1】:
awk -F\| '{print NF}'

给出正确的结果。

【讨论】:

  • -1 - 当文件包含在通用分隔文件中有效的字段(例如“medicare|medicaid”)的一部分时,这会中断。
【解决方案2】:

纯 Unix 解决方案(没有 awk/Perl):

$ cat  /tmp/x1
1|2|3|34
4534|23442|1121|334434

$ head -1 /tmp/x1 | tr "|" "\012" | wc -l
4

Perl 解决方案 - 1-liner:

$ perl5.8 -naF'\|' -e 'print scalar(@F)."\n";exit;' /tmp/x1
4

但是!!!!重要!!!

这些解决方案中的每一个 - 以及其他答案中的那些 - 都不能 100% 工作!

也就是说,当它是一个真正的“管道分隔”文件时,它们都会中断,管道是字段中的有效字符(并且被引用的字段),真正的 CSV 文件的工作方式.

例如

$ cat /tmp/x2
"0|1"|2|3|34
4534|23442|1121|334434
$ perl5.8 -naF'\|' -e 'print scalar(@F)."\n";exit;' /tmp/x1
5   <----- BROKEN!!! There are only 4 fields, first field is "0|1"

要解决这个问题,应该使用适当的 CSV(或分隔文件)解析器,例如 Perl 中的解析器:

$ perl5.8 -MText::CSV_XS 
-ne '$csv=Text::CSV_XS->new({sep_char => "|"});  $csv->parse($_); 
print $csv->fields(); print "\n"; exit;' /tmp/x2

打印正确的值

4

请注意,简单地使用复杂的 RegEx 修复 awksed 解决方案并不容易,因为在包含管道和引用的 PSV 字段之上,规范还允许 引用 也是该字段的一部分。这不适合一个好的 RegEx 解决方案。

【讨论】:

  • trheadwc 并不比 awk 更“纯 unix”...perl 是一个稍微不同的故事...
  • @twalberg - 一些遗留下来的 unix 可能没有 awk 或 Perl。或者安装了 Unix util 包的 Windows 系统
  • 不能不同意perl,但awk 是SUS、LSB 和其他类似标准的一部分。当然,有人可以故意选择不安装某些核心软件包,但这并不会使他们的安装更加“纯粹”(事实上,可能会使其更加“破碎”)......
  • @twalberg - 你使用“核心包”这个词意味着你不是在谈论比那些包管理器更老/更奇怪的 unix。
  • 相反,我指的是更一般意义上的“包”——无论是 RPM,还是 tar 存档或 sharuuencoded 文件,它仍然是一个“包”在一般意义上。虽然我可能从未在 PDP-11 上工作过,但我至少记得从软盘安装 SunOS 2.5,并在 AT&T SVR2 系统上学习 C...
【解决方案3】:
$ cat fieldparse.awk
#NR > 1 { print "--"; }

# Uncomment printf/print in the for loops to see
#   each field on a separate line as well as the commented line above (to show that it works).
{
    nfields = 0;
    for (i = 1; i <= NF; i++) {
        if ($i ~ /^".*[^"]$/)
            for (; i <= NF && ($i !~ /.*"$/); i++) {
                #printf("%s%s", $i, FS);
            }
        #print $i;
        nfields++;
    }
    print nfields;
    if (FILENAME == "-")
        FILENAME = "(standard input)";
    filenames[FILENAME] = sprintf("%d %d", FNR, nfields);
}

END {
    print NR, "total records processed";
    for (f in filenames) {
        split(filenames[f], fn, " ");
        printf("\t* %s: %d records with %d fields\n", f, fn[1], fn[2]);
    }
}

$ awk -F'|' -f fieldparse.awk demo.txt

它适用于任何不是双引号的单字符分隔符,这意味着标准制表符分隔、CSV 等格式(无论如何都是标准的......)

输出格式只是说明性的,最后有点装饰性,但内容仍然很有用恕我直言,例如处理多个文件。无论如何,我希望它有所帮助! :-)

编辑

这是使用 mawk 和 GNU awk (gawk) 进行的测试,后者在传统、POSIX 和默认模式下进行了测试。修剪 cmets 和输出语句,发现它实际上是一个小程序,尽管它不像人们想象的那么小。

【讨论】:

    【解决方案4】:

    对于在此GNU awk v4.0 或更高版本之间嵌入|| 分隔文件应该可以工作:

    gawk '{ print NF }' FPAT="([^|]+)|(\"[^\"]+\")"
    

    【讨论】:

    • -1 - 当文件包含在通用分隔文件中有效的字段(例如“medicare|medicaid”)的一部分时,这会中断。
    • @DVK 对不起,好点。我已经更新了带有嵌入式管道的线路的解决方案。
    • 现在,尝试更新它以说明双引号是字段文本的一部分,您的更新版本无法使用)(可能使用正则表达式,但可能是痛苦的!)
    • @DVK 你是绝对正确的。用 awk 解析 csv(尽管用管道分隔)就像解析 xml。会在一些奇怪的边缘情况下中断。
    【解决方案5】:

    perl -ne 'print scalar( split( /\|/, $_ ) ) . "\n"' [文件名]

    【讨论】:

      猜你喜欢
      • 2015-07-16
      • 1970-01-01
      • 1970-01-01
      • 2019-07-23
      • 2019-08-27
      • 1970-01-01
      • 2022-10-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多