如何在几秒钟内创建大型CSV?

时间:2019-03-30 00:57:51

标签: bash csv

我正在尝试快速创建1000个大型CSV。此函数生成CSV:

function csvGenerator () {

  for ((i=1; i<=$NUMCSVS; i++)); do
    CSVNAME=$DIRNAME"-"$CSVPREFIX$i$CSVEXT
    HEADERARRAY=()

    if [[ ! -e $CSVNAME ]]; then #Only create csv file if it not exist
      touch $CSVNAME
      echo "file: "$CSVNAME "created at $(date)" >> ../status.txt
    fi

    for ((j=1; j<=$NUMCOLS; j++)); do

      if  (( j < $NUMCOLS )) ; then
        HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j", "
      elif (( j == $NUMCOLS )) ; then
        HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j
      fi
      HEADERARRAY+=$HEADERNAME

    done

    echo $HEADERARRAY > $CSVNAME

    for ((k=1; k<=$NUMROWS; k++)); do
      ROWARRAY=()

      for ((l=1; l<=$NUMCOLS; l++)); do

        if (( l < $NUMCOLS )) ; then
          ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l", "
        elif (( l == $NUMCOLS )) ; then
          ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l
        fi
        ROWARRAY+=$ROWVALUE

      done

      echo $ROWARRAY >> $CSVNAME

    done

  done

}

该脚本大约需要3分钟才能生成具有10万行和70列的CSV。我需要做什么才能以1 CSV /〜10秒的速率生成这些CSV?

3 个答案:

答案 0 :(得分:2)

首先让我说bash和“表演者”通常不在同一句子中。正如其他评论员所建议的,awk在某些意义上可能是一个很好的选择。

我还没有机会运行您的代码,但是它每行打开和关闭输出文件一次-在本示例中为100,000次。每次必须搜索到文件的末尾,以便它可以附加最新的行。

尝试将实际的世代(for ((j=1; j<=$NUMCOLS; j++)); do之后的所有东西)引入新功能,例如generateCsvContents。在该新函数中,不要引用$CSVNAME,而要删除echo语句上的重定向。然后,在原始函数中,调用新函数并将其输出重定向到文件名。大概是:

function csvGenerator () {
 for ((i=1; i<=NUMCSVS; i++)); do
    CSVNAME=$DIRNAME"-"$CSVPREFIX$i$CSVEXT

    if [[ ! -e $CSVNAME ]]; then #Only create csv file if it not exist
      echo "file: $CSVNAME created at $(date)" >> ../status.txt
    fi

    # This will create $CSVNAME if it doesn't yet exist
    generateCsvContents > "$CSVNAME"
  done
}

function generateCsvContents() {
  HEADERARRAY=()
  for ((j=1; j<=NUMCOLS; j++)); do
    if  (( j < NUMCOLS )) ; then
      HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j", "
    elif (( j == NUMCOLS )) ; then
      HEADERNAME=$DIRNAME"-csv-"$i"-header-"$j
    fi
    HEADERARRAY+=$HEADERNAME
  done

  echo $HEADERARRAY

  for ((k=1; k<=NUMROWS; k++)); do
    ROWARRAY=()
    for ((l=1; l<=NUMCOLS; l++)); do
      if (( l < NUMCOLS )) ; then
        ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l", "
      elif (( l == NUMCOLS )) ; then
        ROWVALUE=$DIRNAME"-csv-"$i"-r"$k"c"$l
      fi
      ROWARRAY+=$ROWVALUE
    done
    echo "$ROWARRAY"
  done
}

答案 1 :(得分:2)

“不是这样”是我的答案。

这里有一些问题。

  • 您没有将数组用作数组。当您将它们当作字符串对待时,只会影响数组中的第一个元素,这会产生误导。
  • 您使用>>的方式会使输出文件每行打开和关闭一次。这可能很浪费。
  • 您没有引用变量。实际上,您是在引用不需要的内容,而不是在引用那些内容。
  • 不建议使用大写字母的变量名,因为存在与系统变量冲突的风险。 ref
  • 猛击不擅长此事。真的。

函数的清理版本可能如下所示:

csvGenerator2() {

  for (( i=1; i<=NUMCSVS; i++ )); do
    CSVNAME="$DIRNAME-$CSVPREFIX$i$CSVEXT"

    # Only create csv file if it not exist
    [[ -e "$CSVNAME" ]] && continue

    touch "$CSVNAME"
    date "+[%F %T] created: $CSVNAME" | tee -a status.txt >&2

    HEADER=""
    for (( j=1; j<=NUMCOLS; j++ )); do
      printf -v HEADER '%s, %s-csv-%s-header-%s' "$HEADER" "$DIRNAME" "$i" "$j"
    done

    echo "${HEADER#, }" > "$CSVNAME"

    for (( k=1; k<=NUMROWS; k++ )); do

      ROW=""
      for (( l=1; l<=NUMCOLS; l++ )); do
        printf -v ROW '%s, %s-csv-%s-r%sc%s' "$ROW" "$DIRNAME" "$i" "$k" "$l"
      done

      echo "${ROW#, }"

    done >> "$CSVNAME"

  done

}

(请注意,由于我很懒,我没有将变量切换为小写,但这仍然是一个好主意。)

如果要在awk中进行功能上等效的事情:

csvGenerator3() {
  awk -v NUMCSVS="$NUMCSVS" -v NUMCOLS="$NUMCOLS" -v NUMROWS="$NUMROWS" -v DIRNAME="$DIRNAME" -v CSVPREFIX="$CSVPREFIX" -v CSVEXT="$CSVEXT" '
    BEGIN {
      for ( i=1; i<=NUMCSVS; i++) {
        out=sprintf("%s-%s%s%s", DIRNAME, CSVPREFIX, i, CSVEXT)
        if (!system("test -e " CSVNAME)) continue
        system("date '\''+[%F %T] created: " out "'\'' | tee -a status.txt >&2")

        comma=""
        for ( j=1; j<=NUMCOLS; j++ ) {
          printf "%s%s-csv-%s-header-%s", comma, DIRNAME, i, j > out
          comma=", "
        }
        printf "\n" >> out

        for ( k=1; k<=NUMROWS; k++ ) {
          comma=""
          for ( l=1; l<=NUMCOLS; l++ ) {
            printf "%s%s-csv-%s-r%sc%s", comma, DIRNAME, i, k, l >> out
            comma=", "
          }
          printf "\n" >> out
        }
      }
    }
  '
}

请注意,awk不会像前面提到的bash那样受到相同的打开/关闭开销。当文件用于输出或作为管道时,文件将被打开一次并保持打开状态直到关闭。

将两者进行比较确实突出了您需要做出的选择:

$ time bash -c '. file; NUMCSVS=1 NUMCOLS=10 NUMROWS=100000 DIRNAME=2 CSVPREFIX=x CSVEXT=.csv csvGenerator2'
[2019-03-29 23:57:26] created: 2-x1.csv

real    0m30.260s
user    0m28.012s
sys     0m1.395s
$ time bash -c '. file; NUMCSVS=1 NUMCOLS=10 NUMROWS=100000 DIRNAME=3 CSVPREFIX=x CSVEXT=.csv csvGenerator3'
[2019-03-29 23:58:23] created: 3-x1.csv

real    0m4.994s
user    0m3.297s
sys     0m1.639s

请注意,即使是我优化的bash版本也仅比您的原始代码快一点。

答案 2 :(得分:0)

将您的两个内部for循环重构为这样的循环将节省时间:

for ((j=1; j<$NUMCOLS; ++j)); do
  HEADERARRAY+=$DIRNAME"-csv-"$i"-header-"$j", "
done
HEADERARRAY+=$DIRNAME"-csv-"$i"-header-"$NUMCOLS