如何从另一个 csv 文件的选定列动态创建新的 csv?

How can one dynamically create a new csv from selected columns of another csv file?

我动态遍历 csv 文件和 select 符合我需要的条件的列。我的 CSV 以逗号分隔。 我将这些索引保存到一个看起来像

的数组中
echo "${cols_needed[@]}"

1 3 4 7 8

然后我需要将这些列写入一个新文件,我尝试了以下 cut 和 awk 命令,但是,由于数组是动态创建的,我似乎无法找到可以 select 他们一次。我试过剪切、awk 和粘贴命令。

awk -v fields=${cols_needed[@]} 'BEGIN{ n = split(fields,f) }
    { for (i=1; i<=n; ++i) printf "%s%s", $f[i], (i<n?OFS:ORS) }' test.csv

这会引发错误,因为它无法拆分字段,除非我对它们进行硬编码(即使那样,它也只能做 2),按空格拆分。

fields="1 2’

我试过动态创建 -f 参数,但只能像这样在一个循环中使用一个变量

for item in "${cols_needed[@]}";
    do
       cat test.csv | cut -f$item
    done

一次输出一列。

而且我尝试用逗号动态创建它 - 输入为 1,3,4,7...

cat test.csv | cut -f${cols_needed[@]};

这也行不通!

感谢任何帮助!我知道 awk 不像 bash 那样工作,我们不能以相同的方式传递变量。我觉得我有点在兜圈子!提前致谢。

假设您在 bash 中有这个变量:

$ echo "${cols_needed[@]}"
3 4 7 8

而这个 CSV 文件:

$ cat file.csv
1,2,3,4,5,6,7,8
11,12,13,14,15,16,17,18
21,22,23,24,25,26,27,28

您可以通过这种方式在 awk 中 select 该 csv 文件的列:

awk  '
BEGIN{FS=OFS=","}
FNR==NR{split([=12=], cols," "); next}
{
    s=""
    for (e=1;e<=length(cols); e++) 
           s=e<length(cols) ? s $(cols[e]) OFS : s $(cols[e])
    print s
}' <(echo "${cols_needed[@]}") file.csv

打印:

3,4,7,8
13,14,17,18
23,24,27,28

或者,您可以这样做:

awk -v cw="${cols_needed[*]}" '
BEGIN{FS=OFS=","; split(cw, cols," ")}
{
    s=""
    for (e=1;e<=length(cols); e++) 
           s=e<length(cols) ? s $(cols[e]) OFS : s $(cols[e])
    print s
}' file.csv 
# same output

顺便说一句,你完全可以用 cut:

cut -d ',' -f $(IFS=, ; echo "${cols_needed[*]}") file.csv
3,4,7,8
13,14,17,18
23,24,27,28

扩展我的评论:将 bash 数组传递给 awk:

将数组作为 awk 变量传入:

$ cols_needed=(1 3 4 7 8)
$ typeset -p cols_needed
declare -a cols_needed=([0]="1" [1]="3" [2]="4" [3]="7" [4]="8")

$ awk -v fields="${cols_needed[*]}" 'BEGIN{n=split(fields,f); for (i=1;i<=n;i++) print i,f[i]}'
1 1
2 3
3 4
4 7
5 8

通过进程替换将数组作为 'file' 传递:

$ awk 'FNR==NR{f[++n]=;next} END {for (i=1;i<=n;i++) print i,f[i]}' <(printf "%s\n" "${cols_needed[@]}")
1 1
2 3
3 4
4 7
5 8

至于 OP 从 .csv 文件中提取一组特定列的主要问题...

借用 dawg 的 .csv 文件:

$ cat file.csv
1,2,3,4,5,6,7,8
11,12,13,14,15,16,17,18
21,22,23,24,25,26,27,28

扩展将 bash 数组作为 awk 变量传递的建议:

awk -v fields="${cols_needed[*]}" '
BEGIN { FS=OFS=","
        n=split(fields,f," ")
      }
      { pfx=""
        for (i=1;i<=n;i++) {
            printf "%s%s", pfx, $(f[i])
            pfx=OFS
        }
        print ""
      }
' file.csv

注意: 这假设 OP 提供了有效的列号列表;如果对输入(列)数字的有效性有疑问,则 OP 可以添加一些逻辑来解决所述疑问(例如,它们是整数吗?它们是正整数吗?它们是否引用字段(在 file.csv 中)实际存在?等)

这会生成:

1,3,4,7,8
11,13,14,17,18
21,23,24,27,28

你的第一种方法没问题,只是:

  • -v fields=${cols_needed[@]} 更改为 -v fields="${cols_needed[*]}",将数组作为单个 shell 字传递
  • 在BEGIN中添加FS=OFS=",",拆分后(要按空格拆分,FS改为,前)
  • 即。 BEGIN {n = split(fields, f); FS=OFS=","}

此外,如果带引号的 csv 字段中没有嵌入逗号,您可以使用 cut:

IFS=,; cut -d, -f "${cols_needed[*]}" test.csv

如果有 嵌入的逗号,您可以使用 gawkFPAT,只在未加引号的逗号上拆分字段。

这是一个使用它的例子。

# prepend $ to each number
for i in "${cols_needed[@]}"; do
    fields[j++]="$$i"
done

IFS=,

gawk -v FPAT='([^,]+)|(\"[^\"]+\")' -v OFS=, "{print ${fields[*]}}"

将 shell 代码注入到 awk 命令中通常不是很好的做法,但在我看来这没问题。