如何在BASH中将制表符分隔值(TSV)文件转换为逗号分隔值(CSV)文件?

13

我有一些TSV文件需要转换成CSV文件。有没有在BASH中的解决方案,例如使用awk来进行转换?我可以使用sed,像这样,但担心会出现一些错误:

sed 's/\t/,/g' file.tsv > file.csv
  • 不需要添加引号。

如何将TSV文件转换为CSV文件?


2
如果要忽略引号字符串内的制表符/逗号,则会更加复杂。 - Jonathon Reinhart
原始文件没有使用引号字符串,输出文件也不需要添加。 - Village
@Village,如所述,“tr”应该是适合此工作的正确工具,但您担心 sed 可能会犯什么样的错误?您可以更新您的问题以展示一些输入示例,您认为 sed 可能无法正确处理的内容吗? - Ed Morton
5个回答

33
更新:下面的解决方案通常不够健壮,尽管它们在OP的特定用例中确实有效;请参见底部部分以获取一个健壮的、基于awk的解决方案
总结一下选项(有趣的是,它们都表现得差不多): trdevnull的解决方案(在问题的评论中提供)是最简单的:
tr '\t' ',' < file.tsv > file.csv

sed:

如果输入不包含带有可能嵌入 \t 字符的引号字符串,那么 OP 自己的 sed 解决方案完全可以胜任。

sed 's/\t/,/g' file.tsv > file.csv

唯一的注意事项是,在某些平台上(例如 macOS),转义序列 \t 不受支持,因此必须使用 ANSI 引用($'\t')将文字制表符插入命令字符串中:

sed 's/'$'\t''/,/g' file.tsv > file.csv

awk:

awk 的注意点是必须明确地将输入字段分隔符 FS 设置为 \t,否则默认行为会剥离前导和尾随制表符,并且将多个制表符的内部跨度替换为单个逗号 ,

awk 'BEGIN { FS="\t"; OFS="," } {$1=$1; print}' file.tsv > file.csv

请注意,简单地将$1分配给它本身会导致awk使用OFS重新构建输入行 - 输出字段分隔符;这实际上将所有\t字符替换为,字符。然后print只需打印重建的行。
健壮的awk解决方案: 正如A. Rabus指出的那样,上面的解决方案不能正确处理未引用的输入字段,这些字段本身包含,字符 - 你最终会得到额外的CSV字段。
以下awk解决方案通过根据需要在"..."中封装这些字段来解决此问题(有关方法的部分解释,请参见上面的非健壮的awk解决方案)。
如果这样的字段还包含嵌入的"字符,则根据RFC 4180对其进行转义为""感谢Wyatt Israel
awk 'BEGIN { FS="\t"; OFS="," } {
  rebuilt=0
  for(i=1; i<=NF; ++i) {
    if ($i ~ /,/ && $i !~ /^".*"$/) { 
      gsub("\"", "\"\"", $i)
      $i = "\"" $i "\""
      rebuilt=1 
    }
  }
  if (!rebuilt) { $1=$1 }
  print
}' file.tsv > file.csv
  • $i ~ /[,"]/ && $i !~ /^".*"$/ 可以检测到任何包含,和/或"但尚未被双引号括起来的字段

  • gsub("\"", "\"\"", $i) 通过加倍嵌入的"字符来转义

  • $i = "\"" $i "\"" 更新结果,将其用双引号括起来

  • 如前所述,更新任何字段都会导致awk从带有OFS的字段中重新构建该行,即在这种情况下为,,这相当于有效的TSV -> CSV转换;标志rebuilt用于确保每个输入记录至少重建一次。


关于awk的警告是,必须显式地将FS - 输入字段分隔符设置为\t - 这对awk来说不再是一个警告,就像对于“tr”或“sed”一样。在这3个工具中,您需要指定输入字段分隔符以及要将其转换为的内容,如果您不这样做,那么显然您将无法获得所需的行为。 - Ed Morton
1
@EdMorton:由于\tawk默认情况下处理输入字段分隔符之一,因此人们可能会认为在此处设置输入字段分隔符是不必要的-这是一个谬论,原因如我所指出的那样;因此需要注意。至于trsed:输入字段分隔符的概念不适用。 - mklement0
当我说“输入字段分隔符”时,我只是指“将输入分隔成字段(值)的字符”,这适用于所述的输入格式(制表符分隔值),而不是任何特定的工具。我只是认为,与为其他工具指定它以告诉awk如何按预期行事相比,不必指定它更多的警告 - 在所有3个工具中,这正是相同的概念,如果您不这样做,那么您将无法获得所需的行为。无论如何,tr是所述工作的正确工具,因此这可能是一个无意义的观点。 - Ed Morton

2

这也可以通过Perl实现:

要将结果导入新的输出文件,您可以使用以下命令:
perl -wnlp -e 's/\t/,/g;' input_file.tsv > output_file.csv

如果您想直接编辑文件,可以使用-i选项:
perl -wnlpi -e 's/\t/,/g;' input_file.txt

如果你发现你处理的实际上不是制表符(tab),而是多个空格,则可以使用以下命令用逗号替换每个出现的两个或多个空格:
perl -wnlpi -e 's/\s+/,/g;' input_file

请注意,\s 表示任何空白字符,包括空格、制表符或换行符,并且不能在替换字符串中使用。


你也可以使用vim。只需在命令模式下使用以下搜索和替换::%s/\t/,/g这样可以让你立即查看结果,并在需要时通过单个按钮按下(u)来撤消它们。 - Toby
1
如果你已经在使用 Perl,那么你也可以使用 https://metacpan.org/pod/Text::CSV。 - Robert

1
使用awk对我很有用

将tsv转换为csv

awk 'BEGIN { FS="\t"; OFS="," } {$1=$1; print}' file.tsv > file.csv

将CSV转换为TSV。
awk 'BEGIN { FS=","; OFS="\t" } {$1=$1; print}' file.csv > file.tsv

0

您可以在shell中简单地使用sed的强大功能:

sed -r 's/\t/","/g' file.tsv|sed -r 's/(^|$)/"/g' > file.csv

通常情况下,上述命令将您的 tsv 文件转换为 csv。然而,tsv 文件可能包含数字字段。在这种情况下,它们不应该像 "123456" 那样被双引号所包围。因此我们需要另一个阶段,以便删除此类双引号。最终解决方案:
sed -r 's/\t/","/g' file.tsv|sed -r 's/(^|$)/"/g'|sed -r 's/"([0-9]+)"/\1/g' > file.csv

0

tr 命令:

tr '\t' ',' < file.tsv > file.csv

这个程序非常简单,对我来说即使在一个非常大的文件(约10 GB)上也能够给出绝对正确和非常快速的结果。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接