将CSV转换为TSV


27

我有许多大型CSV文件,希望它们采用TSV(制表符分隔格式)。复杂之处在于CSV文件的字段中有逗号,例如:

 A,,C,"D,E,F","G",I,"K,L,M",Z

预期产量:

 A      C   D,E,F   G   I   K,L,M   Z

(中间的空格是“硬”标签)

我在此服务器上安装了Perl,Python和coreutils。


我可以使用node.js或perl做到这一点。
彼得说恢复莫妮卡的

1
用制表符替换未引用的逗号...
cricket_007'4

是的,如果我有超过5分钟的时间来回答这个问题。但是,我将以我的选票高兴地支持答复者。我想说的是,常见的sed / awk东西可能不符合此条件(至少在它们的常用用法中)。
彼得说恢复莫妮卡的时间

6
我不确定您的示例是否代表实际数据,但是如果这些将成为实际文本字符串,请不要忘记您可能需要处理该字符串包含制表符的情况...
AC

3
另一个棘手的部分是CSV是一种非常松散定义的格式,没有真正的标准(有RFC,但实际上是在几年之后写的)。我编写的代码使用了语言提供的CSV解析器,然后不得不使用自定义解析器重写它,因为我发现输入数据的格式是csv格式的损坏形式。
plugwash

Answers:


37

蟒蛇

添加到名为的文件中csv2tab.sh,并使其可执行

#!/usr/bin/env python
import csv, sys
csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))

试运行

$ echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' | ./csv2tab.sh                         
A       C   D,E,F   G   I   K,L,M   Z

$ ./csv2tab.sh < data.csv > data.tsv && head data.tsv                                                   
1A      C   D,E,F   G   I   K,L,M   Z
2A      C   D,E,F   G   I   K,L,M   Z
3A      C   D,E,F   G   I   K,L,M   Z

5
可能的错误:此答案无法逃脱内部标签。
Morgen

4
@Morgen csv.writer(sys.stdout, dialect='excel-tab').writerows(csv.reader(sys.stdin))吗?也消除了循环。
muru

1
@chx试试python -c 'import csv,sys; csv.writer(sys.stdout, dialect="excel-tab").writerows(csv.reader(sys.stdin))'。我怀疑那样-m工作。
muru

18

为了好玩,sed

sed -E 's/("([^"]*)")?,/\2\t/g' file

如果您sed不支持-E,请尝试-r。如果您sed不支持\t文字标签,请尝试将文字标签(在许多shell中ctrl- v tab)或在Bash中使用$'...'C样式的字符串(在这种情况下,反斜杠\2需要加倍)。如果要保留引号,请使用\1代替\2(在这种情况下,内括号对无用,可以删除)。

这不会尝试处理双引号内的转义双引号。一些CSV方言通过将双引号(sic)加倍来支持此功能。


1
我认为我尝试了大约100种不同的sed脚本来实现这一目标,但我的所有尝试均以失败告终。这太棒了。
乔治·瓦西里乌


13

一个选项可能是perl的Text :: CSV模块,例如

perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
' somefile

展示

echo 'A,,C,"D,E,F","G",I,"K,L,M",Z' |
  perl -MText::CSV -lne 'BEGIN { $csv = Text::CSV->new() }
  print join "\t", $csv->fields() if $csv->parse($_)
'
A       C   D,E,F   G   I   K,L,M   Z

1
如果字段包含选项卡,那将是不正确的
Neil McGuigan

6

佩尔

perl -lne '
   my $re = qr/,(?=(?:[^"]*"[^"]*")*(?![^"]*"))/;
   print join "\t", map { s/(?<!\\)"//gr =~ s/\\"/"/gr } split $re;
'

Awk

awk -v Q=\" -v FPAT="([^,]*)|(\"[^\"]+\")" -v OFS="\t" '{
   for (i=1; i<=NF; ++i)
      if ( substr($i, 1, 1) == Q )
         $i = substr($i, 2, length($i) - 2)
   print $1, $2, $3, $4, $5, $6, $7, $8
}'

结果:

A               C       D,E,F   G       I       K,L,M   Z

+1 Perl版本就像一个魅力
ATorras

4

热核苍蝇拍解决方案必须使用libreoffice。而https://ask.libreoffice.org/en/question/19042/is-is-possible-to-convert-comma-separated-value-csv-to-tab-separated-value-tsv-via-headless-mode /表示这不可能,但这是错误的(或刚刚过时?),以下命令在我的5.3上有效:

loffice "-env:UserInstallation=file:///tmp/LibO_Conversion" --convert-to csv:"Text - txt - csv (StarCalc)":9,34,UTF8 --headless --outdir some/path --infilter='csv:44,34,UTF8' *.csv

env可以跳过该参数,但是这样文档就不会出现在您最近的文档中。


2
我认为真正的热核flyswatter应该编写一个Java实用程序,以通过LibreOffice的UNO API来实现它:)。

3

如果您具有或可以安装该csvtool实用程序:

csvtool -t COMMA -u TAB cat in.csv > out.ctv

请注意,由于某种原因csvtool没有手册页,但是csvtool --help会打印几百行文档。


3

使用mlr几乎是简洁的,但是禁用标题需要很长的选择:

mlr --c2t --implicit-csv-header --headerless-csv-output cat file.csv 

输出:

A       C   D,E,F   G   I   K,L,M   Z

3

我编写了一个开源的CSV到TSV转换器,可以处理上述转换。它的速度非常快,如果持续需要转换大型CSV文件,则可能值得一看。该工具是eBay的TSV实用程序工具包此处为 csv2tsv文档)的一部分。默认选项足以说明所描述的输入:

$ csv2tsv file.csv > file.tsv

2

Vim

只是为了好玩,可以在Vim中执行正则表达式替换。这是一个潜在的四行解决方案,改编自:https : //stackoverflow.com/questions/33332871/remove-all-commas-between-quotes-with-a-vim-regex

  1. 引号之间的逗号首先更改为下划线(或其他缺少的字符),
  2. 所有其他逗号都被制表符替换,
  3. 引号内的下划线恢复为逗号,
  4. 引号被删除。

    :%s/".\{-}"/\=substitute(submatch(0), ',', '_' , 'g')/g
    :%s/,/\t/g
    :%s/_/,/g
    :%s/"//g
    

要用某种方式编写解决方案的脚本,可以将上面的四行(无冒号)保存到文件中,例如to_tsv.vim。打开每个CSV用于与编辑Vim的source所述to_tsv.vim的上脚本Vim的命令行(改编自/programming/3374179/run-vim-script-from-vim-commandline/8806874#8806874):

    :source /path/to/vim/filename/to_tsv.vim

1

这是使用jq实用程序将CSV转换为TSV的示例:

$ jq -rn '@tsv "\(["A","","C","D,E,F","G","I","K,L,M","Z"])"'
A       C   D,E,F   G   I   K,L,M   Z

要么:

$ echo '["A","","C","D,E,F","G","I","K,L,M","Z"]' | jq -r @tsv
A       C   D,E,F   G   I   K,L,M   Z

但是,CSV格式必须格式正确,因此每个字符串都需要加引号。

来源:简单的TSV输出格式



0

以下仅是对@tripleee答案的更正,以 使它像最后所有字段中的所有引号一样删除最终字段中的所有引号。

为了显示正在纠正的内容,下面是一个三元组的答案,并对OP的示例数据进行了少许修改,并在最后的“ Z ”字段周围添加了引号。

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g'
A       C   D,E,F   G   I   K,L,M   "Z"

您会看到' Z '周围带有引号。这与内部字段的处理方式不同。例如,“ G ”上没有引号。

以下命令使用第二个替换来清除最后一列:

echo 'A,,C,"D,E,F","G",I,"K,L,M","Z"' |  sed -r -e 's/("([^"]*)")?,/\2\t/g' \
                                                -e 's/\t"([^"]*)"$/\t\1/'
A       C   D,E,F   G   I   K,L,M   Z

1
当输入数据'A,,C,"D,E,F","G",I,"K,L,M","Z,A"'输入到此答案时,会"Z,A"被错误地替换为Z A,而不是正确的Z,A
agc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.