【问题标题】:Splitting a file by lines into N guaranteed files as evenly as possible using AWK使用 AWK 将文件按行尽可能均匀地拆分为 N 个保证文件
【发布时间】:2015-03-25 06:40:38
【问题描述】:

如果我有一个 L+1 行长的文件 bigfile.csv(包括一个标题),我的目标是尽可能均匀地将这个文件分成 N 个文件(比如 100 个),并将标题附加到每个文件中的分裂。假设我知道文件中有多少行,以及它需要拆分成多少个文件,这是先验的。

目前,我已经有了这个代码:

awk 'NR==1 {h=$0; next} (NR-2)% 100==0 {filename = sprintf("%04d",int((NR-2)/100)+1) ".smallfile.csv"; print h >> filename} {print >> filename}' bigfile.csv

虽然这段代码大部分时间都有效,但它有两个失败之处:

  1. 除非原始文件中的行数可以被 N 整除,否则将有 N-1 个文件的 ceil(L/N) 行,最后一个文件的行数明显少于其他文件。不过,这没什么大不了的。
  2. 在某些情况下,如果 N 很大并且 L 和 N 之间的比率很低,则不会有 N 个保证文件。例如,如果原始文件有 6021 行要拆分为 100 个文件,那么您最终只会得到 99 个文件,因为每个文件将有 61 行。

我将如何在 AWK 中执行此操作?理想情况下,给定上述示例,将 6021 行拆分为 100 个文件将有 21 个文件,其中 61 行,其余为 60 行。

我知道您可以在 unix 中使用 split(1) 并以循环方式进行,但这会改变行的顺序。稍后这些行将被分布式处理并重新组装,因此保持相同的顺序会更理想。

【问题讨论】:

    标签: unix csv awk split


    【解决方案1】:

    恐怕我们不得不求助于……数学。不是很复杂的数学,很高兴。我的设想是:

    awk -v files=100 'NR == FNR { linecount = FNR; next } FNR == 1 { chunksize = (linecount - 1) / files; h = $0; next } FNR > nextsplit { close(filename); ++chunk; filename = sprintf("%04d.smallfile.csv", chunk); nextsplit = 1 + int(chunksize * chunk + 0.5); print h >> filename } { print >> filename }' bigfile.csv bigfile.csv
    

    为了将文件分割成偶数块,我们必须知道它有多少行才能开始分割,所以这需要两次传递——这就是为什么bigfile.csv 被提到了两次。

    awk 代码是

    NR == FNR {         # During the first pass: remember the line count.
      linecount = FNR   # I'd like to use ENDFILE for this, but mawk doesn't
                        # know it. Oh well.
      next              # Do nothing else for the first pass.
    }
    FNR == 1 {          # first line of the second pass:
      chunksize = (linecount - 1) / files  # calculate chunksize
      h = $0                               # remember header.
      next                                 # skip to data
    }
    FNR > nextsplit {   # When we pass a split point:
      close(filename)   # close previous output file (if any)
      ++chunk           # update chunk counter and build new output file name
      filename = sprintf("%04d.smallfile.csv", chunk)
    
      # and calculate the next split point. This is very simply
      # chunksize * chunk rounded to the next integer. The rounding is important
      # to ward of floating point rounding errors that might leave us with a
      # one-line file at the end if 1234.9999999 were rounded down to 1234.
      # offset of one because we don't count the header as part of the split data.
      nextsplit = 1 + int(chunksize * chunk + 0.5)
    
      # Then print the header to the new file
      print h >> filename
    }
    { print >> filename } # and print all lines to the file.
    

    【讨论】:

    • 感谢您的帮助。当我运行你的代码时,我实际上得到了 6021 个拆分文件,第一个文件有标题,第二个文件有第一行,等等。
    • 哦,我对 mawk 有这种行为,但不是 gawk。嗯...我可能正在使用一些非便携式浮点功能,但是哪个?给我一点时间。
    • 啊,不:mawk 不懂ENDFILE。我没想到。我将在解决方法中进行编辑。并容纳我第一次忘记的标题。
    • 谢谢,工作正常。不过只有一个问题。在第一个第二遍开始时,nextsplit 尚未定义。是否预设为0?这就是 FNR>nextsplit 评估为 true 的原因吗?
    • "+1" 使用 sprintf AND 字符串连接 sprintf("%04d", chunk) ".smallfile.csv" 有点奇怪 - 考虑只使用 sprintf:sprintf("%04d.smallfile.csv", chunk)
    【解决方案2】:
    # create a test bigfile
    { echo "header"; seq 6021; } > bigfile.csv
    
    # strip the header
    tmp=$(mktemp)
    sed 1d bigfile.csv > "$tmp"
    header=$( sed 1q bigfile.csv )
    
    # split the file
    N=100
    split --suffix-length=${#N} --numeric-suffixes=1 --additional-suffix=.csv --number=r/$N $t smallfile.
    
    # add the header to each file
    sed -i "1i\\
    $header" small*
    

    并且,检查结果:

    $ wc -l small*
       62 smallfile.001.csv
       62 smallfile.002.csv
       62 smallfile.003.csv
       62 smallfile.004.csv
       62 smallfile.005.csv
       62 smallfile.006.csv
       62 smallfile.007.csv
       62 smallfile.008.csv
       62 smallfile.009.csv
       62 smallfile.010.csv
       62 smallfile.011.csv
       62 smallfile.012.csv
       62 smallfile.013.csv
       62 smallfile.014.csv
       62 smallfile.015.csv
       62 smallfile.016.csv
       62 smallfile.017.csv
       62 smallfile.018.csv
       62 smallfile.019.csv
       62 smallfile.020.csv
       62 smallfile.021.csv
       61 smallfile.022.csv
       61 smallfile.023.csv
       61 smallfile.024.csv
       61 smallfile.025.csv
       61 smallfile.026.csv
       61 smallfile.027.csv
       61 smallfile.028.csv
       61 smallfile.029.csv
       61 smallfile.030.csv
       61 smallfile.031.csv
       61 smallfile.032.csv
       61 smallfile.033.csv
       61 smallfile.034.csv
       61 smallfile.035.csv
       61 smallfile.036.csv
       61 smallfile.037.csv
       61 smallfile.038.csv
       61 smallfile.039.csv
       61 smallfile.040.csv
       61 smallfile.041.csv
       61 smallfile.042.csv
       61 smallfile.043.csv
       61 smallfile.044.csv
       61 smallfile.045.csv
       61 smallfile.046.csv
       61 smallfile.047.csv
       61 smallfile.048.csv
       61 smallfile.049.csv
       61 smallfile.050.csv
       61 smallfile.051.csv
       61 smallfile.052.csv
       61 smallfile.053.csv
       61 smallfile.054.csv
       61 smallfile.055.csv
       61 smallfile.056.csv
       61 smallfile.057.csv
       61 smallfile.058.csv
       61 smallfile.059.csv
       61 smallfile.060.csv
       61 smallfile.061.csv
       61 smallfile.062.csv
       61 smallfile.063.csv
       61 smallfile.064.csv
       61 smallfile.065.csv
       61 smallfile.066.csv
       61 smallfile.067.csv
       61 smallfile.068.csv
       61 smallfile.069.csv
       61 smallfile.070.csv
       61 smallfile.071.csv
       61 smallfile.072.csv
       61 smallfile.073.csv
       61 smallfile.074.csv
       61 smallfile.075.csv
       61 smallfile.076.csv
       61 smallfile.077.csv
       61 smallfile.078.csv
       61 smallfile.079.csv
       61 smallfile.080.csv
       61 smallfile.081.csv
       61 smallfile.082.csv
       61 smallfile.083.csv
       61 smallfile.084.csv
       61 smallfile.085.csv
       61 smallfile.086.csv
       61 smallfile.087.csv
       61 smallfile.088.csv
       61 smallfile.089.csv
       61 smallfile.090.csv
       61 smallfile.091.csv
       61 smallfile.092.csv
       61 smallfile.093.csv
       61 smallfile.094.csv
       61 smallfile.095.csv
       61 smallfile.096.csv
       61 smallfile.097.csv
       61 smallfile.098.csv
       61 smallfile.099.csv
       61 smallfile.100.csv
     6121 total
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-09-22
      • 1970-01-01
      • 1970-01-01
      • 2018-10-05
      • 2020-10-15
      • 2015-08-22
      • 2012-07-02
      相关资源
      最近更新 更多