在 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 列的新内容被写回到同一个源文件中。 &lt;(...) 部分称为进程替换,通常在管道数据不可用时使用。可以在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 中复制“文本到列”任务? [复制]

如何将 CSV 复制到缺少列的表中?

PostgreSQL,试图将日期列从 csv 文件复制到表的列

如何根据列名将数据从 CSV 复制到目标表?

python csv复制列