如何从另一个 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
如果有 嵌入的逗号,您可以使用 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 命令中通常不是很好的做法,但在我看来这没问题。
我动态遍历 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
如果有 嵌入的逗号,您可以使用 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 命令中通常不是很好的做法,但在我看来这没问题。