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

Posted

技术标签:

【中文标题】如何从另一个 csv 文件的选定列动态创建新的 csv?【英文标题】:How can one dynamically create a new csv from selected columns of another csv file? 【发布时间】:2021-12-28 00:15:05 【问题描述】:

我动态地遍历一个 csv 文件并选择符合我需要的标准的列。我的 CSV 用逗号分隔。 我将这些索引保存到一个看起来像

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

1 3 4 7 8

然后我需要将这些列写入一个新文件,并且我尝试了以下 cut 和 awk 命令,但是,由于数组是动态创建的,我似乎无法找到可以一次选择它们的正确命令.我尝试过剪切、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 那样工作,我们不能以同样的方式传递变量。我觉得我有点绕圈子!提前致谢。

【问题讨论】:

试试fields="$cols_needed[*]";另一种选择是通过进程替换作为“文件”传递,例如awk 'FNR==NR f[FNR]=$1;next ....' &lt;(printf "%s\n" "$cols_needed[@]") test.csv @markp-fuso 你能解释一下你的解决方案吗?我目前正在尝试实现第一个(可能是我自己在 awk 函数中的逻辑错误),如果可能的话,我很想尝试第二个。谢谢! 【参考方案1】:

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

-v fields=$cols_needed[@] 更改为-v fields="$cols_needed[*]",将数组作为单个shell 字传递 将FS=OFS=","添加到BEGIN,拆分后(您想在空格上拆分,在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 命令通常不是很好的做法,但在 IMO 中是可以的。

【讨论】:

这是非常非常有用的,感谢您对awk结构的解释。我不知道将 shell 代码注入 awk 并不是最好的,所以 cut 可能是最好的方法,但你的答案对于这两种情况都很棒!【参考方案2】:

扩展我的 cmets re:将 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[*]" 'BEGINn=split(fields,f); for (i=1;i<=n;i++) print i,f[i]'
1 1
2 3
3 4
4 7
5 8

通过进程替换将数组作为“文件”传入:

$ awk 'FNR==NRf[++n]=$1;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

【讨论】:

【参考方案3】:

假设你在 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 中选择该 csv 文件的列:

awk  '
BEGINFS=OFS=","
FNR==NRsplit($0, 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[*]" '
BEGINFS=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

【讨论】:

这非常有用,谢谢。 awk 的语法绝对不是超级直观,所以我将进行更多研究! 注意:for (e in cols) 和 posix:for (variable in array) which shall iterate, assigning each index of array to variable in an **unspecified order**.。 gawk 始终保持顺序,但其他 awk 不会。您可以使用e=0; e&lt;length(cols); e++ 保留订单。

以上是关于如何从另一个 csv 文件的选定列动态创建新的 csv?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 pyspark 数据框中读取 csv 文件时读取选定的列?

如何读取 2 列 csv 文件并创建字典?

pandas为csv添加新的行和列

pandas - 如何仅将 DataFrame 的选定列保存到 HDF5

如何解析 JSON 模式文件并使用许多列约束动态创建新的 Python 类?

如何将新列动态添加到 bigquery 中已存在的表..?