【问题标题】:Sed script to edit csv file Or Python用于编辑 csv 文件或 Python 的 Sed 脚本
【发布时间】:2011-01-22 23:10:32
【问题描述】:

在我们的项目中,我们需要将 csv 文件导入 postgres。 有多种类型的文件意味着文件的长度会发生变化,因为有些文件的列较少,有些文件的列全部。

我们需要一种快速将此文件导入 postgres 的方法。我想使用 postgres 的 COPY FROM,因为处理的速度要求非常高(每分钟几乎 150 个文件,每个文件大小为 20K)。

由于文件列编号不固定,我需要在将文件传递给 postgres 过程之前对其进行预处理。预处理只是在 csv 中为列添加额外的逗号,这些列在文件中不存在。

我有两个选项来预处理文件 - 使用 python 或使用 Sed。

我的第一个问题是,预处理文件的最快方法是什么?

第二个问题是,如果我使用 sed,我将如何在第 4、5 个逗号字段之后插入逗号?
例如如果文件有类似的条目 1,23,56,我们,89,2009-12-06 我需要使用最终输出编辑文件,例如: 1,23,56,我们,,89,,2009-12-06

【问题讨论】:

    标签: python sed awk text-processing


    【解决方案1】:

    要回答您的第一个问题,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 列之后添加一个逗号。

    或者你可以使用两个seds 来做一些不那么难看的东西(虽然只是稍微):

    echo "1,23,56,we,89,2009-12-06" | sed -e 's/\(\([^,]*,\)\{4\}\)/\1,/' | sed -e 's/\(\([^,]*,\)\{6\}\)/\1,/'
    

    【讨论】:

    • 过于复杂的sed 语法和不可移植(不适用于 POSIX sed,即没有扩展正则表达式支持)
    • 这就是为什么我说sed 可能是一个糟糕的解决方案。 Perl 或 Python 会更好、更容易,而且正如您提到的那样,是可移植的。
    • errr 不,请参阅下文了解使用 sed 的简单且可移植的方式。此外,perl 和 python 引入了每个文件的开销(设置/加载时间),但从长远来看,它们的正则表达式引擎可能比 sed 的效率更高
    • 我说的是你不能确定位置在第 4 和第 5 的情况。如果它们是任意的,sed 将不是正确的工具。
    【解决方案2】:
    sed 's/^([^,]*,){4}/&,/' <original.csv >output.csv
    

    将在第 4 个逗号分隔字段之后添加一个逗号(通过匹配 4 个重复的 &lt;anything&gt;,,然后在其后添加一个逗号)。请注意,有一个问题;确保这些值都不是带逗号的引号字符串。

    如有必要,您可以通过管道链接多个替换,或者修改正则表达式以同时添加任何需要的逗号(尽管这会变得更加复杂;您需要在替换文本中使用子组捕获)。

    【讨论】:

      【解决方案3】:

      不知道速度,但这里 sed expr 应该可以完成这项工作:

      sed -i 's/\(\([^,]*,\)\{4\}\)/\1,/' file_name
      

      只需将 4 替换为确定的列数

      【讨论】:

        【解决方案4】:

        根据您的要求,考虑将ETL 软件用于此和未来的任务。 PentahoTalend 等工具为您提供了极大的灵活性,您无需编写任何代码。

        【讨论】:

          【解决方案5】:

          您是否知道COPY FROM 允许您指定要导入哪些列(以及它们的顺序)?

          COPY tablename ( column1, column2, ... ) FROM ...
          

          在 Postgres 级别直接指定要导入的列和顺序,通常是最快和最有效的导入方法。

          话虽如此,使用sed(比其他帖子中介绍的方法)到replace an n th occurrence 有一种更简单(且可移植)的方式,例如用双逗号替换第 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-related,尽管您在问题正文中没有明确提及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]) 中,只要该字段不必在其他地方重新排序就可以了。如果出现这种情况,需要稍微多输入一点才能通过chomp 删除换行符,然后手动输入split,最后打印我们自己的换行符\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
          

          抱歉,忍不住了。 :)

          【讨论】:

          • 我喜欢这个!我不知道你可以这样传递一个数字!我今天学到了一些新东西:)
          • 有没有办法从你的答案中替换 sed -e 's/,/,,/5;s/,/,,/4' 这样我就不必重复' s/,/,,/' 并在提及一次后给出像 {5,4} 这样的数字列表。
          • @SystemMatrix,不是我所知道的——而且可能不是以可移植的方式(不是你可能会关心 :) 在你的特定情况下的可移植性。)@vivin,知道为什么不是 :) sed -e `for f in 4 5 ; do echo "s/,/,,/$f" ; done | sort -t/ -k4,4nr | paste -s -d ';'`
          • @Vlad hehe 我知道会有for..in 参与其中。自从我编写 shell 脚本以来已经有一段时间了。这些天我通常使用 Perl。此外,我从未尝试将for 语句放在sed 中。我maned 它看到-e 标志用于将其评估为脚本。整洁的! :)
          • @vivin,实际上 shell 已经执行了 for 循环以及反引号之间的所有其他内容,然后将所有输出提供给 sed(作为正则表达式字符串) 通过argv[2].
          【解决方案6】:

          @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
          

          【讨论】:

          • 我同意,这是一种更清洁的方式来完成任务。但由于速度要求,我必须使用 Sed 方式。
          猜你喜欢
          • 1970-01-01
          • 2019-06-24
          • 2017-07-27
          • 2022-12-15
          • 2010-12-05
          • 1970-01-01
          • 2021-09-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多