如何从另一个 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 ....' <(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
如果有嵌入逗号,您可以使用gawk
的FPAT
,仅在不带引号的逗号上拆分字段。
这是一个使用它的示例。
# 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<length(cols); e++
保留订单。以上是关于如何从另一个 csv 文件的选定列动态创建新的 csv?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 pyspark 数据框中读取 csv 文件时读取选定的列?
pandas - 如何仅将 DataFrame 的选定列保存到 HDF5