我有一个大的 csv 文件(大约 1000 列),我想仅将标题名称中包含“慢性”一词的列提取到新文件中。我怎样才能做到这一点 ?
例如,如果我有:
gender,chronic_disease1,chronic_disease2
male,2008,2009
期望的输出是:
chronic_disease1,chronic_disease2
2008,2009
注意:列/字段分隔符为逗号“,”。如果没有chronic
匹配则根本没有输出。
答案1
使用磨坊主(可从 Ubuntu“universe”存储库获取)其cut
动词可以选择使用正则表达式来匹配字段名称:
mlr --csv cut -r -f 'chronic' file.csv
chronic
(匹配字段名称中任意位置的子字符串)或更具体地说
mlr --csv cut -r -f '^chronic_' file.csv
(将子字符串锚定到名称的开头,并添加尾随下划线)或
mlr --csv cut -r -f '"^chronic_"i' file.csv
使后者不区分大小写地匹配。
要反转匹配,即选择所有列不是匹配^chronic_
,添加-x
:
mlr --csv cut -x -r -f '"^chronic_"i' file.csv
--csvlite
注意:如果您的输入文件不包含更高级的 CSV 功能(例如 RFC-4180 样式双引号),您也许能够使用更高效的引擎。看文件格式 - CSV/TSV/ASV/USV/等。
如果没有包含该字符串的字段名称chronic
,并且您根本不希望输出而不是空记录,请通过 Miller 的skip-trivial-records
子命令传递提取的数据。
mlr --csv cut -r -f 'chronic' then skip-trivial-records file.csv
答案2
使用 awk:
awk '
BEGIN{ FS=OFS="," }
NR==1{
for(i=1; i<=NF; i++)
found+=col[i]=($i ~ /chronic/)
if(!found) exit
}
{
for(i=1; i<=NF; i++)
printf ("%s", (col[i]? (c++?OFS:"")$i :"") )
printf("%s", (c?"\n" : "") ); c=0
}' infile.csv
我们将Field Separator和Output Field Separator设置为逗号,表示输入文件是CSV文件。
对于第一个输入行(假设是标题行),我们创建一个数组col[]
来存储该行中的每个字段是否包含子字符串“慢性的" 然后 TRUE/1(通过将每个字段与/chronic/
正则表达式匹配)或 FALSE/0(如果不匹配)。
if(!found) exit
如果没有任何字段要输出,这部分代码告诉 awk 退出命令并停止处理输入文件。否则...
...然后对于每个后续行(以及第一行),它循环遍历该行中的每个字段,如果相应的col[i]
值为 1,则打印该字段,否则打印一个空字符串;处理该行后,如果有任何字段输出(当c
计数器非零时;c
计数器也用于在输出时不是第一个字段时在字段之间添加 OFS),它会打印换行符,否则不打印任何内容,并重置c
为0。
答案3
假设字段名称位于 .csv 文件的第一行,如下所示:
$ cat input.csv
gender,chronic_disease1,chronic_disease2
male,2008,2009
以下 perl 单行代码将打印字段名称包含字符串“chronic”的字段:
perl -F, -lane '
if ($. == 1) { # first line of input
# get a list of field numbers & names matching "chronic"
foreach my $f (0..$#F) {
if ($F[$f] =~ /chronic/i) { # case-insensitive
push @out, $f; # get the field numbers
push @outnames, $F[$f]; # get the names too
}
};
last unless (@out); # exit early if there's nothing to print
} else {
print join(",", @outnames) if ($. == 2); # print the header only once
print join(",", @F[@out]) # print the data
}' input.csv
示例输出:
chronic_disease1,chronic_disease2
2008,2009
注意:这仅适用于简单的逗号分隔文件。它不适用于包含嵌入逗号或换行符的引用字段的 CSV 文件。为此,您需要使用 CSV 解析器 - 例如 perl 的文本::CSV,甚至是 Perl 的DBD::CSV模块用于数据库接口它允许您对 CSV 文件执行 SQL 查询,就像它们是 SQL 数据库一样。或者使用磨坊主
答案4
使用乐(以前称为 Perl6)
~$ raku -MText::CSV -e ' \
#read header into @hdr array
my $csv1 = Text::CSV.new;
my $fh1 = "chronic_test.txt".IO.open;
my @hdr = $csv1.header($fh1, munge-column-names => "fc").column-names;
close $fh1;
#read full csv file into @whole array
my $csv2 = Text::CSV.new;
my $fh2 = "chronic_test.txt".IO.open;
my @whole; while $csv2.getline($fh2) -> $row {
@whole.push: $row;
}; close $fh2;
#output array that has been @whole>>.[index] filtered for desired columns
.join(",").put for @whole>>.[@hdr.grep(/chronic/, :k)];'
输入示例:
gender,chronic_disease1,chronic_disease2
male,2008,2009
示例输出:
chronic_disease1,chronic_disease2
2008,2009
Raku 是 Perl 编程语言家族中的一种语言。它具有对 Unicode 的高级支持和强大的正则表达式实现。
Raku的Text::CSV
模块解析有效的CSV,并可以输出有效的CSV。如果您需要接受备用列分隔符(例如制表符),或者如何处理引用字段、空白字段、嵌入换行符和/或逗号等,请检查下面的 Markdown 文档。
以上是按列名读取/过滤 CSV 文件的相当强大(但冗长)的方法。简而言之,标头被读取两次,并使用正则表达式来grep
输出匹配的列。您可以munge
根据需要将列名称切换为其他大小写(uc
、lc
、fc
等)。
底部的markdown文档提供了以下代码来输出CSV文件(修改为仅输出所需的列):
# and write CSV file, filtered as above
my $fh_out = open "new.csv", :w;
$csv.say($fh_out, $_) for @whole>>.[@hdr.grep(/chronic/, :k)];
$fh_out.close;
更有效:请注意,上面的代码实际上将@whole
csv 文件读取到内存中,尽管是逐行读取的。下面的代码仅将@filtered
csv 列读入内存,因此内存效率可能更高。
$
注意:“推广” -sigiled非常重要标量到@
-sigiled大批当使用这样的对象作为“位置索引”。促销可以是以下形式@($index)
或更简单@$index
:
~ % raku -MText::CSV -e ' \
#read header into @hdr array
my $csv1 = Text::CSV.new;
my $fh1 = "chronic_test.txt".IO.open;
my @hdr = $csv1.header($fh1, munge-column-names => "fc").column-names;
my $index = @hdr.grep(/chronic/, :k); close $fh1;
#read filtered csv file into @filtered array
my $csv2 = Text::CSV.new;
my $fh2 = "chronic_test.txt".IO.open;
my @filtered; while $csv2.getline($fh2) -> $row {
@filtered.push: $row.[@$index];
}; close $fh2;
.join(",").put for @filtered;'
https://github.com/Tux/CSV/blob/master/doc/Text-CSV.md
https://docs.raku.org
https://raku.org