使用Sed脚本或Python编辑CSV文件
在我们的项目中,我们需要把csv文件导入到Postgres数据库里。这个文件有很多种类型,意味着文件的长度会有所不同,有些文件的列比较少,而有些则包含所有的列。
我们需要一个快速的方法把这个文件导入到Postgres。我想使用Postgres的COPY FROM命令,因为处理速度的要求非常高(几乎每分钟要处理150个文件,每个文件大小为2万行)。
由于文件的列数并不固定,我需要在把文件传给Postgres之前先进行预处理。预处理的工作就是在csv文件中为缺少的列添加额外的逗号。
我有两个选择来预处理这个文件——使用Python或者使用Sed。
我的第一个问题是,预处理文件的最快方法是什么?
第二个问题是,如果我使用Sed,怎么在第4个和第5个逗号后面插入一个逗号呢?
例如,如果文件的内容是:
1,23,56,we,89,2009-12-06
我需要把文件编辑成这样的最终输出:
1,23,56,we,,89,,2009-12-06
6 个回答
关于你第一个问题,sed
的开销会比较小,但使用起来可能会让人觉得麻烦。awk
会好一些(因为它功能更强大)。而 Perl 或 Python 的开销会更大,但用起来会更简单(说到 Perl,这可能有点主观 ;)。就我个人而言,我会选择 Perl。
至于第二个问题,我觉得情况可能会复杂一些。比如,你是不是需要检查字符串,看看哪些字段实际上是缺失的?还是说可以保证缺失的字段总是第4和第5个?如果是前一种情况,用 Python 或 Perl 来处理会简单得多,而不是用 sed
。否则:
echo "1,23,56,we,89,2009-12-06" | sed -e 's/\([^,]\+\),\([^,]\+\),\([^,]\+\),\([^,]\+\),\([^,]\+\),/\1,\2,\3,\4,,\5,,/'
或者(看起来更舒服一点):
echo "1,23,56,we,89,2009-12-06" | sed -e 's/\(\([^,]\+,\)\{3\}\)\([^,]\+\),\([^,]\+\),/\1,\3,,\4,,/'
这段代码会在第5和第4列后面添加一个逗号,前提是文本中没有其他的逗号。
或者你可以用两个 sed
来做一些稍微好看一点的事情(不过也只是稍微好看一点):
echo "1,23,56,we,89,2009-12-06" | sed -e 's/\(\([^,]*,\)\{4\}\)/\1,/' | sed -e 's/\(\([^,]*,\)\{6\}\)/\1,/'
@OP,你正在处理一个csv文件,这种文件有不同的字段和分隔符。建议你使用一些可以根据分隔符来拆分内容的工具,这样你就能更轻松地处理这些字段。虽然sed也可以做到这一点,但它并不是最好的选择,因为当情况变复杂时,sed的正则表达式会变得难以理解。你可以使用像awk、Python或Perl这样的工具,它们在处理字段和分隔符时更简单。而且,还有一些专门为处理csv文件设计的模块可供使用。对于你的例子,下面是一个简单的Python方法(这里不使用csv模块,但你最好还是尝试使用它)
for line in open("file"):
line=line.rstrip() #strip new lines
sline=line.split(",")
if len(sline) < 8: # you want exact 8 fields
sline.insert(4,"")
sline.insert(6,"")
line=','.join(sline)
print line
输出
$ more file
1,23,56,we,89,2009-12-06
$ ./python.py
1,23,56,we,,89,,2009-12-06
你知道吗,COPY FROM
这个命令可以让你指定要导入哪些列,以及这些列的顺序吗?
COPY tablename ( column1, column2, ... ) FROM ...
直接在Postgres中指定要导入的列和顺序,通常是最快、最有效的导入方法。
不过,还有一种更简单(而且更通用)的方法,就是使用 sed
,比其他帖子里介绍的方法要简单得多。比如说,你可以用它来 替换第 n 次出现的内容,例如把第4和第5次出现的逗号替换成双逗号:
echo '1,23,56,we,89,2009-12-06' | sed -e 's/,/,,/5;s/,/,,/4'
这样就会得到:
1,23,56,we,,89,,2009-12-06
注意,我是先替换最右边的字段(#5)。
我看到你在问题中也提到了 perl
,虽然你没有明确提到它;这里有一个可能的实现,可以让你灵活地重新排序或处理字段:
echo '1,23,56,we,89,2009-12-06' |
perl -F/,/ -nae 'print "$F[0],$F[1],$F[2],$F[3],,$F[4],,$F[5]"'
同样会得到:
1,23,56,we,,89,,2009-12-06
用 awk
也很类似,记录一下:
echo '1,23,56,we,89,2009-12-06' |
awk -F, '{print $1","$2","$3","$4",,"$5",,"$6}'
Python的部分就留给别人吧。 :)
关于Perl的例子小补充:我使用了 -a
和 -F
选项来自动拆分,这样命令就短一些;不过,这样会把换行符留在最后一个字段($F[5]
)里,只要这个字段不需要在别的地方重新排序,这样是没问题的。如果需要重新排序,就得多打一些代码来去掉换行符,接着手动拆分,最后打印自己的换行符 \n
(上面的 awk
例子就没有这个问题):
perl -ne 'chomp;@F=split/,/;print "$F[0],$F[1],$F[2],$F[3],,$F[4],,$F[5]\n"'
编辑(灵感来自Vivin):
COMMAS_TO_DOUBLE="1 4 5"
echo '1,23,56,we,89,2009-12-06' |
sed -e `for f in $COMMAS_TO_DOUBLE ; do echo "s/,/,,/$f" ; done |
sort -t/ -k4,4nr | paste -s -d ';'`
1,,23,56,we,,89,,2009-12-06
抱歉,忍不住了。 :)