ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Shell、Makefile、CMake 中 lt、gt、eq 等比较运算符详解

Shell、Makefile、CMake 中 lt、gt、eq 等比较运算符详解 1. 六个比较运算符的真实身份从 lt 到 gt 的完整拆解第一次看到lt、le、eq、ne、ge、gt这六个缩写很多人会以为是某种加密口令或者内部代号。其实它们就是英文单词的缩写专门用来做数值或字符串的大小比较。这套命名规则最早来自 Unix 的test命令和expr表达式后来被 Shell、Makefile、CMake、SQL、汇编指令集、模板引擎等大量场景沿用成了跨语言的“通用暗号”。先把结论摆出来方便你对照记忆缩写英文全称中文含义数学符号ltless than小于leless than or equal小于等于≤eqequal等于nenot equal不等于≠gegreater than or equal大于等于≥gtgreater than大于这六个词解决的核心问题只有一个在不能直接写数学符号的环境里如何表达比较逻辑。比如在 Shell 脚本里和会被解释成重定向符号直接写if [ $a $b ]会报错甚至产生意外文件在 Makefile 里是命令行输出重定向在 HTML/XML 里和是标签定界符必须转义成lt;和gt;。所以这套字母缩写不是“多此一举”而是被环境逼出来的刚需。适合阅读这篇内容的人大致分三类一是刚接触 Shell 脚本或 Makefile 的新手被-lt、-gt这类写法卡住二是做数据库或模板开发需要写条件判断但记不清缩写含义三是想系统梳理一遍比较运算符体系避免在跨语言切换时混淆。下面我会按“为什么这样设计 → 各场景怎么用 → 实操踩坑 → 问题排查”的顺序展开每个场景都给出可直接复制的代码。2. 为什么不用数学符号设计逻辑与场景选型2.1 符号冲突才是根本原因很多人第一反应是“直接写不就行了”。问题在于计算机世界里符号是稀缺资源同一个字符在不同上下文有完全不同的含义。Shell 里表示标准输入重定向表示标准输出重定向表示追加重定向。你写if [ $a $b ]Shell 会先把它当成“把$b的输出重定向到文件$a”然后[命令收到的参数就乱了判断结果完全不可控。Makefile 更典型。Makefile 里用于把命令输出写入文件用于读取文件作为输入。如果比较表达式里直接用符号Make 解析器会先做重定向处理逻辑判断根本轮不到执行。所以 Makefile 提供了ifeq、ifneq、ifdef、ifndef这套条件语法其中eq和ne就是从这里来的。HTML 和 XML 则是另一条线。和是标签的起止符正文里要显示“小于”必须写成lt;显示“大于”写成gt;。这也是为什么你在网页源码里经常看到lt;这种实体它和 Shell 里的lt本质是同一个思路用字母替代符号绕开解析器的特殊处理。2.2 各场景的选型对照不同环境对这套缩写的支持程度不一样我整理了一张对照表方便你按需查阅使用场景支持形式典型写法注意事项Shell test-lt -le -eq -ne -ge -gt[ $a -lt $b ]数值比较必须带短横线Shell 字符串 ! [ $a $b ]字符串比较用符号注意加引号Makefileifeq ifneqifeq ($(A),$(B))只有等于和不等于CMakeLESS LESS_EQUAL EQUAL GREATER_EQUAL GREATERif(A LESS B)全大写单词不是缩写SQL ! WHERE a b直接用符号不用缩写汇编lt le eq ne ge gtblt label分支指令后缀模板引擎lt le eq ne ge gt{% if a lt b %}各引擎略有差异从表里能看出一个规律越是底层、越是和文本解析强相关的环境越倾向于用字母缩写。Shell 和 Makefile 是典型代表因为它们的历史包袱重符号早被占用了。而 SQL 这种专门为查询设计的语言符号就是它的原生语法反而不用缩写。2.3 记忆技巧词根拆解这六个词其实有很强的构词规律抓住词根就不用死记l开头都是“小于”方向ltless than、leless than or equalg开头都是“大于”方向gtgreater than、gegreater than or equaleq是 equal 的缩写ne是 not equal 的缩写带e结尾的le、ge、ne都表示“或等于”或“否定”lt、gt是纯方向我个人的记法是l 小 g 大e 结尾带等号n 开头是取反。这套口诀在 Shell、汇编、模板引擎里通用切换语言时不用重新背。3. Shell 脚本里的六种比较数值与字符串必须分清3.1 数值比较的标准写法Shell 里做数值比较必须用-lt、-le、-eq、-ne、-ge、-gt这六个带短横线的操作符。注意短横线不能省省了就变成字符串比较或者命令名了。#!/bin/bash a10 b20 if [ $a -lt $b ]; then echo $a 小于 $b fi if [ $a -le $b ]; then echo $a 小于等于 $b fi if [ $a -eq $b ]; then echo $a 等于 $b else echo $a 不等于 $b fi if [ $a -ne $b ]; then echo $a 不等于 $b fi if [ $a -ge $b ]; then echo $a 大于等于 $b else echo $a 小于 $b fi if [ $a -gt $b ]; then echo $a 大于 $b else echo $a 不大于 $b fi这段脚本跑下来输出会是“10 小于 20”“10 小于等于 20”“10 不等于 20”“10 小于 20”“10 不大于 20”。逻辑很直白但有几个细节必须注意。第一[和]两边必须有空格。[$a -lt $b]这种写法会报command not found因为 Shell 把[$a当成一个整体命令名了。这是新手最常踩的坑没有之一。第二变量最好加双引号。虽然数值比较时$a是数字加不加引号影响不大但养成习惯能避免空变量导致的语法错误。如果a是空值[ -lt $b ]会直接报错而[ $a -lt $b ]至少能给出更清晰的错误提示。第三-eq和不是一回事。-eq用于数值比较用于字符串比较。[ 10 -eq 010 ]返回真因为数值上都是 10但[ 10 010 ]返回假因为字符串不相等。这个差异在解析用户输入时特别容易出问题。3.2 字符串比较的符号写法字符串比较不用缩写直接用、!、、。但和在[ ]里需要转义或者改用[[ ]]。#!/bin/bash s1apple s2banana if [ $s1 $s2 ]; then echo 相等 else echo 不相等 fi if [ $s1 ! $s2 ]; then echo 不相等 fi # 字符串大小比较推荐用 [[ ]] if [[ $s1 $s2 ]]; then echo $s1 排在 $s2 前面 fi if [[ $s1 $s2 ]]; then echo $s1 排在 $s2 后面 fi[[ ]]是 Bash 的增强版测试语法支持模式匹配和正则而且不用转义和。如果你确定脚本只在 Bash 下跑优先用[[ ]]。如果要求 POSIX 兼容比如要在 dash 或 busybox 下跑那就只能用[ ]加转义。提示字符串比较时变量一定要加双引号。[ $s1 $s2 ]在变量为空时会变成[ ]直接语法错误。加引号后变成[ ]至少能正常返回结果。3.3 数值比较的替代方案双括号Bash 还提供了(( ))做算术比较可以直接写数学符号可读性更高a10 b20 if (( a b )); then echo a 小于 b fi if (( a b )); then echo a 大于等于 b else echo a 小于 b fi(( ))里可以直接用、、、!、、不用缩写也不用转义。代价是它只支持整数运算不支持浮点数而且不是 POSIX 标准。做纯数值判断时我个人更倾向用(( ))写起来清爽读起来也直观。但如果是写给别人维护的脚本或者需要兼容老环境还是用-lt这套更稳妥。4. Makefile 与 CMake构建系统里的条件判断4.1 Makefile 的 ifeq 与 ifneqMakefile 只提供了等于和不等于两种判断对应ifeq和ifneq。没有lt、gt这套因为 Make 的条件主要用于判断变量是否定义、是否等于某个值很少做数值大小比较。MODE release ifeq ($(MODE),release) CFLAGS -O2 else CFLAGS -g endif ifneq ($(MODE),debug) $(info 当前不是调试模式) endififeq的语法有几种写法ifeq ($(A),$(B))是最常见的。注意括号和逗号的位置写错了 Make 会直接报missing separator。另外ifeq和ifneq后面必须跟endif闭合嵌套时每个if都要有对应的endif否则解析会乱。如果确实需要在 Makefile 里做数值比较通常得借助shell函数调用外部命令COUNT : $(shell echo 5) ifeq ($(shell test $(COUNT) -gt 3 echo yes),yes) $(info COUNT 大于 3) endif这种写法能跑但可读性差而且每次执行都会 fork 一个 shell 进程。我的建议是Makefile 里尽量只做字符串和定义判断数值比较挪到脚本里做。构建系统不是用来做复杂逻辑的硬塞进去只会让维护成本飙升。4.2 CMake 的全大写单词CMake 用的是全大写单词不是缩写CMake 操作符含义LESS小于LESS_EQUAL小于等于EQUAL等于NOT_EQUAL不等于GREATER_EQUAL大于等于GREATER大于set(A 10) set(B 20) if(A LESS B) message(STATUS A 小于 B) endif() if(A GREATER_EQUAL B) message(STATUS A 大于等于 B) else() message(STATUS A 小于 B) endif()CMake 的if语法比较特殊变量名直接写不用加${}。if(A LESS B)里 A 和 B 会被自动解引用。如果你写成if(${A} LESS ${B})在变量为空时会变成if( LESS )直接语法错误。这是 CMake 新手最容易搞混的地方。另外 CMake 的EQUAL对数值和字符串都适用但数值比较时建议确保两边都是数字否则会按字符串规则比较10会小于9因为字符1的编码小于9。5. 汇编与模板引擎底层与上层的两种面孔5.1 汇编里的条件分支在 ARM 汇编里lt、le、eq、ne、ge、gt是条件码后缀直接跟在指令后面表示“满足条件才执行”。比如blt是“小于则跳转”bgt是“大于则跳转”。cmp r0, r1 ; 比较 r0 和 r1 blt label_less ; 如果 r0 r1跳转到 label_less ble label_le ; 如果 r0 r1跳转 beq label_eq ; 如果 r0 r1跳转 bne label_ne ; 如果 r0 ! r1跳转 bge label_ge ; 如果 r0 r1跳转 bgt label_gt ; 如果 r0 r1跳转这套后缀和 Shell 的缩写完全一致因为都源自同一套英文缩写传统。区别在于汇编里它们是指令的一部分不是独立的操作符。cmp指令负责比较并设置标志位后面的分支指令根据标志位决定是否跳转。理解这个“先比较、后分支”的模型对看懂汇编控制流很关键。x86 汇编用的是另一套jljump if less、jle、je、jne、jge、jg。j是 jump后面跟的条件码和 ARM 基本对应只是lt变成了lgt变成了g。记住这套对应关系跨架构看汇编时能省不少查文档的时间。5.2 模板引擎里的比较很多模板引擎为了避免和 HTML 标签冲突也采用这套缩写。以 Jinja2 为例{% if a lt b %} a 小于 b {% elif a eq b %} a 等于 b {% else %} a 大于 b {% endif %}Jinja2 同时支持lt和但在 HTML 模板里容易和标签混淆所以官方文档更推荐用lt这套。Django 模板、Twig、Liquid 等引擎也都有类似支持具体写法略有差异用之前查一下对应文档。模板引擎里做比较时有个常见陷阱变量类型不确定。从后端传过来的可能是字符串10也可能是整数10。10 lt 9在字符串比较下返回真因为字符1小于9但数值上 10 大于 9。所以模板里做数值比较前最好在后端就转成数字类型别指望模板引擎帮你做类型推断。6. 常见问题与排查技巧实录6.1 高频报错速查表报错信息原因解决方法command not found: [10[后没加空格写成[ $a -lt $b ]integer expression expected用-lt比较了非数字检查变量值或改用字符串比较unary operator expected变量为空导致参数缺失变量加双引号missing separatorMakefile 用了空格缩进命令行必须用 Tab 缩进syntax error near unexpected token[[ ]]里用了未转义的特殊字符检查引号和转义if(A LESS B)不生效CMake 变量没解引用或类型不对确认变量是数字直接写变量名6.2 三个我踩过的坑第一个坑-eq比较带前导零的数字。有次处理日期输入用户填了08脚本里用[ $month -eq 8 ]判断结果报value too great for base。原因是 Shell 把08当成八进制数而八进制里没有 8。解决办法是加10#前缀强制十进制[ $((10#$month)) -eq 8 ]。这个坑在月份、日期处理里特别常见因为01到09都会触发。第二个坑浮点数比较。Shell 的-lt这套只支持整数[ 1.5 -lt 2 ]直接报错。要比较浮点数得用bc或awkif [ $(echo 1.5 2 | bc) -eq 1 ]; then echo 1.5 小于 2 fi或者用awk BEGIN{exit !(1.5 2)}。我一般用bc因为写法更接近普通比较可读性好一些。第三个坑Makefile 里ifeq的空格。ifeq ($(A), $(B))和ifeq ($(A),$(B))结果可能不同因为逗号后的空格会被当成值的一部分。如果B是release写成ifeq ($(A), release)实际比较的是 release永远不相等。这个坑很隐蔽因为 Makefile 不会报错只是条件永远不成立。我的习惯是逗号后绝不加空格所有ifeq都写成紧凑形式。6.3 跨语言切换的防混淆清单同时用多种语言时最容易搞混的就是比较运算符。我给自己列了一张速查卡Shell 数值-lt -le -eq -ne -ge -gt带短横线Shell 字符串 ! 用符号Bash 算术 ! 双括号里用符号Makefile只有ifeq和ifneqCMakeLESS LESS_EQUAL EQUAL NOT_EQUAL GREATER_EQUAL GREATERSQL直接用符号不用缩写汇编lt le eq ne ge gt作指令后缀每次切换语言前扫一眼这张卡能避免大部分低级错误。特别是 Shell 和 Bash 算术之间同一个脚本里两种写法混用很常见写的时候要清楚当前在哪个上下文。7. 一套可直接复用的比较函数库如果你经常写 Shell 脚本建议把这六个比较封装成函数调用时更直观也避免记错操作符#!/bin/bash num_lt() { [ $1 -lt $2 ]; } num_le() { [ $1 -le $2 ]; } num_eq() { [ $1 -eq $2 ]; } num_ne() { [ $1 -ne $2 ]; } num_ge() { [ $1 -ge $2 ]; } num_gt() { [ $1 -gt $2 ]; } # 使用示例 if num_gt 10 5; then echo 10 大于 5 fi if num_le 3 3; then echo 3 小于等于 3 fi这样写的好处是函数名本身就是英文含义读代码时不用在脑子里做缩写翻译。而且函数内部统一处理了引号和操作符调用处更干净。代价是多了一层函数调用开销但对于普通脚本来说完全可以忽略。如果要做浮点数比较可以再加一组float_lt() { [ $(echo $1 $2 | bc) -eq 1 ]; } float_gt() { [ $(echo $1 $2 | bc) -eq 1 ]; } float_eq() { [ $(echo $1 $2 | bc) -eq 1 ]; }这套函数库我用了好几年在各种运维脚本、构建脚本里都跑得很稳。唯一要注意的是bc不是所有环境都预装嵌入式系统里可能得换成awk。8. 从缩写看计算机的命名哲学这六个缩写能在这么多领域存活几十年背后其实有一套命名哲学在符号受限的环境里用最短的字母组合表达最明确的语义。lt只有两个字母但任何懂英文的人看一眼就能猜到是 less than。这种“压缩但不失可读性”的平衡是 Unix 工具链设计的一贯风格。对比一下其他命名方式Java 的Comparator用compareTo返回负数、零、正数表示大小关系更灵活但不够直观Python 的operator.lt直接用了同样的缩写说明这套命名确实经得起时间考验。你在设计自己的 API 或配置语法时如果也面临符号冲突问题不妨参考这个思路用目标用户最熟悉的英文缩写而不是自创一套符号。最后分享一个我常用的验证方法遇到不确定的比较写法先在命令行里用最简单的值试一下比如[ 1 -lt 2 ] echo ok。确认语法通过后再套用到实际脚本里。这个习惯帮我省了大量调试时间尤其是跨语言切换的时候花十秒钟验证一下比写完一整段再排查要高效得多。
返回列表