在 Bash 中复制 CSV 列
Posted
技术标签:
【中文标题】在 Bash 中复制 CSV 列【英文标题】:Duplicate a CSV column in Bash 【发布时间】:2017-10-25 21:49:08 【问题描述】:我有一个问题,客户需要复制 CSV 文件中的列。这些值总是相同的,不幸的是我们的 API 不允许在 JSON 中指定重复的列。
例如,我有以下列结构和值:
Name, Surname, City, Age, Job
John, Doe, Johannesburg, 28, Technical Support
现在我需要复制 City 所以输出应该是:
Name, Surname, City, City Again, Age, Job
John, Doe, Johannesburg, Johannesburg, 28, Technical Support
该列需要放在将要复制的列之后。该值也取决于第一列。
【问题讨论】:
这里案例中的值是约翰内斯堡。第一个例子它出现在 City 下一次。它在 City Again 下的第二个示例中重复 因此,在第一条记录中,包含您的列名,$3 != $4。你想如何生成你的标题? (我在您的问题中没有看到您用来尝试执行此操作的代码,请添加它。) 【参考方案1】:下面是两个输入的小脚本
-
文件名
列名
它会重复提及列
下面是脚本
echo "Enter CSV File name "
read fileName
echo "Enter Column name to be duplicated "
read columnName
columnNumber=`head -1 $fileName | awk -v RS="," "/$columnName/print NR;"` #Identify Column number using column name
totalNumberOfColumn=`head -1 $fileName | awk -F',' 'print NF'` #identify total number of column
str="" #Create empty variab str to print column number in awk
for ((i=1;i<=$totalNumberOfColumn;i++));
do
str="$str \$$i\",\""
if [ $i == $columnNumber ]
then
str="$str \$$i\",\""
fi
done
awk -F',' "print $str" $fileName | sed 's/,$//g' #Print all column inculding duplicate column
以下是文件内容,文件名为data.csv
:cat data.csv
Name, Surname, City, Age, Job
John, Doe, Johannesburg, 28, Technical Support
复制列 City 的输出 1
:bash script.sh
Enter CSV File name
data.csv
Enter Column name to be duplicated
City
Name, Surname, City, City, Age, Job
John, Doe, Johannesburg, Johannesburg, 28, Technical Support
输出 2:复制列 Name
:bash script.sh
Enter CSV File name
data.csv
Enter Column name to be duplicated
Name
Name,Name, Surname, City, Age, Job
John,John, Doe, Johannesburg, 28, Technical Support
【讨论】:
【参考方案2】:awk
可以轻松处理:
awk 'BEGINFS=OFS=", " $3 = $3 OFS $3 1' file.csv
Name, Surname, City, City, Age, Job
John, Doe, Johannesburg, Johannesburg, 28, Technical Support
请注意,这在单个简短的命令中完成这项工作,该命令易于阅读,并且比涉及调用 cut
两次然后是“粘贴”的管道命令效率更高。
正如@codeforester 在下面正确评论的那样,cut
不允许在输出中重复列;它用于剥离一个值。
【讨论】:
是的,如果这是一个 awk 问题而不是 bash 问题,那将是这样做的方法。 :-) OP 可能不知道awk
的实力 :)
非常优雅和高效。 @anubhava - cut
不允许在其输出中重复列的点可以在您的答案中注明。来自man cut
:如果一个字段或列被多次指定,它只会在输出中出现一次。
感谢@codeforester 的客气话。我也在我的答案中添加了这个。
我创建了一个 25000000
行的 csv 文件。 awk
命令需要 58 秒才能完成。并且cut+paste
仍在运行最后 25 分钟。【参考方案3】:
不久前,我写了一个 bash 函数来插入数组元素:
function array_insert
# options: arrayname index [value]
if ! declare -p "$1" 2>/dev/null | grep -q '^declare -a'; then
printf '%s: not an array: %s\n' "$0" "$1" >&2
return 1
fi
local -n source="$1"
local -a indices=( "$!source[@]" )
for ((i=$#indices[@]-1; i>=$2; i--)) ; do
source[$((i+1))]="$source[$i]"
done
if [ -n "$3" ]; then
source[$2]="$3"
fi
加载此函数后,您可以执行以下操作:
while read line; do
IFS=, declare a=( $line ) # assign the line's fields to an array,
array_insert a 3 "$a[2]" # insert the column in this line,
o=$(printf '%s,' "$a[@]") # assemble your output,
printf '%s\n' "$o%," # remove the trailing comma.
done < input.txt
对我来说,这会使用您的输入提供以下输出:
Name, Surname, City, City, Age, Job
John, Doe, Johannesburg, Johannesburg, 28, Technical Support
请注意,在 bash 中,数组从 0
开始索引,因此 $a[2]
是第三列。
【讨论】:
【参考方案4】:假设City
将始终位于第3 列,则可以使用cut 和paste 命令。例如:
csv=path/to/somefile.csv
echo "$(paste -d',' <(cut -d',' -f1-3 $csv) <(cut -d',' -f3- $csv))" > "$csv"
注意事项:
-
分配给
csv
变量的path/to/quux.csv
部分应替换为.csv
文件的实际路径。
包含重复的City
列的新内容被写回到同一个源文件中。
<(...)
部分称为进程替换,通常在管道数据不可用时使用。可以在here找到一个很好的解释。
【讨论】:
这对于大文件来说是非常低效的,因为整个文件必须被读取 3 次或更多!awk
解决方案要好得多,因为它只读取文件一次。
@codeforester - 感谢您的评论。也许您可以澄清“对于大文件效率非常低” 的意思。我获得的关于完成时间的指标显示,在包含 100 万行和 5 列的 .csv
上运行我提供的解决方案,平均为 12.2 秒。而 awk 解决方案使用相同的数据集平均为 13.7 秒。两种解决方案的所有时间都包括将结果写回磁盘所花费的时间。您的评论基于哪些指标? awk 解决方案是不是特别节省内存?
除了awk
方法的完成时间比我提供的解决方案略长,平均而言awk
解决方案也消耗了更多的实际内存。 awk
解决方案消耗了 283Mb 的内存,而这个解决方案消耗了 190Mb。这些指标是在运行之前评论中提到的相同测试条件时获得的。我只能从这些指标中得出结论,关于我的解决方案 “对于大文件效率非常低” 的 cmets 有点误导和不准确。
如果你像@codeforester 所说的那样使用大文件输入,那么行为上的差异就会很明显。我创建了一个包含 2500 万行的 csv 文件并运行了这个命令。它甚至在一个小时后都没有完成,没有在输出文件中写入任何行,我不得不终止 shell,而 awk
命令需要 58 秒才能完成。以上是关于在 Bash 中复制 CSV 列的主要内容,如果未能解决你的问题,请参考以下文章
Python 修改 csv 文件,复制和格式化列,并保存输出
我有一个大型 CSV 文件,其中包含单个列中的信息。如何使用 python 在 excel 中复制“文本到列”任务? [复制]