如何在Bash中拆分文件时更新表头的行数与指定列求和值
拆分带统计信息表头的大文件并更新子文件表头
问题背景
我有一个包含数十亿条记录的大文件,文件首行是表头,其中包含元信息、总行数(第5个字段,分隔符为|)以及第6列的求和值。现在需要将文件拆分为指定行数的小文件,每个子文件的表头必须更新为对应子文件的实际记录行数和第6列的求和值。
示例输入文件(testFile.text)
00|STMT|08-09-2022 13:24:56||5|13.10|SHA2 10|000047290|8ddcf4b2356dfa7f326ca8004a9bdb6096330fc4f3b842a971deaf660a395f65|18-01-2020|12:36:57|3.10|00004729018-01-20201|APP 10|000052736|cce280392023b23df2a00ace4b82db8eb61c112bb14509fb273c523550059317|07-02-2017|16:27:49|2.00|00005273607-02-20171|APP 10|000070355|f2e86d2731d32f9ce960a0f5883e9b688c7e57ab9c2ead86057f98426407d87a|17-07-2019|20:25:02|1.00|00007035517-07-20192|APP 10|000070355|54c1fc2667e160a11ae1dbf54d3ba993475cd33d6ececdd555fb5c07e64a241b|17-07-2019|20:25:02|5.00|00007035517-07-20192|APP 10|000072420|f5dac143082631a1693e0fb5429d3a185abcf3c47b091be2f30cd50b5cf4be11|14-06-2021|20:52:21|2.00|00007242014-06-20212|APP
预期拆分结果
testFile_1.text
00|STMT|08-09-2022 13:24:56||3|6.10|SHA2 10|000047290|8ddcf4b2356dfa7f326ca8004a9bdb6096330fc4f3b842a971deaf660a395f65|18-01-2020|12:36:57|3.10|00004729018-01-20201|APP 10|000052736|cce280392023b23df2a00ace4b82db8eb61c112bb14509fb273c523550059317|07-02-2017|16:27:49|2.00|00005273607-02-20171|APP 10|000070355|f2e86d2731d32f9ce960a0f5883e9b688c7e57ab9c2ead86057f98426407d87a|17-07-2019|20:25:02|1.00|00007035517-07-20192|APP
testFile_2.text
00|STMT|08-09-2022 13:24:56||2|7.00|SHA2 10|000070355|54c1fc2667e160a11ae1dbf54d3ba993475cd33d6ececdd555fb5c07e64a241b|17-07-2019|20:25:02|5.00|00007035517-07-20192|APP 10|000072420|f5dac143082631a1693e0fb5429d3a185abcf3c47b091be2f30cd50b5cf4be11|14-06-2021|20:52:21|2.00|00007242014-06-20212|APP
现有脚本问题
目前已实现文件拆分和求和计算,但无法更新子文件表头中的行数和求和值,现有脚本如下:
#!/bin/bash splitRowCount=$1 transactionColumn=$2 filename=$(basename -- "$3") extension="${filename##*.}" nameWithoutExt="${filename%.*}" echo "splitRowCount: $splitRowCount" echo "transactionColumn: $transactionColumn" awk 'NR == 1 { head = $0 } NR % '$splitRowCount' == 2 { filename = "'$nameWithoutExt'_" int((NR-1)/'$splitRowCount')+1 ".'$extension'"; print head > filename } NR != 1 { print >> filename }' $filename ls *.txt | while read line do firstLine=$(head -n 1 $line); awk -F '|' 'NR !=1 {sum += '$transactionColumn'}END {print sum} ' $line done
解决方案
修改脚本,在拆分过程中直接统计每个子文件的行数和求和值,生成正确的表头,避免事后修改的麻烦(尤其适合数十亿行的大文件,减少IO操作):
#!/bin/bash splitRowCount=$1 # 第6列的索引是6,这里默认取6,也可通过参数传入 transactionCol=${2:-6} inputFile=$3 filename=$(basename -- "$inputFile") extension="${filename##*.}" nameWithoutExt="${filename%.*}" echo "splitRowCount: $splitRowCount" echo "transactionColumn: $transactionCol" awk -v split_rows="$splitRowCount" -v trans_col="$transactionCol" \ -v base_name="$nameWithoutExt" -v ext="$extension" ' BEGIN { FS = "|" OFS = "|" file_num = 0 row_count = 0 sum_val = 0.0 } NR == 1 { # 保存表头的固定字段,仅动态替换第5(行数)和第6(求和值)字段 fixed_header[1] = $1 fixed_header[2] = $2 fixed_header[3] = $3 fixed_header[4] = $4 fixed_header[7] = $7 next } { # 每达到指定行数就新建一个子文件 if (row_count % split_rows == 0) { # 处理上一个子文件(如果存在) if (file_num > 0) { # 生成更新后的表头 printf "%s%s%s%s%s%s%s%s%d%s%.2f%s%s\n", fixed_header[1], OFS, fixed_header[2], OFS, fixed_header[3], OFS, fixed_header[4], OFS, row_count, OFS, sum_val, OFS, fixed_header[7] > out_file # 写入缓存的记录行 for (i=1; i<=row_count; i++) { print line_cache[i] > out_file } close(out_file) delete line_cache } file_num++ out_file = base_name "_" file_num "." ext row_count = 0 sum_val = 0.0 } row_count++ sum_val += $trans_col line_cache[row_count] = $0 } END { # 处理最后一个子文件 if (file_num > 0) { printf "%s%s%s%s%s%s%s%s%d%s%.2f%s%s\n", fixed_header[1], OFS, fixed_header[2], OFS, fixed_header[3], OFS, fixed_header[4], OFS, row_count, OFS, sum_val, OFS, fixed_header[7] > out_file for (i=1; i<=row_count; i++) { print line_cache[i] > out_file } close(out_file) } }' "$inputFile"
关键改进点
- 一次性处理:拆分同时统计行数和求和值,无需事后遍历修改表头,大幅减少IO开销,适配超大型文件
- 表头复用:仅动态替换表头中需要更新的字段,保留其余元信息不变
- 精度控制:用
%.2f保证求和值保留两位小数,和示例格式一致 - 缓存机制:缓存子文件的记录行,最后和表头一起写入,避免频繁文件IO操作
使用方式与原脚本一致:
./split_script.sh 3 6 testFile.text
内容的提问来源于stack exchange,提问作者Muddassir Rahman
相关产品推荐
相关产品推荐

