我正在尝试使用 bash 脚本提取每行中具有最大值的列名称,即列标题值或第一行中同一列的值。我使用以下命令从 CSV 文件中的每一行中提取最大值,但无法找到如何打印其列名称以及最大值:
awk -F ',' '{max=$'$col1';for (i=1;i<=NF;i++) {if ($i > max){max=$i}};print " max: " max}' "$INPUT_PATH/tmp.csv" >>$INPUT_PATH/max1.csv
例子:
示例 CSV 数据:
col1,col2,col3,col4
1,5,2,6
4,0,1,2
1,2,0,0
0,0,7,0
期望的输出:
col4 6 2
col1 4 1
col2 2 2
col3 7 3
有没有办法在上面的命令中执行此操作,或者是否有更好的方法从 CSV 文件中提取所需的信息?
答案1
使用米勒(https://github.com/johnkerl/miller)并运行
mlr --c2n merge-fields -a max -r "^[a-z]" -o value -k then put '
for (key, value in $*) {
if (value == $value_max && key != "value_max") {
$fieldName=key;
}
}' then cut -f fieldName,value_max then reorder -f fieldName,value_max input.csv
你将会拥有
col4 6
col1 4
col2 2
col3 7
答案2
使用tr
和datamash
:
tr , '\t' < file.csv | datamash -H max 1-4 | datamash transpose
输出:
max(col1) 4
max(col2) 5
max(col3) 7
max(col4) 6
笔记:
如果不需要
sed
前导,可以用一些清理输出。max()
如果列数不是特别已知,但肯定小于某个大数,请替换
1-4
为1-1000
,并根据需要添加零。要获得准确的计数,请将 替换为
4
,$(head -1 file.csv | tr , ' ' | wc -w)
或 (通过用POSIX壳)$(read x < file.csv; echo ${x##*l};)
。通过清理和精确计数,生成的丑陋代码将如下所示:
tr , '\t' < file.csv | datamash -H max 1-$(read x < file.csv; echo ${x##*l};) | datamash transpose | sed 's/.*(\|)//g'
输出:
col1 4 col2 5 col3 7 col4 6
答案3
mx=0
如果记录中的所有字段均为负数,则设置初始值的解决方案将失败。设置为$1
是安全的,然后字段可以像@Peter.O 一样循环。
只是为了好玩,这里有一个轻微的awk
变体,它迭代head
数组索引,而不是创建计数器和循环
awk -F',' '
NR==1{split($0,head,FS); next}
{x=1; for (h in head) if ($h>$x) x=h;print head[x], $x }
' file
输出
col4 6
col1 4
col2 2
col3 7
答案4
CSV 的问题是它不能用普通的 shell 工具很好地解析。他们就是做得不好。它能在微不足道的情况下可以完成,但实际上 - 脚本语言是完成这项工作的工具。
我会更perl
个人地思考:
#!/usr/bin/env perl
use strict;
use warnings;
use Text::CSV;
my $csv = Text::CSV->new();
open ( my $input, "<", "your_file.csv" ) or die $!;
$csv->column_names( $csv->getline( $input ) );
while ( my $row = $csv->getline_hr( $input ) ) {
my ( $highest, @rest ) = sort { $row->{$b} <=> $row->{$a} } keys %$row;
print join( "\t", $highest, $row->{$highest} ), "\n";
}
如果用作输入:
first,second,third,fourth
1,3,4,5,
5,4,3,2,
1,1,4,1,
将打印:
fourth 5
first 5
third 4