【问题标题】:What is the best, python or bash for selectively concatenating lots of files?有选择地连接大量文件的最佳 Python 或 bash 是什么?
【发布时间】:2011-01-26 22:35:22
【问题描述】:

我有大约 20000 个文件来自某个程序的输出,它们的名称遵循以下格式:

data1.txt
data2.txt
...
data99.txt
data100.txt
...
data999.txt
data1000.txt
...
data20000.txt

我想编写一个脚本,将数字 N 作为输入参数。然后它会生成 N 个连接文件的块,因此如果 N=5,它将生成以下新文件:

data_new_1.txt: it would contain (concatenated) data1.txt to data5.txt (like cat data1.txt data2.txt ...> data_new_1.txt )

data_new_2.txt: it would contain (concatenated) data6.txt to data10.txt
.....

我想知道你认为最好的方法是什么,无论是 bash、python 还是 awk、perl 等其他方法。

就最简单的代码而言,我的意思是最好的方法。

谢谢

【问题讨论】:

    标签: python bash


    【解决方案1】:

    因为这可以在任何 shell 中轻松完成,所以我会简单地使用它。

    应该这样做:

    #!/bin/sh
    FILES=$1
    FILENO=1
    
    for i in data[0-9]*.txt; do
        FILES=`expr $FILES - 1`
        if [ $FILES -eq 0 ]; then
            FILENO=`expr $FILENO + 1`
            FILES=$1
        fi
    
        cat $i >> "data_new_${FILENO}.txt"
    done
    

    Python 版本:

    #!/usr/bin/env python
    
    import os
    import sys
    
    if __name__ == '__main__':
        files_per_file = int(sys.argv[1])
    
        i = 0
        while True:
            i += 1
            source_file = 'data%d.txt' % i
            if os.path.isfile(source_file):
                dest_file = 'data_new_%d.txt' % ((i / files_per_file) + 1)
                file(dest_file, 'wa').write(file(source_file).read())
            else:
                break
    

    【讨论】:

    • 我相信这两种语言都会受到 IO 速度的限制。
    • 与在 1 个进程中工作的 Python 版本相比,bash 版本中的 Spawning 2000 cat 进程肯定无法加速;-)。
    • 这个程序有一个bug:data[0-9]*.txt会在data2.txt之前匹配data19.txt
    • @Jason:它的排序方式取决于你的 shell,也许它确实应该首先排序 :) @Alex:非常真实......我没想到头顶上的猫......
    • 是的,这两个版本都存在错误,因为它们都使用通配符。在我的 Python 版本和 @sorpigal 的 bash 版本中,整数计数器用于代替通配符,以绕过问题。跨度>
    【解决方案2】:

    在什么意义上最好? bash 可以很好地做到这一点,但是如果您更熟悉另一种脚本语言,则可能更难编写一个好的 bash 脚本。您想针对特定内容进行优化吗?

    也就是说,这是一个 bash 实现:

     declare blocksize=5
     declare i=1
     declare blockstart=1
     declare blockend=$blocksize
     declare -a fileset 
     while [ -f data${i}.txt ] ; do
             fileset=("${fileset[@]}" $data${i}.txt)
             i=$(($i + 1))
             if [ $i -gt $blockend ] ; then
                      cat "${fileset[@]}" > data_new_${blockstart}.txt
                      fileset=() # clear
                      blockstart=$(($blockstart + $blocksize))
                      blockend=$(($blockend+ $blocksize))
             fi
     done
    

    编辑:我看到你现在说“最好”==“最简单的代码”,但什么是简单的取决于你。对我来说,Perl 比 Python 更简单,因为有些 Awk 比 bash 更简单。这取决于你最了解什么。

    再次编辑:受 dtmilano 的启发,我已将我的更改为每个块大小使用一次 cat,因此现在 cat 将被称为“仅”4000 次。

    【讨论】:

    • 是的,完全正确。但在这种情况下,您的代码相当简单,谢谢
    【解决方案3】:

    这是一个 Python (2.6) 版本(如果您有 Python 2.5,请添加第一行说明

    from __future__ import with_statement
    

    脚本也可以工作)...:

    import sys
    
    def main(N):
       rN = range(N)
       for iout, iin in enumerate(xrange(1, 99999, N)):
           with open('data_new_%s.txt' % (iout+1), 'w') as out:
               for di in rN:
                   try: fin = open('data%s.txt' % (iin + di), 'r')
                   except IOError: return
                   out.write(fin.read())
                   fin.close()
    
    if __name__ == '__main__':
        if len(sys.argv) > 1:
            N = int(sys.argv[1])
        else:
            N = 5
        main(N)
    

    正如您从其他答案和 cmets 中看到的那样,对性能的看法不同——有些人认为 Python 启动(和模块的导入)会使这比 bash 慢(但导入部分至少是假的:sys,唯一需要的模块是内置模块,不需要“加载”,因此导入它的开销基本上可以忽略不计);我怀疑避免cat 的重复分叉/执行可能会减慢 bash 的速度;其他人认为 I/O 无论如何都会占主导地位,从而使这两种解决方案等效。您必须在自己的系统上使用自己的文件进行基准测试,才能解决这个性能问题。

    【讨论】:

    • 您的fin.read() 不会碰巧将所有内容加载到内存中,是吗? :)
    • 当然不是“一切”——只是当前数据文件的内容。如果您正在处理大于 1 GB 的单个文件,或者您可以轻松加载到内存中的任何文件(在配备良好 RAM 的 64 位服务器上的许多 GB),则可以轻松地将缓冲区限制为您需要的任何内容(1GB, 16GB,随便),懒惰的方法见shutil标准库模块,或者只是编码出三行循环;-)。
    • 这是我瞄准的三行循环。 ;) 当然,“一切”是指fin 中的“一切”。 :)
    • @Vlad, while True:\n data=fin.read(BUFSIZ)\n if not data:break\n out.write(data)(格式较弱,但这是您在 SO cmets 中必须做的;-)。不过,在处理 20,000 个文件时,我不会担心每个文件超过几 GB,因此不会打扰这个循环;-)。
    【解决方案4】:

    假设您有一个简单的脚本来连接文件并为您保留一个计数器,如下所示:

    #!/usr/bin/bash
    COUNT=0
    if [ -f counter ]; then
      COUNT=`cat counter`
    fi
    COUNT=$[$COUNT+1]
    echo $COUNT > counter
    cat $@ > $COUNT.data
    

    命令行会做:

    find -name "*" -type f -print0 | xargs -0 -n 5 path_to_the_script
    

    【讨论】:

      【解决方案5】:

      我喜欢这种节省执行过程的方法,每个块只有 1 只猫

      #! /bin/bash
      
      N=5 # block size
      S=1 # start
      E=20000 # end
      
      for n in $(seq $S $N $E)
      do
          CMD="cat "
          i=$n
          while [ $i -lt $((n + N)) ]
          do
              CMD+="data$((i++)).txt "
          done
          $CMD > data_new_$((n / N + 1)).txt
      done
      

      【讨论】:

        【解决方案6】:

        够简单吗?

        make_cat.py

        limit = 1000
        n = 5
        for i in xrange( 0, (limit+n-1)//n ):
             names = [ "data{0}.txt".format(j) for j in range(i*n,i*n+n) ]
             print "cat {0} >data_new_{1}.txt".format( " ".join(names), i )
        

        脚本

        python make_cat.py | sh
        

        【讨论】:

        • 为什么必须调用shell?你可以用 Python 做任何事情。
        • “就最简单的代码而言,我的意思是最好的方法。”我认为这是对“最佳”的完美定义。这就是为什么我写了这么一段奇特的代码。我认为它符合定义。并证明“最简单的代码”并不总是可取的。
        【解决方案7】:

        一个班轮怎么样? :)

        ls data[0-9]*txt|sort -nk1.5|awk 'BEGIN{rn=5;i=1}{while((getline _<$0)>0){print _ >"data_new_"i".txt"}close($0)}NR%rn==0{i++}'
        

        【讨论】:

        • 嗨,我现在意识到你的方法是最好的。只是一件事,对于超过 20000 个文件我得到错误,你怎么能用你的脚本和 xargs 一起使用?谢谢
        • ls file* |xargs -n1 | sort...
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-05-13
        • 2021-03-16
        • 2010-12-18
        • 1970-01-01
        • 2021-08-01
        • 2018-08-20
        相关资源
        最近更新 更多