shell被设计出来是为了方便的执行系统调用的。见大宽宽:Shell 是用来解决什么问题的?www.zhihu.com

所以其基本形式是:

...

命令和参数之间用分隔符(比如空格)来分隔。当然实际上语法有很多扩充,比如执行命令前的环境变量,多个命令可以用管道串联,重定向,后台执行等等。

如果说一个编程语言可以编译成汇编,那么shell脚本就得“编译“成这种命令的形式,并且不能与这种“命令语法”相冲突。在这个前提之下,可以尽量搞看起来像逻辑表达式的语法,但总会有很别扭的地方,比如

if [ -d $dir ]; then

里面的[就是test命令,后边-d之类的都是test命令的参数。但把test改成[之后看起来有点表达式的意思,但本质却没有变。但你不能去掉[后的空格,因为`[`之所以被看成是一个命令是因为bash的规则是先要用分隔符切分。没空格就无法区分命令和参数了。于是只能接受这种不太自然的语法。

顺便提一下很多DSL都有这种问题,底层必须得满足宿主语言的一些规则,然后就不那么“自然”。比如接触过某门编写测试用例的语言,一定要用4个空格来代表某种特定的分隔语义。以至于我们后来都会戏谑测试同学“四个空格”(然后他们叫我们六个核桃,嗯)。

再看看一般编程语言,做法不一样,比如

if (a==1) { // ...}

在这个java语法里,(,a,==,1先被词法分析器解析成不同的token,再分析语法做语法树。因为先能区别不同的token,就没必要再根据某个特定分隔符切分了。所以在(和a,a和==之间加多少空格都无所谓。

之所以编程语言能够这样做,是因为编程语言不同种类的token的规则是受到限制的。比如变量名只能是"字母数字下化线,且不能以数字开头“;字符串必须用""括起来操作符只能是+、-,>,=……这些符号。这是编程语言的词法规则。因此在parsefoo=1时,parse完了foo,发现=不符合变量名规则,自然就可以停了。

而bash脚本里面实际上是可执行文件的文件名和参数,而文件名和参数可以用数字开头,可以用各种符号(试试touch %)。所以作为bash选择用分隔符分隔。分隔出来的东西,再根据位置和关键字判断每个字符串到底是什么,如何处理。

这就是两种不同设计目标得到的截然不同的语法规则。

回到题主问到的,a=1,这样的形式是bash等一开始就设计好了用于赋值的语法。

如果写成了

a = 1

就会被看成a这个命令带两个参数=和1

如果是

a= 1

则会被看成是在执行1这个命令之前先设定环境变量a,变量的值是空。

如果是

a =1

那就是执行命令a,带参数=1.

这些都是写死在bash整个体系里的,不可能更改。

如果觉得不爽,可以改用python,perl,ruby,tcl等写。这些工具都是在shell还处于早期时不成熟或者压根就没有的东西,但现在已经很普及了。但即便如此,每门语言都有自己的问题,比如python的2和3的不兼容(现在好点了,3普及多了),以及很多人吐槽的缩进;perl的奇葩$和魔幻的语法(我更不习惯这个)等等。

而且还要担心,登录到一台机器上后,这些语言是不是安装了?安装的版本能否支持脚本的语法(也许脚本会用新一点的语法写,但机器上只有老的运行时)。如果没有,运维肯不肯安装和维护?(不光是语言本身,还有各种包,比如pip install xxx)机器能不能直接连外网然后apt-get?如果写个启动的脚本,则要求目标机器一定得安装对应的语言?这些都是麻烦事。

对比这些问题,也许咬咬牙写1个月的bash,也就习惯了。个人觉得这笔投资很值啊。