Shell中各种括号的作用和区别

2019-10-15

Shell中各种括号的作用和区别,单小括号() 和 双小括号(())、 单中括号[] 和 双中括号[[]]、 大括号、花括号 {}。

对各自功能和使用方式,做个整理学习。

Shell中各种括号的作用和区别

参考: shell 中各种括号的作用()、(())、[]、[[]]、{}

单小括号() 和 双小括号(())

  1. 单小括号()
    • 命令组
      • 括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用,多个命令分号隔开 (cmd1;cmd2;cmd3)
    • 命令替换
      • 等同于cmd,shell扫描一遍命令行,发现了$(cmd)结构,便将$(cmd)中的cmd执行一次,得到其标准输出,再将此输出放到原来命令。 e.g. 获取pid+3赋值给a, a=$((3+$(pidof crond))); echo $a
    • 用于初始化数组
      • 如:array=(a b c d)
1
2
3
4
5
6
7
8
        arr=(1 2 3)
        echo $arr          # 并打不出数组所有成员,只会打印1
        i=0
        while(( $i <= 3 )) # i需要先定义
        do
            echo ${arr[i]} # 此处需要用{},用()和不用任何括号都不行
            ((i++))
        done
  1. 双小括号(())
    • 整数扩展
      • 不支持浮点型,((exp))结构扩展并计算一个算术表达式的值
    • 只要括号中的运算符、表达式符合C语言运算规则,都可用在$((exp))中,甚至是三目运算符。 i=4; a=$((i>4?9:10)); echo $a, 作不同进位(如二进制、八进制、十六进制)运算时,输出结果全都自动转化成了十进制。如:echo $((16#5f)) 结果为95 (16进位转十进制)
    • 单纯用 (( )) 也可重定义变量值,比如 a=5; ((a++)) 可将 $a 重定义为6
    • 常用于算术运算比较,双括号中的变量可以不使用$符号前缀。 括号内支持多个表达式用逗号分开。 只要括号中的表达式符合C语言运算规则
1
2
3
4
5
6
7
8
9
    比如可以直接使用for((i=0;i<5;i++)),
    如果不使用双括号, 则为
    for i in `seq 0 4` 或者 for i in {0..4}。

    再如可以直接使用if (($i<5)), 或者 if ((i<5))
    如果不使用双括号, 则为if [ $i -lt 5 ]a=3
    if ((i<5, a++)), 执行完后,a=4

单中括号[] 和 双中括号[[]]

  1. 单中括号[]
    • bash 的内部命令,[和test是等同的。 新版的Bash中要求必须有]匹配。
    • Test和[]中可用的比较运算符只有==和!=,两者都是用于字符串比较的
      • 不可用于整数比较,整数比较只能使用-eq,-gt这种形式。
      • 无论是字符串比较还是整数比较都不支持大于号小于号。 如果实在想用,对于字符串比较可以使用转义形式,如果比较”ab”和”bc”:[ ab \< bc ],结果为真,也就是返回状态为0。
      • [ ]中的逻辑与和逻辑或使用-a 和-o 表示。([]内部用,各[]之间可以&&)
    • 字符范围。用作正则表达式的一部分,描述一个匹配的字符范围 e.g. [0-3]作为test用途的中括号内不能使用正则
    • 在一个array 结构的上下文中,中括号用来引用数组中每个元素的编号。 e.g. ${arr[i]}
  2. 双中括号[[]]
    • [[是 bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。
      • 在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。
    • 支持字符串的模式匹配,使用=~操作符时甚至支持shell的正则表达式。
      • 比如[[ hello == hell? ]],结果为真。[[ ]] 中匹配字符串或通配符,不需要引号(也不能用,用引号时当做引号字符)。
    • 使用[[ … ]]条件判断结构,而不是[ … ],能够防止脚本中的许多逻辑错误。
      • 比如,&&、||、<和>操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。
      • 比如可以直接使用if [[ $a != 1 && $a != 2 ]], 如果不使用双括号, 则为if [ $a -ne 1] && [ $a != 2 ]或者if [ $a -ne 1 -a $a != 2 ]
1
2
3
4
5
6
7
8
9
10
    if ($i<5)                       # 报错 没有那个文件或目录 ()中当做命令,<为重定向,执行报错
    if [ $i -lt 5 ]
    if [ $a -ne 1 -a $a != 2 ]      # 且, -ne比较数字,!=用于字符串比较
    if [ $a -ne 1] && [ $a != 2 ]   # 同[ -a ]
    if [[ $a != 1 && $a != 2 ]]     # [[]] 内部可以使用&&

    for i in $(seq 0 4);do echo $i;done    # $()相当于``
    for i in `seq 0 4`;do echo $i;done     #
    for ((i=0;i<5;i++));do echo $i;done    # (()) 可以使用符合C语言运算规则的表达式
    for i in {0..4};do echo $i;done        #

大括号、花括号 {}

  1. 大括号拓展。(通配(globbing))将对大括号中的文件名做扩展。 在大括号中,不允许有空白,除非这个空白被引用或转义。
    • 第一种:对大括号中的以逗号分割的文件列表进行拓展。touch {a,b}.txt 结果为a.txt b.txt {a,b}之间不能有空格,有空格会变成 “{a,” 和 “b}.txt”两个文件
    • 第二种:对大括号中以点点(..)分割的顺序文件列表起拓展作用 touch {a..d}.txt 结果为a.txt b.txt c.txt d.txt
  2. 代码块,又被称为内部组,这个结构事实上创建了一个匿名函数 。
    • 与小括号中的命令不同,大括号内的命令不会新开一个子shell运行,即脚本余下部分仍可使用括号内变量。
    • 括号内的命令间用分号隔开,最后一个也必须有分号。{ cmd1;cmd2;cmd3;} (注意与但括号的区别(cmd1;cmd2;cmd3))
    • {}的第一个命令和左括号之间必须要有一个空格。
  3. 符号$后的括号
    • ${a} 变量a的值, 在不引起歧义的情况下可以省略大括号
    • $(cmd) 命令替换,和cmd效果相同,结果为shell命令cmd的输出
    • $((expression)) 和exprexpression效果相同, 计算数学表达式exp的数值, 其中exp只要符合C语言的运算规则即可, 甚至三目运算符和逻辑表达式都可以计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
几种特殊的替换结构
    ${var:-string}
    若变量var为空,则用在命令行中用string来替换${var:-string},否则变量var不为空时,则用变量var的值来替换${var:-string};

    ${var:+string}
    ${var:+string}的替换规则和上面的相反,即只有当var不是空的时候才替换成string,若var为空时则不替换或者说是替换成变量 var的值,即空值。

    ${var:=string}
    对于${var:=string}的替换规则和${var:-string}是一样的,所不同之处是${var:=string}若var为空时,用string替换${var:=string}的同时,把string赋给变量var

    ${var:?string}
    若变量var不为空,则用变量var的值来替换${var:?string};若变量var为空,则把string输出到标准错误中,并从脚本中退出。我们可利用此特性来检查是否设置了变量的值。

四种模式匹配替换结构(#和%中的单一符号是最小匹配,两个相同符号是最大匹配。)
    ${var%pattern}
    这种模式时,shell在var中查找,看它是否以给的模式pattern结尾,如果是,就从命令行把var中的内容去掉右边最短的匹配模式

    ${var%%pattern
    这种模式时,shell在var中查找,看它是否以给的模式pattern结尾,如果是,就从命令行把var中的内容去掉右边最长的匹配模式
    ${var#pattern}
    这种模式时,shell在var中查找,看它是否以给的模式pattern开始,如果是,就从命令行把var中的内容去掉左边最短的匹配模式

    ${var##pattern}
    这种模式时,shell在var中查找,看它是否以给的模式pattern开始,如果是,就从命令行把var中的内容去掉左边最短的匹配模式

    这四种模式中都不会改变variable的值

再次说明

部分参考: shell if [[ ]]和[ ]区别 || &&

  • []和test,两者是一样的,在命令行里test expr和[ expr ]的效果相同。

    test中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较(>、<),整数比较只能使用-eq, -gt这种形式。

    无论是字符串比较还是整数比较都千万不要使用大于号小于号。当然,如果你实在想用也是可以的,对于字符串比较可以使用尖括号的转义形式, 如果比较”ab”和”bc”:[ ab \< bc ],结果为真,也就是返回状态为0.

  • [[ ]]

    比test强大,支持字符串的模式匹配

    字符串比较时可以把右边的作为一个模式 (这是右边的字符串不加双引号的情况下。如果右边的字符串加了双引号,则认为是一个文本字符串。)

    注意:使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    字符串比较

    =   等于,如:if [ "$a" = "$b" ]
    ==  等于,如:if [ "$a" == "$b" ],与=等价

       注意:==的功能在[[]]和[]中的行为是不同的,如下:
       1 [[ $a == z* ]]    # 如果$a以"z"开头(模式匹配)那么将为true
       2 [[ $a == "z*" ]]  # 如果$a等于z*(字符匹配),那么结果为true
       3
       4 [ $a == z* ]      # File globbing(通配、文件名替换) 和word splitting(单词分割)将会发生(不会如预期通配符一样起作用)
       5 [ "$a" == "z*" ]  # 如果$a等于z*(字符匹配),那么结果为true
       一点解释,关于File globbing是一种关于文件的速记法,比如"*.c"就是,再如~也是.
       但是file globbing并不是严格的正则表达式,虽然绝大多数情况下结构比较像.

       (将*/?/[]这些shell元字符扩展为文件名的过程就称作globbing,通配)
  • let和(())

    两者也是一样的(或者说基本上是一样的,双括号比let稍弱一些)。

    主要进行算术运算,也比较适合进行整数比较(可直接使用>、<)

    可以直接使用变量名如var而不需要$var这样的形式。

tips:

  1. 首先,尽管很相似,但是从概念上讲,二者是不同层次的东西。

    ”[[“,是关键字,许多shell(如ash bsh)并不支持这种方式。

    ”[“是一条命令, 与test等价,大多数shell都支持。

  2. [[]]结构比Bash版本的[]更通用。

    用[[ … ]]测试结构比用[ … ]更能防止脚本里的许多逻辑错误。

    1
    
     &&,||,<和>操作符能在一个[[]]测试里通过,但在[]结构会发生错误。
    
  3. [ … ]为shell命令,所以在其中的表达式应是它的命令行参数,

    所以串比较操作符”>” 与”<”必须转义,否则就变成IO改向操作符了(比较"ab"和"bc":[ ab \< bc ])。

    在[[中”<”与”>”不需转义;

在bash中,数字的比较最好使用 (( )),虽说可以使用 [[ ]],但若在其内使用运算符 >、>=、<、<=、==、!= 时,其结果经常是错误的,

不过若在 [[ ]] 中使用 [ ] 中的运算符“-eq、-ne、-le、-lt、-gt、-ge”等,还尚未发现有错。

因此诸如[[ " a" != “b” && 4 > 3 ]] 这类组合(见上)也不可以在bash中使用,其出错率很高。

例:[[ "a" != "b" && 10 > 2 ]] 判断结果就不正常, 走的是false。

[[ "a" != "b" && 10 -gt 2 ]]是true, [ "a" != "b" -a 10 -gt 2 ]是true

诸如 [ 2 < 10 ]、[[ 2 < 10 ]] 都是不要使用。使用算术扩展最好用 (( 99+1 == 100 )) ,而不要使用[[ 99+1 -eq 100 ]] 。


  • 本文作者: xiaodongQ
  • 本文链接: /2019/10/15/shell-bracket-usage/
  • 版权声明: 本文可以自由拷贝,转载,请注明出处,谢谢! 🤝

Comments