对文件中每 6 行记录的第 5 行值求和

对文件中每 6 行记录的第 5 行值求和

我有一个 txt 文件,其中包含如下文本块:

17-01-2023
Purchase AAA
Apple Pay John Doe
Full Payment
-11,34€
0,11€
30-01-2023
Purchase BBB
Mastercard Jane Doe
Installment
-23,90€
0,24€

因此,我们依次有日期、购买类型、付款类型和名称、付款类型、负值和折扣。

这些在包含数千个条目的文件中重复。

我想对这些值进行求和,在本例中为 11,34 + 23,90,并将总和设为正数。请记住,数字后面有一个欧元符号,在我的语言环境中,逗号是小数点分隔符。

如何使用 sed、awk 等从终端执行此操作?

答案1

您可以使用 awk - 与非数字货币符号前缀(如 )的情况不同€-23,90,在数字转换期间将忽略非数字后缀。请注意,不同的实现可能会以不同的方式处理区域设置的小数分隔符,例如。

mawk 'NR%6 == 5 {sum -= $0} END {print sum}' file

尊重LC_NUMERIC/LC_ALL根据要求POSIX 合规性,而 GNU awk 默认情况下偏离 POSIX 规范,需要指示使用您的语言环境:

gawk --use-lc-numeric 'NR%6 == 5 {sum -= $0} END {print sum}' file

请参阅 GNU Awk 用户指南:区域设置会影响转换


例如,使用 de_DE.UTF-8 语言环境进行测试:

$ export LC_NUMERIC=de_DE.UTF-8
$ 
$ mawk 'NR%6 == 5 {sum -= $0} END {print sum}' yourfile
35,24
$ 
$ gawk --use-lc-numeric 'NR%6 == 5 {sum -= $0} END {print sum}' sum=x yourfile
35,24

在 Mac 操作系统上:

$ awk --version
awk version 20200816

$ export LC_NUMERIC=de_DE.UTF-8

$ awk 'NR%6 == 5 {sum -= $0} END {print sum}' yourfile
35,24

答案2

这只是为了好玩 - 假设使用 GNU sed 来n~m构建:

$ sed -n '5~6{y/-,€/_.+/;p}' file | dc -e0 -f- -e_1\*p
35.24

(当然,如果需要,您可以添加另一个 sed 或 tr 将小数点转换回原始语言环境,)。

答案3

假设交易金额始终位于记录的第 5 行,记录开始由“DD-MM-YYYY”格式的日期指示,并且这种模式只能出现在记录开始时,以下awk程序将执行以下操作:

awk -v dpt=$(locale decimal_point) '/^([[:digit:]]{2}-){2}[[:digit:]]{4}$/{line_of_rec=0}
     {if (++line_of_rec==5) { if (dpt==".") sub(/,/,"."); total-=$0 } }
     END{printf "Total payments: %.2f\n",total}' input.txt

其工作原理如下:

  • 它将命令的结果locale decimal_point作为变量传递dpt给程序。这是相关的,因为您似乎awk在使用小数点分隔符格式化输入的设置中使用,,但区域设置设置为使用 的内容.,导致awk错过数字的小数部分。
  • 它通过模式识别记录起始行(我们假设没有前导和尾随空格!),并将变量设置line_of_record为 0。
  • 对于每一行,增加line_of_record计数器。如果达到 5,它将用,a替换.(如果需要),以便awk将行内容解释为十进制数,并从变量中减去行内容,total以便对正支付值求和。
  • 在行尾,它将打印总数。

这比最低限度需要的代码要多,但如果有空行,则会使程序更加健壮分离记录(它仍然依赖于交易金额位于记录的第 5 行)。

答案4

使用(以前称为 Perl_6)

没有类型检查:

~$ raku -ne 'state $sum1; $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5; END say $sum1;'  file

#OR 

raku -e 'my $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5 for lines; say $sum1;'  file

通过类型检查:

~$ raku -ne 'state Rat $sum1; $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5; END say $sum1;'  file

#OR

~$ raku -e 'my Rat $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5 for lines; say $sum1;'  file

简而言之(第一个示例),Raku 在命令行中使用-ne非自动打印标志运行。标量变量$sum1stated,这意味着它将在标志指示的循环-ne开始之前实例化。在第二个语句中,如果匿名递增行计数器变量模++$除以%6 等于 5,trans则将,逗号添加到点.并删除(subst不删除)欧元符号。然后+=累加到$sum变量中。在END循环的末尾,say $sum1.

输入示例:

17-01-2023
Purchase AAA
Apple Pay John Doe
Full Payment
-11,34€
0,11€
30-01-2023
Purchase BBB
Mastercard Jane Doe
Installment
-23,90€
0,24€

示例输出:

-35.24

对于累计总计,只需say累计变量:

~$ raku -ne 'state $sum1; say $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5;'

示例输出:

-11.34
-35.24

受@AdminBee的答案的启发,如果仅当行包含字符awk时增加行计数器,则可以容忍记录之间的空行:.chars

~$ raku -ne 'state Rat $sum1; say $sum1 += $_.trans("," => ".").subst(/\€/) if .chars && ++$ % 6 == 5;'

&&示例输出(与上面使用或相同and):

-11.34
-35.24

请注意,OP 示例中给出的数字Rat默认在 Raku 中输入为 ional 数字(Raku 中可用的其他类型包括Nums 和Ints)。RatRaku 中的 s(假设它们足够小)通常不会出现舍入误差,并且会快速转换为分数。例如,将END语句更改如下:

~$ raku -ne 'state $sum1; $sum1 += $_.trans("," => ".").subst(/\€/) if ++$ % 6 == 5; END say $sum1.numerator, "/", $sum1.denominator;'

示例输出:

-881/25

为了更快地操作,say $sum1.nude返回(-881 25).

https://docs.raku.org/language/numerics.html#Rational
https://raku.org

相关内容