我有一个 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
非自动打印标志运行。标量变量$sum1
是state
d,这意味着它将在标志指示的循环-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 中可用的其他类型包括Num
s 和Int
s)。Rat
Raku 中的 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