有效执行 CSV 拆分,同时考虑限制

有效执行 CSV 拆分,同时考虑限制

1000我必须将一个大的 CSV 文件拆分为多个小文件,每个小文件中的记录数量有限。但是,我事先不知道那个大 CSV 文件中有多少条记录。我必须有效地进行此划分,如下所示,

  1. 如果大型 CSV 有 1000 条记录 - 不执行任何拆分
  2. 如果大型 CSV 有 2000 条记录 - 创建 2 个文件,每个文件包含 1000 条记录
  3. 如果大型 CSV 有 1200 条记录 - 创建 2 个文件,每个文件包含 600 条记录,而不是创建 1 个包含 1000 条记录的文件和第二个包含 200 条记录的文件。

这种划分必须尽可能有效,即创建尽可能少的文件,但尝试使每个文件中的记录几乎相同,而不达到文件中 1000 条记录的上限。

想知道这个数学方程在 shell 脚本中会是什么样子,

calculate_number_of_files() {
    max_limit=1000
    total_records=$1

    ... math logic here
}

答案1

假设没有 CSV 字段包含换行符(文件中的行数等于 CSV 记录的数量),并且没有标题行需要复制到每个文件,您可以这样做(假设split这里是 GNU):

#! /bin/zsh -
ret=0 max=1000
for file do
  if lines=$(wc -l < $file); then
    if (( lines > max )); then
      (( nfiles = lines / max + ! ! (lines % max) ))
      (( lines_per_file = lines / nfiles + ! ! (lines % nfiles) ))
      split --verbose \
            --lines=$lines_per_file \
            --additional-suffix=.csv \
            --numeric-suffixes=1 -- $file $file:r. || ret=$?
    else
      print -ru2 - $file has $lines lines, no splitting.
    fi
  else
    ret=$?
  fi
done
exit $ret

调用为that-script foo.csv bar.csv, 并根据需要生成foo.01.csv, foo.02.csv...。添加将/--suffix-length=3更改为/ (并允许超过99个输出文件)。0102001002

这些(( x = y / n + !! (y % n) ))就像(( x = ceil(y / n) ))(整数除法,四舍五入)。(( x = (y + n - 1) / n ))作为由 @LSerni 显示也会起作用。

zshceil()其函数中确实有该函数zsh/mathfunc,但我们需要将数字从整数/浮点数转换为整数/浮点数,所以最终结果是类似的工作量:

#! /bin/zsh -
zmodload zsh/mathfunc || exit
ret=0 max=1000

for file do
  if lines=$(wc -l < $file); then
    if (( lines > max )); then
      (( nfiles = ceil(lines * 1. / max) ))
      # lines is integer, nfiles is float
      (( lines_per_file = int(ceil(lines / nfiles)) ))
      split --verbose \
            --lines=$lines_per_file \
            --additional-suffix=.csv \
            --numeric-suffixes=1 -- $file $file:r. || ret=$?
    else
      print -ru2 - $file has $lines lines, no splitting.
    fi
  else
    ret=$?
  fi
done
exit $ret

请注意,这不是完全最佳的拆分,因为4001行文件将被拆分为例如 801、801、801、801、797 行的文件,而人们可能更喜欢例如 801、800、800、800、800 行,但这不是我们可以用该命令进行的一种拆分split

答案2

第 1 阶段:计算行数(按照 Stéphane Chazelas 的建议)

ROWS=$( wc -l < "$FILE" )

第 2 阶段:找到要拆分的正确行数。round(TotalLines/1000)是文件将被分割成的文件数(2000 为 2,1200 也为)。

ROWS=$( echo "scale=0;$ROWS/(($ROWS+999)/1000)" | bc )

第 3 阶段:用于split -l将文件切成$ROWS- 大小的块:

split --lines "$ROWS" "$FILE"

答案3

这个解决方案怎么样?我认为这个解决方案可以做得更好。

calculate_number_of_files() {
  declare maxLimit=1000
  declare numberOfRecords=3475
  declare filesNeeded=0

  if [[ $numberOfRecords -le $maxLimit ]]; then
    filesNeeded=1
  else 
    filesNeeded=$(echo $(( numberOfRecords / maxLimit )))
    filesNeeded=$(echo $(( filesNeeded + 1 )))
  fi

  echo "Number of files needed --> " $filesNeeded
  lastFile=$(echo $(( filesNeeded - 1 )))

  for ((i=0; i<$filesNeeded; i++)) do
    recordsInEachFile=$(echo $(( numberOfRecords / filesNeeded )))


    if [[ $i == $lastFile ]]; then
      recordsInEachFile=$(echo $(( numberOfRecords - (recordsInEachFile * i) )))
      echo "Number of records in file " $i " --> $recordsInEachFile";
      break
    fi

    echo "Number of records in file " $i " --> $recordsInEachFile";
  done
}

这打印,

Number of files needed -->  4
Number of records in file  0  --> 868
Number of records in file  1  --> 868
Number of records in file  2  --> 868
Number of records in file  3  --> 871

相关内容