【问题标题】:How to remove multiple, repeating ranges of lines from a csv file?如何从 csv 文件中删除多个重复的行范围?
【发布时间】:2019-10-09 13:20:09
【问题描述】:

我正在处理一个 csv 文件,该文件是气相色谱数据分析仪的输出,因此我只能操作所提供的内容。我需要从 csv 文件中删除不必要的行或只保留必要的行。实际文件中有 960 行。

文件中的第 1 8 行如下所示

[Line 1]  Remove
[Line 2]  Remove
[Line 3]  Keep
[Line 4]  Remove
[Line 5]  Remove
[Line 6]  Remove
[Line 7]  Keep
[Line 8]  Keep

我要保留/删除的行范围模式持续数百行,因此这里以接下来的 8 行为例。

[Line 9]   Remove
[Line 10]  Remove
[Line 11]  Keep
[Line 12]  Remove
[Line 13]  Remove
[Line 14]  Remove
[Line 15]  Keep
[Line 16]  Keep

没有字符串模式可以仅识别这些行号本身。我想避免必须计算数百行的范围并将它们全部放入 sed 中,就像下面显示的脚本一样,它只削减前 8 行所需的行数。

    sed '1,2d; 4,6d' test.csv >> cut_test.csv

我希望得到以下结果:

[Line 3]  Keep
[Line 7]  Keep
[Line 8]  Keep
[Line 11] Keep
[Line 15] Keep
[Line 16] Keep

【问题讨论】:

    标签: python csv unix sed


    【解决方案1】:

    如果要保留的行号遵循您在说明中提供的确切模式(每 8 行重复一次),您可以使用以下 GNU sed 命令:

    $ sed '1~8d;2~8d;4~8d;5~8d;6~8d;' input.csv 
    [Line 3]  Keep
    [Line 7]  Keep
    [Line 8]  Keep
    [Line 11]  Keep
    [Line 15]  Keep
    [Line 16]  Keep
    

    并将其重定向到新文件或用户-i.back 以就地更改文件。

    说明:

    • 1~8d 将在第 1 行,第 9 行,... 执行 d 命令。
    • 2~8d 将在第 2 行、第 10 行执行d 命令,...

    input.csv:

    $ cat input.csv 
    [Line 1]  Remove
    [Line 2]  Remove
    [Line 3]  Keep
    [Line 4]  Remove
    [Line 5]  Remove
    [Line 6]  Remove
    [Line 7]  Keep
    [Line 8]  Keep
    [Line 9]   Remove
    [Line 10]  Remove
    [Line 11]  Keep
    [Line 12]  Remove
    [Line 13]  Remove
    [Line 14]  Remove
    [Line 15]  Keep
    [Line 16]  Keep
    

    您甚至可以通过以下方式(接近您的命令)重新组合所有内容来简化命令:

    $ sed '1~8,2~8d;4~8,6~8d;' input.csv 
    [Line 3]  Keep
    [Line 7]  Keep
    [Line 8]  Keep
    [Line 11]  Keep
    [Line 15]  Keep
    [Line 16]  Keep
    

    正如Thor 所述,如果您不删除要删除的行,而是打印要保留的行,则可以减少命令:

    $ sed -n '3~8p;7~8,8~8p;' input.csv
    [Line 3]  Keep
    [Line 7]  Keep
    [Line 8]  Keep
    [Line 11]  Keep
    [Line 15]  Keep
    [Line 16]  Keep
    

    【讨论】:

    • 选择更短,即使用-n 和此脚本3~8p; 7~8p; 8~8p 运行
    • @Thor:你完全正确!谢谢,我已经编辑了我的答案
    • 谢谢 Thor 和 Allen,我喜欢这个解决方案。只有两个问题:1)我需要下载 GNU sed(gsed)才能使用〜,但使用 macports 很容易,2)在实际文件中,模式是 60 行,所以我想保留第 4 行,并且第 20-60 行。鉴于此,写出来最终会有点困难,但我应该在问题中提到这一点。由于实际文件中我要删除的行数实际上更少,因此我最终得到了以下代码,它就像一个魅力:
    • gsed '1~60d;2~60d;3~60d;5~60d;6~60d;7~60d;8~60d;9~60d;10~60d;11~60d; 12~60d;13~60d;14~60d;15~60d;16~60d;17~60d;18~60d;19~60d' Test_2.csv > Cut.csv
    • @amwalker:无论哪种情况,您都可以让sed 生成脚本,例如:( echo 4; seq 20 60) | sed 's/$/~60p/' | sed -nf - infile
    【解决方案2】:

    Python 方法只是

    import sys
    for i,l in enumerate(sys.stdin):
      if i%8 in (2,6,7): print(l)  # 0-based
    

    【讨论】:

      【解决方案3】:

      sed 解决方案很优雅,但由于您还标记了 Python,这里有一个该语言的等效解决方案。如果有必要,它应该扩展到巨大的文件,因为它从不一次读取整个文件(我相信 sed 解决方案也是如此):

      import itertools
      
      with open('input.csv', 'r') as in_file:
          with open('output.csv', 'w') as out_file:
              out_file.writelines(entry for entry, keep in zip(in_file.readlines(), itertools.cycle([False, False, True, False, False, False, True, True])) if keep)
      

      【讨论】:

      • 啊抱歉,从一个不进行文件处理的不完整的 sn-p 留下。它只是 in_file.readlines()。已编辑。这也消除了对“+'\n'”的需要,再次编辑。
      • 也就是说,无论如何,这个解决方案大多不如你的解决方案。我想这些天我只是在以令人费解的方式思考。
      • readlines 是渴望的(Python 2 中的zip 也是如此),这就是我只使用文件对象的原因。但是itertools.cycle 确实可以让您执行[0]*12+[1]*34+[0] 之类的操作,就像编辑后的问题可能想要的那样。
      【解决方案4】:

      简答

      awk 中匹配的默认操作是打印以下行: awk 'NR%8~/3|7|0/' 输入.csv

      长答案,灵感来自 @kvantour 的 cmets

      awk 'NR%8~/3|7|0/' input.csv
      # or shorter (when module < 10)
      awk 'NR%8~/[037]/' input.csv
      

      当您需要模数 > 9 时,您需要将整行与 ^$ 标记匹配。使用模 25 和第 3、7、8、11、14、22 行,您可以使用

      awk 'NR%25~/^[3|7|0|11|14|22]$/' input.csv
      # or shorter
      awk 'NR%25~/^[037]|1[14]|22$/' input.csv
      

      对于更多的值,这变得更难阅读。另一种选择是

      # Original case
      awk 'BEGIN {a[3];a[7];a[0]} NR%8 in a' input.csv 
      # 3,7,8,11,14,22
      awk 'BEGIN {a[3];a[7];a[8];a[11];a[14];a[22];} NR%25 in a' input.csv 
      

      提取数字:

      # Original case
      awk 'FNR==NR {a[$0];next} FNR%8 in a' <(printf "%s\n" 3 7 0) input.csv 
      # 3,7,8,11,14,22
      awk 'FNR==NR {a[$0];next} FNR%25 in a' <(printf "%s\n" 3 7 8 11 14 22) input.csv 
      

      【讨论】:

      • 类似但可能稍快:awk '"037"~(NR%8)' 请注意,此技巧仅在模数始终低于 10 时才有效
      • 一个更短的版本是:awk 'NR%8~/[037]/' 如果模数是例如 19,你将不得不做类似awk 'NR%19~/^([037]|10|16)$/'
      【解决方案5】:

      这可能对你有用(GNU sed):

      sed -n 'n;n;p;n;n;n;n;p;n;p' file
      

      按照罐头上说的做。

      更好(Thor 已经提到):

      sed -n '3~8p;7~8,+1p' file
      

      【讨论】:

        猜你喜欢
        • 2018-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-01-16
        • 2015-11-13
        • 2021-10-05
        • 1970-01-01
        相关资源
        最近更新 更多