小U有一天想要使用shell脚本跑一段时间的脚本,写了下面一段shell语句,发现报错了,运行的时候变成了这样:
一开始以为是由于bash将0开头的数字作为8进制了,所以出现08,09这样的数字就会报错,之后发现是由于是用错了关键词和括号导致的。自此小U仿佛打开了新世界的大门,之前并没有系统了解过shell中的括号,经常出现混用的情况,今天我们就来总结一下shell中各种括号的使用方法和场景。
单小括号——( )
1) 命令组:
单小括号最常用的是作为命令组出现。括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
2) 初始化数组
其次小括号常被用于初始化数组。下图为初始化数组并对数组进行遍历输出。
3) 命令替换:
等同于 `cmd` ,shell扫描一遍命令行,发现了$(cmd)结构,便将$(cmd)中的cmd执行一次,得到其标准输出,再将此输出放到原来命令中。
双小括号——(( ))
1) 整数扩展:
这种扩展计算是整数型的计算,不支持浮点型。((exp))结构扩展并计算一个算术表达式的值,如果表达式的结果为0,那么返回的退出状态码为1,或者 是"假",而一个非零值的表达式所返回的退出状态码将为0,或者是"true"。若是逻辑判断,表达式exp为真则为1,假则为0。
2) 进制转化:
只要括号中的运算符、表达式符合C语言运算规则,都可用在$((exp))中,甚至是三目运算符。作不同进位(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成了十进制。
3) 重定义变量值
可以对变量进行重定义,下图对变量a执行了++操作。
4) 算术运算比较
双括号中的变量可以不使用$符号前缀。括号内支持多个表达式用逗号分开。只要括号中的表达式符合C语言运算规则,比如可以直接使用for((i=0;i<5;i++)),其他写法包括for i in `seq 0 4` 或者for i in {0..4}。再如可以直接使用if (($i<5)), 如果不使用双括号, 则为if [ $i -lt 5 ]。注意浮点数是作为字符串处理。
单中括号——[ ]
1) 相当于test
“[”和test是等同的,if/test结构中的左中括号是调用test的命令标识,右中括号是关闭条件判断的。这个命令把它的参数作为比较表达式或者作为文件测试(判断文件是否存在是否可执行等),并且根据比较的结果来返回一个退出状态码。
2) 整数和字符串的比较
[ ]中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq,-gt这种形式。不能直接使用大于小于号。对于字符串比较可以使用转义形式,如果比较"ab"和"bc":[ ab \< bc ],结果为真,也就是返回状态为0。[ ]中的逻辑与和逻辑或使用-a 和-o 表示。
3) 字符范围
使用起来类似用作正则表达式的一部分,描述一个匹配的字符范围。
4) 在数组中括号用来引用数组中每个元素的编号
注意:$[ expr ]和$(( expr ))是一样的,都是进行数学运算的。支持+ - * / %(“加、减、乘、除、取模”)。但是注意,bash只能作整数运算,对于浮点数是当作字符串处理的。
双中括号——[[ ]]
1)部分shell中的内置命令
这是内置在部分shell中的一个命令,它就比刚才说的test强大的多了。支持字符串的模式匹配(使用=~操作符时甚至支持shell的正则表达式)。逻辑组合可以不使用test的-a,-o而使用&& ||。
2)模式匹配
字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号。
3)防止脚本中的许多逻辑错误
使用[[ ... ]]条件判断结构,而不是[ ... ],能够防止脚本中的许多逻辑错误。比如,&&、||、 操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。比如可以直接使用if [[ $a != 1 && $a != 2 ]], 如果不适用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]或者if [ $a -ne 1 -a $a != 2 ]。
注意:使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!
大括号——{ }
1)大括号拓展
通配(globbing)对大括号中的文件名做扩展。在大括号中,不允许有空白,除非这个空白被引用或转义。第一种:对大括号中的以逗号分割的文件列表进行拓展。如 touch {a,b}.txt 结果为a.txt b.txt。第二种:对大括号中以点点(..)分割的顺序文件列表起拓展作用,如:touch {a..d}.txt 结果为a.txt b.txt c.txt d.txt
2)代码块(又被称为内部组)
这个结构事实上创建了一个匿名函数,与小括号中的命令不同,大括号内的命令不会新开一个子shell运行,即脚本余下部分仍可使用括号内变量。括号内的命令间用分号隔开,最后一个也必须有分号。{ }的第一个命令和左括号之间必须要有一个空格。下图中可以看到小括号括起来的部分只在对应子shell中作用,参数并没有传出来。
3)字符串提取和替换
类似python中的substr和replace方法。下图测试了几重常用的字符提取替换方法。
4)模匹配替换结构
${variable}用于变量替换。一般情况下,$var 与${var} 并没有什么不一样,但是用 ${ } 会比较精确的界定变量名称的范围。
假设我们定义了一个变量为:file=/dir1/dir2/dir3/my.file.txt,我们可以用 ${ } 分别替换获得不同的值:
总结
(…) 是命令列表。这些命令在单独的子过程中执行,因此在括号内执行的任何重定向,赋值等在括号外均无效。{ … }大括号类似于括号,但是在当前shell中执行,语法有所不同。
$(…)是命令的替换:括号内有一个命令,并且命令的输出用作命令行的一部分,等价于反引号。
((…))双括号将算术指令(对整数的计算)括起来,其语法类似于其他编程语言。此语法主要用于赋值和条件句中。
$((…))是算术表达式,该语法扩展为表达式计算后的整数值。
[ … ]单括号包围条件表达式。条件表达式主要建立在运算符上。不能直接用>
[[ … ]]双括号是部分shell中条件表达式的替代形式,具有一些附加功能,在对字符串进行比较判断的时候最好使用这个。
带有前导美元符号的${VAR}是参数扩展,扩展为变量的值,并可能进行额外的转换。
比较的时候慎用shell中的字符串比较,如果要使用的话使用bash中的[[ ]]更符合一般的编程习惯。
如果要使用选的日期,时间等,尽量将其转化为yyyymmdd形式或者秒数,通过整数的比较和计算之后再转回需要的形式,这样可以减少错误的发生。
往期推荐:
【UI分享】游戏交互提升技巧,想看吗?
【RD分享】正确处理CSRF校验
【UE分享】玩家吐槽“猪队友”时,到底在吐槽什么?
【UI分享】远程武器准星设计方法论
【US分享】如何实现shell脚本的单例运行