我有一个管道|
分隔文件。
文件:
106232145|"medicare"|"medicare,medicaid"|789
我想计算每行中的字段数。我尝试了下面的代码
代码:
awk -F '|' '{print NF-1}'
这会将结果返回为 5 而不是 4。这是因为 awk 将“medicare|medicaid”作为两个不同的字段而不是一个字段
awk -F\| '{print NF}'
给出正确的结果。
纯 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 修复awk
orsed
解决方案并不容易,因为在包含管道和引用的 PSV 字段之上,规范还允许将引号作为字段的一部分。这并不适合一个好的 RegEx 解决方案。
$ 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 和默认模式下进行了测试。修剪注释和输出语句,发现它实际上是一个小程序,尽管它不像人们想象的那么小。
对于|
嵌入|
在此GNU awk v4.0
或更高版本之间的分隔文件应该可以工作:
gawk '{ print NF }' FPAT="([^|]+)|(\"[^\"]+\")"
perl -ne 'print scalar( split( /\|/, $_ ) ) . "\n"'
[文件名]