“究竟在幹什麼”是一系列關於軟件開發過程中背後運作原理的文章,每一篇文章旨在講解一些在日常編程實踐中常見但可能並不為人所熟知的技術細節,拋磚引玉,期待激發讀者朋友的更多思考。
如何在 shell 中比較大小?
如果在搜索引擎中搜索“shell 比較”,那麼得到的結果基本上都在告訴你要寫[ blablabla ]這樣的代碼
例如,如果想知道當前的 UNIX 時間是否已經以16開頭,可以用下列的 shell 代碼
<code>#!/bin/bash
ts=$(date '+%s')
if [ "${ts}" -gt 1600000000 -a "${ts}" -lt 1700000000 ]; then
echo '當前的UNIX時間戳已經以16開頭啦。'
else
echo '當前的UNIX時間戳還沒以16開頭哦。'
fi/<code>
當我寫這個的時候,date '+%s'的值為1587901648,所以運行後走的是else的分支。
[是 shell 的語法麼?
大部分寫 shell 代碼的人或許會認為,[]是 shell 語言用於實現一系列的比較操作的特殊語法。但實際上,[]並不是一個語法——[是一個獨立的命令行程序,]則什麼都不是,僅僅是一個普通的字符。
在 bash 中使用which命令可以看到[的真面目
[是一個獨立的程序,對,你沒有看錯(鮑爾默臉)。而且[有它自己的 man 文檔
在 man 文檔中出現了另外一個命令test,它和[的功能是一模一樣的。或許test是一個“yet another [”?真相卻更簡單一點——test和[是同一個東西
[源代碼的二三事
可以在 GitHub 上找到[和test的源代碼[1],代碼很短,稍微讀一下可以發現不少有意思的地方。
眾所周知,如果在 shell 代碼中使用[做比較運算,必須寫上對應的右方括號]。但既然[是一個普通的外部程序,那麼這個匹配括號的檢查顯然不會是 shell 來做的——沒錯,[自己會檢查是否有寫上相應的右方括號,這一段邏輯在源文件的main函數開始不久就出現了。
這個檢查只有在程序被以[的名字啟動的時候才會生效,所以test 1 -eq 1是不需要寫括號的。
其實除了上文中給出的那些比較和測試運算符之外,[也支持複雜的邏輯運算表達式,比如文章開頭的示例代碼中的-a就是邏輯與的意思。在代碼的註釋中還貼心地給出了所接受的參數的 BNF
而解析參數的過程則是一個手寫的遞歸下降語法分析器,在源代碼中可以找到與上面的產生式對應的多個函數:oexpr、aexpr、nexpr、primary,以及binop。
由於在 shell 語言中,0 表示邏輯真,而 1 表示邏輯假(與 C 語言相反),所以在main函數中,如果發現傳入的第一個參數為感嘆號(!,表示邏輯取反),則將oexpr的調用結果直接返回,否則需要將結果取反後再從main函數中返回——給操作系統。
shell 真的不原生支持比較?
儘管在 bash 中,[的確是作為一個外部程序存在的,但在 zsh 中卻相反
而且,即使是 bash 也並非完全沒有原生的比較操作——此處需要召喚[[。[[是 shell 的保留字,它是一個less suprise版本的[,在 Stack Overflow 上有不少關於它的問答值得一看:
- https://stackoverflow.com/questions/3427872/whats-the-difference-between-and-in-bash
- https://stackoverflow.com/questions/669452/is-double-square-brackets-preferable-over-single-square-brackets-in-ba
第二個鏈接的回答中還給出了一個值得一看的、關於 bash 中的“測試”功能的指引[2],其中甚至提到了
It can produce surprising results, especially for people starting shell>
後記
不得不承認,本文標題黨了一把,shell 還是自身就具備比較大小這樣的功能的。
[1]
和test`的[源代碼: https://github.com/freebsd/freebsd/blob/master/bin/test/test.c
[2]
指引: http://mywiki.wooledge.org/BashGuide/Practices#Bash_Tests
閱讀更多 小碼農Liutos醬 的文章