我有一个下面的输入文件,我需要根据第三列中的日期将其拆分为多个文件。基本上所有相同日期的交易都应该分成特定日期的文件。分割后我需要创建标题和预告片。预告片应在第四列中包含记录数和金额总和(该日期的金额总和)。在这种情况下,正如我上面所说,我有非常大的数字,如何在下面的代码中集成 bc 。
输入文件
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^8|~^xxx|~^123670130.37256
输出文件20190305.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068
输出文件20190306.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456
我正在使用的代码(PS:由我们的一位社区成员建议)这是一个awk
解决方案:
awk -F'\\|~\\^' '{
if($1=="H"){
head=$0
}
else if($1=="T"){
foot=$1"|~^"$2
foot4=$4
}
else{
date=$3;
sub("T.*","", date);
data[date][NR]=$0;
sum[date]+=$4;
num[date]++
}
}
END{
for(date in data){
file=date".txt";
gsub("-","",file);
print head > file;
for(line in data[date]){
print data[date][line] > file
}
printf "%s|~^%s|~^%s|~^%s\n", foot, num[date],
foot4, sum[date] > file
}
}' file
代码运行得非常好。但在这一步中
sum[date]+=$4;
它无法对大数求和。因为我在最后一步使用%s
,所以预告片总和以指数值打印。
printf "%s|~^%s|~^%s|~^%s\n", foot, num[date],
foot4, sum[date] > file
在这里,我只想对大数应用求和并打印精确的总和。 (我在这里尝试了 bc(bash 计算器),但被卡住了,因为这个总和是基于数组的,而且它是根据特定日期添加的)。请帮我解决这个问题
另外,我尝试"%.15g"
了拖车步骤
printf "%s|~^%s|~^%s|~^%.15g\n", foot, num[date],
foot4, sum[date] > file
在此,如果结果有 15 位(包括小数点),我就能得到准确的总和。如果总和结果超过 15 位,则此方法不起作用。请帮忙
答案1
如果不考虑你的大数字问题,我会编写awk
如下程序:
BEGIN {
FS = "\\|~\\^"
OFS= "|~^"
}
$1 == "H" {
header = $0
}
$1 == "R" {
name = $3
sub("T.*", "", name)
sum[name] += $4
cnt[name] += 1
if (cnt[name] == 1)
print header >name ".txt"
print >name ".txt"
}
$1 == "T" {
for (name in sum)
print $1, $2, cnt[name], $4, sum[name] >name ".txt"
}
为了方便起见,我将输出字段分隔符 , 设置OFS
为|~^
。这使我不必担心将其插入到我输出的字段之间。输入的字段分隔符FS
, 设置为与该字符串匹配的正则表达式。
然后我有三个主要代码块:
一个用于解析该
H
行。假设只有其中一个并且它在一开始就发生。这只是将标题行存储在变量中header
。一个用于解析
R
行。每条记录都包含应在第三个字段中用作输出文件名的日期。其解析方式与您的解析方式相同。该日期的总和会被累加,并且计数器也会递增。如果计数器为 1,即如果这是我们第一次看到该特定日期,则我们将标头写入与该日期关联的输出文件。然后我们将当前记录写入文件。
最后一个块解析该
T
行。假设只有其中之一并且它出现在最后。这只是将每个单独日期的累积总和和计数以及原始T
行中的一些数据输出到与该日期关联的文件。
支持任意大数(你说别处如果您的数字需要超过 100 位来存储,因此会溢出awk
) 中的整数,我们将任意精度计算器用作bc
“协进程”(一种计算服务)。该行sum[name] += $4
被替换为
if (sum[name] == "") sum[name] = 0
printf "%s + %s\n", sum[name], $4 |& "bc"
"bc" |& getline sum[name]
这需要 GNU awk
(以一种或另一种方式可用于大多数 Unix 系统)。
其作用是,如果当前日期还没有总和,则首先将当前日期的总和初始化为零。我们这样做是因为我们需要提供初始总和0
。bc
bc
然后,我们打印应使用 GNUawk
特定管道进行计算的表达式,|&
以写入协进程。该bc
实用程序将与我们的脚本并行启动并运行awk
,进行计算,然后将另一个管道的getline
输出直接读取到.bc
|&
sum[name]
据我了解,GNUawk
不会bc
为每个求和生成一个单独的进程,而是会维护一个bc
作为协进程运行的进程。因此,这比在本地进行计算要慢,但比为每个求和awk
生成单独的计算要快得多。bc
对于给定的数据,将创建以下两个文件:
$ cat 2019-03-05.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-05T12:33:52.27|~^105603.042|~^2018-10-23T12:33:52.27|~^aus
R|~^abc|~^2019-03-05T12:33:52.27|~^2054.026|~^2018-10-24T12:33:52.27|~^usa
R|~^abc|~^2019-03-05T12:33:52.27|~^30.00|~^2018-08-05T12:33:52.27|~^ddd
R|~^abc|~^2019-03-05T12:33:52.27|~^20.00|~^2018-07-23T12:33:52.27|~^audg
T|~^20200425|~^4|~^xxx|~^107707.068
$ cat 2019-03-06.txt
H|~^20200425|~^abcd|~^sum
R|~^abc|~^2019-03-06T12:33:52.27|~^123562388.23456|~^2018-04-12T12:33:52.27|~^hhh
R|~^abc|~^2019-03-06T12:33:52.27|~^10.00|~^2018-09-11T12:33:52.27|~^virginia
R|~^abc|~^2019-03-06T12:33:52.27|~^15.03|~^2018-10-23T12:33:52.27|~^jjj
R|~^abc|~^2019-03-06T12:33:52.27|~^10.04|~^2018-04-08T12:33:52.27|~^jj
T|~^20200425|~^4|~^xxx|~^123562423.30456
答案2
我已经写了一个awk 代码来解决这个问题它的运行速度比您在此处呈现的代码更快。
您过去已经问过对许多数字求和并得到不精确答案的问题。这个问题与另一个问题非常相似为什么这两个 sum 命令之间存在差异?。
该问题的文件大小为 20 MB,有 700 多行。
您已声明您的文件的顺序为文件大小约为 500 至 600 mb。这会将行数增加到 1000 万行的范围。
问题是要添加的数字:
可能相差很大:范围从 3 位数字
12.8
到 28 位数字1245637.34526234567299999999
。将 28 位数字相加 1000 万次,需要 28 + 7 = 35 位数字。这是假设数字不全是小数或整数。如果发生这种情况,我们讨论的是 70 位数字(35 个整数 + 35 个小数)。
浮点数的表示始终是精确数字的近似值,这是浮点数的一个基本问题。如果必须有精确的总和,则必须将它们全部作为整数相加。
作为问题的解决方案,可能是使用具有更长位数的 GNU awk。 awk 中的默认浮点数使用 53 位尾数,仅适用于 15 位数字。
如果您使用使用 MPFR(可靠多精度浮点)和 GMP(GNU 多精度算术库)编译的 GNU AWK,则其 --version 文本的结果应包含该信息 (execute awk --version
)。在这种情况下,您可以使用更多位。为了能够保留 40 位浮点数(上面计算的 35 位数字 + 一些安全边际),您将需要:
b = ceil(d log2(10)) + 1
b = ceil( 40 * 3.321928 ) + 1 = 133 + 1 = 134 binary digits (bits)
因此,awk 调用应该是:
awk -M -v PREC=134
警告:使用更多数字会使程序变慢。
并且仍然使用相同的 awk 程序
awk -M -v PREC=134 '
BEGIN { FS="\\|~\\^"; OFS="|~^" }
$1=="H"{ header=$0; hdr=$2 }
$1=="R"{
t=gensub(/-/, "","g",$3)
file=gensub(/T.*/,"",1,t);
sum[file]+=$4
if(count[file]==0){ print header >file }
count[file]++
print $0 >>file
}
END {
for( i in sum ){
printf "T %s %10d xxx %45.25f",hdr,count[i],"xxx",sum[i] >> i;
close(i)
}
}
' "inputfile"
仅供参考:您一遍又一遍地问几乎相同的问题: