“究竟在干什么”是一系列关于软件开发过程中背后运作原理的文章,每一篇文章旨在讲解一些在日常编程实践中常见但可能并不为人所熟知的技术细节,抛砖引玉,期待激发读者朋友的更多思考。

如何在shell中比较大小?

如果在搜索引擎中搜索“shell 比较”,那么得到的结果基本上都在告诉你要写[ blablabla ]这样的代码

搜索“shell比较”的结果.jpg

例如,如果想知道当前的UNIX时间是否已经以16开头,可以用下列的shell代码

#!/bin/bash
ts=$(date '+%s')
if [ "${ts}" -gt 1600000000 -a "${ts}" -lt 1700000000 ]; then
    echo '当前的UNIX时间戳已经以16开头啦。'
else
    echo '当前的UNIX时间戳还没以16开头哦。'
fi

当我写这个的时候,date '+%s'的值为1587901648,所以运行后走的是else的分支。

除了用-gt表示大于之外,还有各种各样的其它比较运算符,例如下列的四个运算符

运算符 作用 示例代码
-ge 大于或等于 [ 2 -ge 1 ]
-eq 等于 [ 1 -eq 1 ]
-le 小于或等于 [ 2 -le 3 ]
-lt 小于 [ 3 -lt 4 ]

还有一些“测试”类型的运算符,例如

运算符 作用 示例代码
-b file 测试file是否存在并且是个块设备 [ -b /dev/disk0 ]
-c file 测试file是否存在并且是个字符设备 [ -c /dev/tty ]

[是shell的语法么?

大部分写shell代码的人或许会认为,[]是shell语言用于实现一系列的比较操作的特殊语法。但实际上,[]并不是一个语法——[是一个独立的命令行程序,]则什么都不是,仅仅是一个普通的字符。

在bash中使用which命令可以看到[的真面目

[是一个独立的程序,对,你没有看错(鲍尔默脸)。而且[有它自己的man文档

在man文档中出现了另外一个命令test,它和[的功能是一模一样的。或许test是一个“yet another [”?真相却更简单一点——test[是同一个东西

[源代码的二三事

可以在GitHub上找到[test源代码,代码很短,稍微读一下可以发现不少有意思的地方。

众所周知,如果在shell代码中使用[做比较运算,必须写上对应的右方括号]。但既然[是一个普通的外部程序,那么这个匹配括号的检查显然不会是shell来做的——没错,[自己会检查是否有写上相应的右方括号,这一段逻辑在源文件的main函数开始不久就出现了。

这个检查只有在程序被以[的名字启动的时候才会生效,所以test 1 -eq 1是不需要写括号的。

其实除了上文中给出的那些比较和测试运算符之外,[也支持复杂的逻辑运算表达式,比如文章开头的示例代码中的-a就是逻辑与的意思。在代码的注释中还贴心地给出了所接受的参数的BNF

而解析参数的过程则是一个手写的递归下降语法分析器,在源代码中可以找到与上面的产生式对应的多个函数:oexpraexprnexprprimary,以及binop

由于在shell语言中,0表示逻辑真,而1表示逻辑假(与C语言相反),所以在main函数中,如果发现传入的第一个参数为感叹号(!,表示逻辑取反),则将oexpr的调用结果直接返回,否则需要将结果取反后再从main函数中返回——给操作系统。

shell真的不原生支持比较?

尽管在bash中,[的确是作为一个外部程序存在的,但在zsh中却相反

而且,即使是bash也并非完全没有原生的比较操作——此处需要召唤[[[[是shell的保留字,它是一个less suprise版本的[,在Stack Overflow上有不少关于它的问答值得一看:

  1. https://stackoverflow.com/que...
  2. https://stackoverflow.com/que...

第二个链接的回答中还给出了一个值得一看的、关于bash中的“测试”功能的指引,其中甚至提到了

It can produce surprising results, especially for people starting shell scripting that think [ ] is part of the shell syntax.

后记

不得不承认,本文标题党了一把,shell还是自身就具备比较大小这样的功能的。

阅读原文


用户bPGfS
169 声望3.7k 粉丝