
Linux下做整数和浮点数运算看着是个基础话题但真到了写脚本、处理数据的时候坑一个接一个。我自己最早折腾Linux时以为有个python天下无敌后来发现很多场景根本不需要动用那么重的工具一个$(( ))或者bc管道就能解决而且速度飞快。但反过来说如果你只知道bash内建算术遇到0.1 0.2这种浮点需求时又会直接翻车。这篇文章我就把Linux下做数学运算的整套方案捋一遍从整数到浮点从命令到脚本把每个工具的边界、坑点、适用场景讲透。内容适合刚接触Linux的运维新手也适合写过几个脚本但总在数值计算上报错的人照着做基本不会再被加减乘除难住。1. 整数运算bash内建算术与expr的边界1.1 起步首选$(( ))和letLinux下做整数运算我最常用的就是bash内建的算术扩展$(( ))。它的好处显而易见不需要额外安装任何命令直接在脚本里写表达式就能出结果而且没有子进程开销跑大量循环时性能优势非常明显。基本用法很简单echo $((3 5)) # 输出 8 echo $((10 - 2 * 3)) # 输出 4注意乘除优先级 echo $((7 / 2)) # 输出 3不是 3.5 echo $((7 % 2)) # 输出 1取余数这里有个关键认知必须先建立bash内建算术只支持整数运算。7 / 2的结果是3而不是3.5因为bash会直接把小数部分截掉不会四舍五入更不会保留精度。这不是bug是设计如此——bash的算术器本质上是C语言风格的整数计算器凡是用到除法的地方你先要确认自己确实需要的是整除结果。let命令是另一个内建工具和$(( ))功能几乎相同但更多用在需要改变变量值的场景let a 5 let a 10 # a 变成 15 let a # a 变成 16 echo $alet的坑在于它对空格敏感表达式里不能乱加空格比如let a 5在某些环境下会报错。建议要么写let a 5加引号要么直接用$(( ))配合赋值写a$((5))。我在脚本里一般直接选$(( ))因为它的写法更接近现代编程语言运算符两侧加不加空格都能识别踩坑概率小。1.2 expr老牌命令的用法与坑提到Linux整数运算绕不开expr这个老牌外部命令。它从Unix诞生之初就存在所以你在很多旧脚本、面试题里都能看到它的影子。用法和bash内建算术不太一样核心特征是所有运算符之间必须有空格expr 3 5 # 输出 8 expr 10 - 4 # 输出 6 expr 3 \* 4 # 输出 12注意星号必须转义 expr 10 / 3 # 输出 3 expr 10 % 3 # 输出 1乘号必须写成\*这是新手最容易懵的地方。原因在于星号*在shell里是通配符会匹配当前目录下的所有文件名。如果你直接写expr 3 * 4shell会先把*展开成当前目录的文件列表然后expr拿到一堆乱七八糟的参数直接报expr: syntax error。加反斜杠转义等于告诉shell“这里不要展开”。expr还有一个用途判断场景它把表达式写在命令行参数里而不是像$(( ))那样整体计算。所以我个人对expr的态度是能用$(( ))就别用expr只有当你需要兼容极端老旧的sh环境或者读别人代码时必须理解它在干什么才需要仔细研究expr。新写的脚本里继续用expr除了增加出错概率没有任何好处。1.3 取整、取余与位运算等高级整数操作整数运算不只是加减乘除bash内建还提供了一整套C语言风格的运算符平时用得上的是取余、幂运算、位运算和自增自减。取余的场景很具体比如判断一个数是奇数还是偶数if [ $((number % 2)) -eq 0 ]; then echo 偶数 else echo 奇数 fi幂运算用的是**echo $((2 ** 10)) # 输出 1024位运算在权限判断、状态位检查里有用处echo $((8 3)) # 按位与输出 0 echo $((8 | 3)) # 按位或输出 11 echo $((1 4)) # 左移4位输出 16自增自减写循环时很顺手i0 while [ $i -lt 10 ]; do echo $((i)) # 注意i 整体算完再对 i 自增这里输出 0 done关于整数范围有个常见误解得纠正一下很多人以为bash整数是32位算超过21亿就会溢出。实际上几乎所有的64位Linux发行版上bash的算术都是64位有符号整数范围从-9223372036854775808到9223372036854775807。普通脚本里很难碰到这个边界但如果你在做时间戳换算纳秒级别、或者某种大数ID计算最好还是留个心眼超了范围会变成负数非常难排查。1.4 整数运算的进制转换与“非数字”处理$(( ))还有个杀招支持base#number语法可以灵活做进制转换。echo $((2#1010)) # 二进制1010输出 10 echo $((16#ff)) # 十六进制ff输出 255 echo $((8#17)) # 八进制17输出 15这个技巧在解析配置、处理权限位时非常实用。比如你想把八进制的755转成十进制权限值直接echo $((8#755))一下就出来。另一个容易被忽略的问题是变量为空或非数字时$(( ))的容错unset x echo $((x 1)) # 输出 1空变量被当作0这行为在脚本里算是个双刃剑。好处是很多场景你不会因为变量没定义直接崩溃坏处是如果你期望一个数字却拿到了空值计算结果会是0而不是报错掩盖了上游逻辑bug。我的习惯是凡是外部传入的数字在计算前先做一次校验用正则匹配一下是不是纯数字if [[ ! $num ~ ^[0-9]$ ]]; then echo 参数不是有效整数 2 exit 1 fi2. 浮点运算为什么bash本身做不了以及三大工具2.1 bash内建只支持整数的“原罪”回到开头的问题为什么echo $((1 / 3))输出0因为bash的算术引擎根本没有浮点类型它把除法当作整数除法处理一切小数直接截断。初学者最容易在这个地方怀疑人生明明在计算器上按1 ÷ 3 0.333...shell里怎么就成了0。这个限制的根源在于POSIX shell设计时就只要求整数算术bash算个扩展但也没打算引入浮点支持。如果你想算百分比、平均值、速率任何涉及小数的计算都必须借助外部工具。这里我把常用的浮点计算工具分成三档你可以根据场景选工具定位适合场景缺点bc高精度计算器需要控制小数位、进制转换、长度不定的数学表达式交互式操作略繁琐awk文本处理内置浮点从文本提取数字后直接聚合统计语法有门槛学习曲线高python3 -c通用语言计算复杂数学函数、科学计算、需要变量和函数的场景启动慢重工具杀鸡用牛刀2.2 bc最灵活的浮点计算器bc是我最推荐的浮点运算工具没有之一。它在几乎所有Linux发行版上都预装支持任意精度语法接近C语言还支持函数定义。基础用法直接管道传入表达式echo 3.14 2.86 | bc # 输出 6.00 echo 10 / 3 | bc # 输出 3默认完全不保留小数bc有个让人意外的默认行为整数除法的结果只输出整数部分必须设置scale变量才显示小数位echo scale3; 10 / 3 | bc # 输出 3.333 echo scale10; 10 / 3 | bc # 输出 3.3333333333这里的scale3表示保留3位小数注意bc做的是四舍五入还是截断取决于运算——加减乘是按精度的除法是截断式的。比如scale2; 10 / 3输出3.33不是3.34因为它直接截掉了多余小数。bc还支持变量、条件判断、函数但多数脚本用不上。真正频繁碰到的场景是进制转换echo obase16; 255 | bc # 输出 FF echo obase2; 255 | bc # 输出 11111111 echo ibase16; obase10; FF | bc # 输出 255obase是输出进制ibase是输入进制在主机地址换算、权限掩码计算里特别好用。bc还有一个容易被忽略的优势它支持幂运算和数学函数比如scale5; e(2)求自然对数底数的2次幂s(1)求正弦需要用到时直接查man bc即可。我在脚本中凡是遇到带幂次或长小数的浮点运算优先丢给bc。2.3 awk文本处理顺便算数awk是另一个处理浮点的神器它本身的文本处理能力太强了导致很多人忘了它的数值计算也很能打。当你有日志文件或命令输出里夹着一堆数字想要做累加、平均、比较awk往往一步到位不需要先掏bc再写循环。最简单的用法echo 8.5 2.3 | awk {print $1 $2} # 输出 10.8 echo 8.5 2.3 | awk {printf %.2f\n, $1 / $2} # 输出 3.70printf格式化输出是awk的精髓%.2f表示保留两位小数\n换行。在写脚本统计时我经常对一批数值做求和cat data.txt | awk {sum $1} END {print sum}比如data.txt里每一行都有个时间耗时数字这个命令就能把总和算出来。要是想算平均再加个计数器cat data.txt | awk {sum $1; count} END {printf avg%.3f\n, sum/count}awk浮点用的是系统双精度浮点数绝大多数场景精度足够。需要警惕的是在极端大数或需要高精度定点运算的金融场景下双精度可能引入误差这时还是改用bc或python更保险。2.4 其他选择dc、python3、perldc是bc的“爸爸”用逆波兰表达式就是计算器那种先数字后运算符的流程echo 10 3 / p | dc # 输出 3 echo 10 3 / 2 k p | dc # 保留2位小数输出 3.33dc的语法反直觉为什么要提它因为有些精简容器镜像里只有dc没有bc你遇到时至少要知道它是干嘛的。平时真没几个人用dc写脚本能用bc就不碰dc。python3是另一大杀器在系统装了python3的前提下用python3 -c一行命令做浮点计算支持所有标准数学函数python3 -c import math; print(math.sqrt(2)) python3 -c print(0.1 0.2) python3 -c print(round(3.14159, 2))python的优势是生态丰富三角函数、对数、随机数、复数全都有语法比bc友好太多。缺点是命令启动时间大概几十毫秒循环里调用一万次就多出几秒所以计算量大且对速度敏感的脚本我不建议用python做内层循环计算更适合用awk或bc。3. 实战场景从脚本变量到批量计算的完整案例3.1 场景一计算CPU使用率系统监控脚本这个场景会用到整数和浮点混合运算特别适合展示各个工具怎么配合。Linux系统里CPU使用率不是直接读出来的要从/proc/stat的第一行拿四个时间值算差值cpu_line$(head -1 /proc/stat) user$(echo $cpu_line | awk {print $2}) nice$(echo $cpu_line | awk {print $3}) system$(echo $cpu_line | awk {print $4}) idle$(echo $cpu_line | awk {print $5}) total1$((user nice system idle)) idle1$idle sleep 1 cpu_line$(head -1 /proc/stat) user$(echo $cpu_line | awk {print $2}) nice$(echo $cpu_line | awk {print $3}) system$(echo $cpu_line | awk {print $4}) idle$(echo $cpu_line | awk {print $5}) total2$((user nice system idle)) idle2$idle # 浮动和繁忙时间差 total_diff$((total2 - total1)) idle_diff$((idle2 - idle1)) # 用bc算百分比保留两位小数 cpu_usage$(echo scale2; (100 * ($total_diff - $idle_diff) / $total_diff) | bc) echo CPU使用率: ${cpu_usage}%这里total1这些变量都是整数用$(( ))直接算百分比最后用bc转成浮点因为如果全程用整数运算100 * 差值 / 总差值很有可能直接变成0尤其在短时间采样、差值小的情况下全靠bc保留小数精度。3.2 场景二批量重命名时生成序号并对齐批量处理文件时经常要生成带固定位数的序号比如把img1.jpg、img2.jpg改成img001.jpg。整数运算加printf格式化可以优雅解决for i in {1..100}; do newname$(printf img%03d.jpg $i) echo mv img${i}.jpg $newname done%03d是printf的格式化输出表示至少占3位不足用0补。这个套路在日志切分、图片序号、任务编号场景里非常常见关键是printf不是只能输出字符串它天生就是格式化数字的能手比手写$(( ))补零逻辑省太多事。3.3 场景三日志文件数值聚合统计假设有个access.log每行末尾是请求耗时毫秒数想统计平均耗时、最大耗时awk {sum $NF; if ($NF max) max $NF; count} END {printf 请求数%d 平均耗时%.2fms 最大耗时%dms\n, count, sum/count, max} access.log$NF是最后一个字段sum做累加END块在整个文件处理完后执行。这里awk的浮点除法sum/count直接得到小数再用printf格式化。如果换成纯bash逐行读取再算不仅代码长浮点还要调bc效率差很多。文本里的数字统计我向来一句话awk搞定。3.4 场景四大整数加减乘除运算前面说bash支持64位整数但某些场景——比如做GitHub上的bkcrack暴力破解密钥或RSA相关的计算——64位根本不够用需要真正的大整数运算。这类需求直接上python3就对了python3 -c print(2 ** 100) # 输出 1267650600228229401496703205376python的整数是任意精度的要多长有多长乘法、加法都不溢出。用bc也行echo 2^100 | bc同样能算出大数但处理复杂逻辑时python显然更顺手。如果你在Linux面试题里碰到“如何做超大整数乘法”答案不是bash内建而是用bc或python这类支持大整数的工具。3.5 场景五内存或磁盘容量换算Linux下很多命令默认以字节为单位输出但人看着费劲。比如拿到ls -l文件字节数想转成MBfile_size536870912 # 536870912 字节 echo scale2; $file_size / (1024 * 1024) | bc # 输出 512.00这里必须用bc因为bash整数除法会把536870912 / 1048576截成整数虽然恰好能整除但换成其他大小你就没法得到带小数的精确容量。顺便提一句如果只是想看人类可读格式很多命令自带-h选项比如du -h、ls -lh不需要自己写计算只有在脚本内部处理时才需要自己算。4. 常见问题与排查技巧实录4.1 syntax error in expression (error token)这是$(( ))和let最常见的报错。我遇过几种典型原因变量为空或不是数字xabc echo $((x 1)) # 报错: syntax error in expression (error token is abc)表达式里混入了非法字符比如在$(( ))里用了%但没配对或者运算符写错。定位方法很简单用set -x打开bash的调试追踪看它到底把表达式扩展成了什么参数。大多数情况下是引号没加或者变量内容带了个空格、换行符肉眼看不见$(( ))却会直接炸。4.2 expr乘号忘记转义expr 3 * 4报错不是我吓唬人shell会把*展开成当前目录下所有文件名命令变成expr 3 a.txt b.txt 4之类的结果惨不忍睹。解决方式前面说过写expr 3 \* 4。不过我还是要说这类古老的坑在新项目里不该出现表达式稍微复杂时直接$(( ))或bc更省心。4.3 bc的scale设置位置不少人把scale2写在命令末尾结果发现输出还是整数echo 10 / 3; scale2 | bc # 输出 3scale2必须放在表达式之前因为bc是顺序执行的语言计算到10 / 3时scale还没设置所以按默认0处理。正确写法echo scale2; 10 / 3 | bc这个顺序问题在bc里非常容易踩我自己刚开始也栽过一次后来总结成一句话scale就是bc的前置设置先声明精度再写表达式。4.4 浮点数比较的精度陷阱在awk或python里直接比较浮点数要小心。比如awk BEGIN {print (0.1 0.2) 0.3} # 输出 0表示不相等这很反直觉但和所有使用IEEE双精度的语言一样0.1 0.2在二进制浮点里无法精确表示结果是0.30000000000000004。如果你在脚本里做条件判断别直接比较浮点相等改不了就用区间判断awk BEGIN {diff (0.1 0.2) - 0.3; if (diff 0.000001 diff -0.000001) print equal}或者干脆用bc指定scale后再比较字符串这个坑能绕开一半。4.5 bash环境差异sh、dash与POSIX写脚本时如果#!/bin/sh开头某些语法就不一定支持了。比如dashDebian系默认sh不支持$(( ))里的、**这些bash扩展特性只支持最基础的加减乘除。如果你写的脚本要跨发行版迁移最保险的做法是脚本第一行写#!/bin/bash明确告诉系统用bash解释。看到报错bad substitution或者not found时先检查是不是shell环境不对。4.6 命令替换时别忘了引号用echo $(cat file | awk {print $1})这类嵌套时如果文件路径带空格或特殊字符很容易出幺蛾子。我处理这类问题的原则是所有可能含空格的变量一律用双引号包起来result$(echo scale2; $base / $total | bc) echo 结果: ${result}如果$base或$total没赋值bc会当作0处理输出可能也是0这和bash内建空变量当0是同样的坑。别嫌啰嗦加一层校验赋值能省掉很多调试时间。4.7 常见问题速查表问题现象排查思路syntax error in expression$(( ))报错检查变量是否为空、是否为纯数字、是否含非法字符expr乘法结果不对或报错expr 3 * 4报错加转义\*或改用$(( ))bc输出是整数echo 10 / 3bc输出3awk比较浮点相等输出00.1 0.2 0.3为假改用bc或做误差区间判断脚本换机器后运算报错原本正常换环境出错确认解释器是bash还是sh检查是否用了**、等扩展负数计算异常$((-1 5))有时报错部分旧版本bash要写$((-1 5))即数字与括号间加空格新版本一般没事处理大数结果溢出成负数乘法超过64位范围改用bc或python35. 一些我长期使用的脚本技巧前面讲了原理和工具最后分享几个我写脚本时长期在用的组合技巧这些细节普通教程不太会提但对实际效率影响很大。技巧一用bc把百分比计算封装成函数脚本里要多次计算百分比时我会在脚本顶部定义一个函数calc_percent() { echo scale2; ($1 / $2) * 100 | bc } usage$(calc_percent 3567 4096) echo 磁盘使用率: ${usage}%这样每次调用时不用重复写scale2也不容易忘记顺序代码干净很多。技巧二awk处理多字段统计当文本每一行有多个数字维度时awk的内置循环和字段拆分是无敌的。比如统计某个监控文件里每列的平均值awk {for (i1; iNF; i) sum[i] $i; count} END {for (i1; iNF; i) printf col%d avg%.2f , i, sum[i]/count; print } stats.txt这段代码对列数不敏感加多少列都能统计非常灵活。技巧三循环里优先用awk/bc聚合别在bash里逐行调命令假设有100万行数字要求平均值逐行跑bc会开出100万个子进程慢到怀疑人生。正确做法是把整个文件交给awk一次算完awk {sum $1} END {print sum/NR} nums.txtNR是awk内建的行数变量连计数器都不用自己写。这属于“最优实践”不仅是代码美观在性能上完全是量级差距——一条awk命令跑几秒bash循环可能要几分钟甚至更久。技巧四printf对齐数字输出输出表格型结果时数字对齐很重要不一定好看但方便批量处理。用printf %10s右对齐、%-10s左对齐加百分号时注意转义printf %-10s %10.2f\n 内存 75.3 printf %-10s %10.2f\n 磁盘 46.8这种输出风格在生成文本报告、邮件通知时特别实用。我在实际运维和开发工作中数不清多少次被各种数值计算的边缘情况卡住。说句掏心窝的话Linux下的数学运算并不是“难”而是工具太多、每个工具的边界不一样。整数小计算用$(( ))高精度浮点用bc文本流里的批量统计优先看awk大整数和复杂数学函数再请python3出山。把这几个工具各自的适用边界记清楚写脚本时就会少走很多弯路。最后再补充一个老生常谈但极其重要的小建议任何脚本里的计算结果尽量在输出时用printf或固定格式统一格式化避免不同工具生成的数字精度不一致导致下游比较或拼接时出问题。把环境适应好模式固定下来Linux下的数字运算其实是一件非常爽快的事情。