1000
大きなCSVファイルを、各小さなファイルに限られた数のレコードを持つ複数の小さなファイルに分割する必要があります。しかし、その大きなCSVファイルにどのくらいのレコードが含まれているかを事前に知ることはできません。この分割を効率的に次のようにしなければならないが、
- 大規模CSVに1000レコードがある場合、分割は行われません。
- 大きなCSVに2000のレコードがある場合 - それぞれ1000のレコードを含む2つのファイルを作成します。
- 大規模CSVに1200のレコードがある場合は、1000のレコードを含む1つのファイルと200のレコードを含む2番目のファイルを作成する代わりに、それぞれ600のレコードを含む2つのファイルを作成します。
このパーティショニングはできるだけ効率的でなければなりません。つまり、できるだけ少ないファイルを生成しますが、ファイルのレコード数の上限である1000個に達せずに、各ファイルのレコードをほぼ同じに保つ必要があります。
この数学方程式がシェルスクリプトでどのように見えるか疑問に思います。
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以上の出力ファイルを許可)。01
02
001
002
これは(整数除算、丸め)(( x = y / n + !! (y % n) ))
と同じです。 〜のように(( x = ceil(y / n) ))
(( x = (y + n - 1) / n ))
@LSerniが表示しました動作します。
zsh
ceil()
関数には機能がありますが、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