在Bash中进行整数除法会导致浮点结果

64
我有一个备份脚本在我的服务器上,它会进行定期备份,并向我发送备份文件的总结,包括新备份文件的大小。作为脚本的一部分,我想将文件的最终大小除以 (1024^3) 以获取以 GB 为单位的文件大小,而不是以字节为单位的文件大小。
由于 bash 不支持浮点数计算,我尝试使用管道将结果传递给 bc,但我在基本示例上遇到了困难。
我试图将 Pi 的值设置为一个比例,然而,即使以下命令可以工作:
~ #bc -l
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4/3
1.33333333333333333333
22/7
3.14285714285714285714
q
0
quit

一个非交互版本无法工作:
#echo $(( 22/7 )) | bc
3

这个有效:

#echo '22/7' | bc -l
3.14285714285714285714

但我需要使用变量。所以以下内容无法帮助我:

#a=22 ; b=7
#echo $(( a/b )) | bc -l
3

我在使用Bash中的变量语法方面显然缺少某些东西,需要一些关于我误解的“指针”。正如DigitalRoss所说,我可以使用以下内容:
#echo $a / $b | bc -l
3.14285714285714285714

然而我不能使用复杂的表达式,例如:

#echo $a / (( $b-34 )) | bc -l
-bash: syntax error near unexpected token `('
#echo $a / (( b-34 )) | bc -l
-bash: syntax error near unexpected token `('
#echo $a / (( b-34 )) | bc -l
-bash: syntax error near unexpected token `('

有人能给我一个可行的、正确的语法,用于获取复杂算术表达式中的浮点结果吗?


可以教授bash,例如浮点结果的整数除法:请参见:https://dev59.com/BGct5IYBdhLWcg3wXsQy#24431665 - Cyrus
6个回答

61

只需在表达式中加上双引号 ("):

echo "$a / ( $b - 34 )" | bc -l

然后bash会扩展$变量并忽略其他所有内容,bc将看到一个带有括号的表达式:

$ a=22
$ b=7
$ echo "$a / ( $b - 34 )" 
22 / ( 7 - 34 )

$ echo "$a / ( $b - 34 )" | bc -l
-.81481481481481481481

太棒了!我从没想到可以使用双引号来扩展变量。谢谢! - Joel G Mathew
嗯?双引号不会扩展变量,但允许这样做。 - Gilles Quénot
太棒了,如果我早点发现这个,我就会转用Python了。 - Gurubaran
似乎在除法时需要使用-l,但加法时不需要。我应该总是使用-l吗? - user2023370
@user2023370:-l 使用 "mathlib" 并将比例尺设置为 20,因此结果将显示到小数点后 20 位。如果没有 -l,默认比例尺为 0。 请参阅 man bc - Adrian Pronk
@AdrianPronk 是的,我最近发现了bc命令的scale参数可以帮助我解决这个问题。但即使考虑精度,也会使用-l选项,因为你将获得一致的格式...并且除法也可以工作! - user2023370

13
请注意,您的echo $(( 22/7 )) | bc -l实际上是让bash计算22/7,然后将结果发送给bc。因此,整数输出不是bc的结果,而只是提供给bc的输入。
尝试不使用管道将其传递到bc的echo $(( 22/7 )),您将会看到结果。

4

scale变量确定小数点后的数字位数。

$ bc
$ scale=2
$ 3/4
$ .75

3

我更喜欢使用awk而不是bc,因为它可以在一个命令中完成相同的事情,并且还可以通过使用printf添加变量和格式化输出,让你拥有更多的灵活性:

# Define vars in the command
awk -v a=3 -v b=2 'BEGIN{print a/b}'
1.5

# Define vars earlier and init with them awk vars
c=3
d=2
awk -v a=$c -v b=$d 'BEGIN{print a/b}'
1.5

# Use vars that are defined in script
a=3
b=2
awk 'BEGIN{print '$a'/'$b'}'

# Format your output using C printf syntax
awk -v a=3 -v b=2 'BEGIN{printf("%.3f\n", a/b)}'
1.500

此外,bc在除以零时不会返回代码错误,因此您无法检查错误:

echo 3/0 | bc -l
Runtime error (func=(main), adr=5): Divide by zero
# The error code is zero, that means there is no errors
echo $?
0

虽然awk会返回错误代码2:

awk -v a=3 -v b=0 'BEGIN{print a/b}'
awk: cmd. line:1: fatal: division by zero attempted
# awk returned code error 2, that indicates that something went wrong
echo $?
2

代码错误可用于检查除以零的情况,例如:

# Set your own vars
if output=$(awk -v a=3 -v b=0 'BEGIN{print a/b}' 2> /dev/null); then
    echo "$output"
else
    echo "error, division by zero"
fi

1
我同意awk是bash中最好的内置数学解释器。bash本身无法执行任何浮点运算,例如echo $((3/2))bc也无法处理十进制指数,例如echo "2^1.5" | bc -l。但是awk可以完成所有这些操作! - wisbucky

0

你可以直接在 awk 中处理除零错误检查:

for a in 19 29 31; do 
for b in 11  3  0; do 

   gawk -v PREC=512 -Mbe '$++NF= +(_=$NF) ? $(!!_)/_ : "div_by_zero"' \
                              \ 
         CONVFMT='%.59g' OFS=' \t| ' <<< "${a} ${b}"; done; done
19  | 11    | 1.7272727272727272727272727272727272727272727272727272727273
19  | 3     | 6.3333333333333333333333333333333333333333333333333333333333
19  | 0     | div_by_zero
29  | 11    | 2.6363636363636363636363636363636363636363636363636363636364
29  | 3     | 9.6666666666666666666666666666666666666666666666666666666667
29  | 0     | div_by_zero
31  | 11    | 2.8181818181818181818181818181818181818181818181818181818182
31  | 3     | 10.333333333333333333333333333333333333333333333333333333333
31  | 0     | div_by_zero

如果你不需要所有这些 GMP 的精度,那么 mawk 可以直接返回无穷大而不是致命错误信息:

for a in 19 29 31; do for b in 11 3 0; do 

mawk '$++NF=$++_/$(_+_--)' CONVFMT='%.19g' OFS='\t' <<<"$a $b";done;done
19  11  1.727272727272727293
19  3   6.333333333333333037
19  0   inf
29  11  2.636363636363636243
29  3   9.666666666666666075
29  0   inf
31  11  2.818181818181818343
31  3   10.33333333333333393
31  0   inf

或者更好的方法是,从一个单一的调用awk来完成它,而不是不停地调用它:
for a in 19 29 31; do for b in 11 3 0; do 

    echo "${a} ${b}"

done; done | mawk '$++NF = $(++_) / $(_+_--)' CONVFMT='%.19g' OFS='\t' 
19  11  1.727272727272727293
19  3   6.333333333333333037
19  0   inf
29  11  2.636363636363636243
29  3   9.666666666666666075
29  0   inf
31  11  2.818181818181818343
31  3   10.33333333333333393
31  0   inf

如果你喜欢的话,也可以让`mawk`间接调用`gawk-gmp`:
echo "22 7\n22 4\n22 0" | 

mawk '$++NF = substr(_=__="", (__="gawk -v PREC=65536 -Mbe"\
              " \47BEGIN { printf(\"%.127f\","(+(_=$(NF-!_))\
              ? "("($!__)")/("(_)")" : (+(_=$!__)<-_?__:"-") \
              "log(_<_)")") } \47" ) | getline _, close(__))_' 
22 7 3.1428571428571428571428571428571428571428571428571428………
22 4 5.5000000000000000000000000000000000000000000000000000………
22 0 +inf

0
继续对于2013年2月22日和2022年9月1日的回答进行扩展,这里也不需要使用echo。你还可以使用here strings (<<<)将数据输入到bc中。
bc -l <<< "$a / ( $b - 34 )"

这对于快速制作一句话有时很有用。
user@machine:~$ a=22; b=7; bc -l <<< "$a / ( $b - 34 )"
-.81481481481481481481

bc中添加scale非常容易。
user@machine:~$ a=22; b=7; c=2; bc <<< "scale=$c; $a / ( $b - 34 )"
-.81

这个“here string”是来自于bash而不是bc,所以它也适用于许多其他终端命令。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接