• shell 基本语法


    shell 基本语法

    jenkins 上构建项目时,经常需要借助 shell 脚本,最近也经常跟服务器打交道,顺便记录些常用命令,方便查阅

    语法-变量

    # 定义变量
    name='dasu'
    
    # 使用变量
    echo $name  # dasu
    echo "I am ${name}."  # I am dasu.
    

    xxx='dasu'

    key=value 形式定义变量,= 等号两边不能有空格

    $xxx 或 ${xxx}

    变量名前加个 $ 使用变量,大括号省略也可以

    语法-字符串

    # 字符串使用
    name='dasu'
    name2="dasu"
    name3=dasu
    echo "$name $name2 $name3"  # dasu dasu dasu
    
    # 字符串长度
    echo ${#name} #4
    
    # 注意,shell 里都是命令
    'dasu' # dasu: command not found
    
    # 获取子字符串
    echo ${name:0:2} # da
    
    # 寻找字符
    echo `expr index $name s` # 3   下标从1开始
    

    'dasu' "dasu" dasu

    单引号双引号、甚至不加引号都会被作为字符串使用

    单引号里的字符串不做任何处理工作,是什么就原样输出

    双引号里如果有表达式、有转义符,有变量,会先进行处理,最后再输出,所以字符串的拼接,可以放在双引号内

    注意,shell 里都是命令,所以只有当在命令参数、或表达式右值时,字符串才会被当做字符串处理,否则都会被认为命令,从而报找不到 xxx 命令错误

    ${#xxx}

    加个 # 号使用,可以用来获取 xxx 变量字符串的长度

    ${xxx:1:2}

    用来截取子字符串

    i=`expr index "$xxx" x`

    用来查找子字符,expr 表示后面跟着的是表达式,因为 shell 默认每一行都是命令,所以本身不支持表达式

    index 用来查找,后面跟着接收两个参数:原字符串,查找的字符

    注意,只找字符,不是找子字符串

    `xxx`$(xxx)

    因为不加引号也可以被认为是字符串处理,所以在某些场景,需要让脚本解释器知道,这是一串命令,而不是字符串,此时就需要通过 ` 反引号,或者 $() 来实现

    echo ls  # ls,被认为是字符串
    echo `ls` # 执行 ls 命令,并将结果输出
    echo $(ls) # 执行 ls 命令,并将结果输出
    

    ` 反引号内部是一个命令,$() 美元符合加小括号形式,括号内也是表示一个命令

    注意,`$() 内部的命令执行之后的结果,会再次作为输入,被当做下一行 shell 脚本命令执行,所以需要注意这个结果是否可以作为命令被执行

    `whoami` # root: command not found
    

    因为 whoami 命令执行输出 root,root 又被作为命令执行,就报错了

    如果有需求是要将命令执行结果,作为日志输出,这种场景就很适用了

    语法-表达式

    编程语言都可以通过各种运算符来实现一个个表达式,如算术表达式、赋值表达式等

    但由于在 shell 内,都被当做命令来处理,所以正常的运算符无法直接使用,需要借助其他命令或语法实现

    expr

    a=2 + 2   # +: command not found
    a=2+2     # 2+2被认为字符串了
    a=`expr 2 + 2`  # a=4
    

    ` 反引号内的会被当做一个命令来执行,因为上面例子是将 expr 命令放在 = 号右侧,如果不加反引号,expr 会被当做字符串处理

    有些算术运算符需要加转义符,如乘号 *,大于 >,小于 < 等

    算术运算符跟两侧的变量基本都需要以空格隔开,这样才能辨别是字符串还是表达式

    expr 2 + 2  # 4,加法运算
    expr 2+2    # 2+2,整个被当做字符串处理
    expr 2 - 2  # 0,减法运算
    expr 2 * 2 # 4,乘法运算,需要加转义
    expr 2 / 2  # 1,除法运算
    expr 2 % 2  # 0,取余运算
    
    expr 2 > 1 # 1,比较运算,需要加转义
    expr 2 < 1 # 0,比较运算,需要加转义
    expr 2 == 2 # 1
    

    (()) 和 $(())

    (()) 双括号内,可以执行表达式,多个表达式之间以 , 逗号隔开,最后一个表达式会被作为 (()) 运算的结果,可以通过在前面加个 $ 提取结果

    echo $((a=2+2,$a+2))  # 6
    echo $a   # 4
    ((b=$a*2)) # 8, * 号不用加转义符
    

    (()) 和 expr 有各自优缺点:

    • (()) 支持语句,即形如 ((a=2+2)),但 expr 只支持表达式,expr 2 + 2
    • (()) 里的乘号,大于号等不需要加转义符,expr 需要加转义符
    • (()) 只支持整数的运算,不支持字符串、小数的计算,expr 支持
    • 等等其他未遇到的场景

    $[]

    简单的算术表达式还有一种写法:

    a=$[2+2]  # a=4
    a=$[2*2]  # a=4,不需要加转义符
    

    跟 expr 相比,$[] 好处就是一些运算符无需加转义符

    $[]$(()) 很像,一样支持语句,一样支持多个表达式,通过 , 逗号隔开,一样会将最后一个表达式的值返回,但 $[] 前的 $ 符合不能省略

    注意:关于 $[]$(()) 的理解可能不是很正确,基本没用过,只是在看资料时遇到,顺手测了些数据梳理出来的知识点,以后有使用到,发现错误的地方再来修改。

    而且,目前碰到的 shell 脚本的需求场景,更多的是参数的获取、变量的使用,因为需要动态生成命令来执行,这种场景比较多,关于表达式运算的场景比较少,所以先不必过多关注。

    语法-条件判断 if

    if 的语法:

    if condition 
    then
      command
      ...
    elif condition; then
      command
      ...
    else  
    fi
    

    如果想让 then 和 if 同行,那么需要用 ; 分号隔开,同理,fi 如果想跟 else 或 then 同行,也需要 ; 分号隔开,否则会有语法错误

    if 的本质其实是检测命令的退出状态,虽然我们经常可以看到这种写法:

    if [ 2 -eq 2 ]
    if [[ 2 == 1 ]]
    if (( 1 == 1 ))
    

    以上三种,不管是中括号,双中括号,双小括号,其本质都是在运行数学计算命令,既然是命令,就都会有命令的退出状态

    命令退出状态有两种,0 是正常,非 0 是异常,同时,可以用 $? 来获取上个命令的执行退出状态,所以可以来试试看:

    [ 2 -eq 2 ]
    echo $?  # 0,正常
    
    [ 2 == 1 ]
    echo $?  # 1,非正常
    
    [[ abc == abc ]]
    echo $?  # 0,正常
    
    [[ ab == abc ]]
    echo $?  # 1,非正常
    
    (( 1 == 1 && 1 > 0 ))
    echo $?  # 0,正常
    
    (( 1 == 1 && 1 > 1 ))
    echo $?  # 1,非正常
    

    明白了吗?

    其实, if 检测的是命令的退出状态,这也就意味着,if 后面跟随着的 condition 只要是命令就是符合语法的,不必像其他编程语言那样,必须是类似 if () 这种语法结构,这也就是为什么,你可能看到别人写的很奇怪的 if 代码,比如:

    if test 1 -eq 1; then echo true; fi  # true
    if whoami; then echo true; fi # root true
    

    这样一来,即使再看到别人写的 if 代码很奇葩,至少你也知道,它的执行原理是啥了吧,至少也能看懂他那代码的意图了吧

    好,虽然清楚了 if 检测的本质其实是命令的退出状态,但最好还是使用良好的编程风格,使用阅读性较好的写法

    关系运算符 -eq -ne -gt -lt -ge -le

    • 等价于 == != > < >= <=

    这些运算符只能用于比较数值类型的数据,且只能用于 [][[]] 这两种(()) 不能使用这种运算符。

    但使用 [][[]] 这种语法形式时,有个很重要的点,就是中括号内部两侧必须有空格,然后运算符两侧也需要有空格,否则可能就不是预期的行为了:

    if [ 1 -eq 1 ]; then echo true; else echo false; fi  # true 
    if [ 1-eq2 ]; then echo true; else echo false; fi  # true,因为 1-eq2 被当做字符串了,运算符左右需要有空格
    if [ 1==2 ]; then echo true; else echo false; fi # true,因为 1==2 被当做字符串了,运算符左右需要有空格
    

    [][[]] 内部既可以用类似 -eq 这种形式,也可以直接使用 == 这种方式,后者可以用于比较字符串,前者不能

    布尔运算符 ! -o -a

    • 分别对应:非运算,或运算,与运算
    if [ 1 -eq 1 -a 1 -gt 1 ]; then echo true; else echo false; fi # false
    if [ 1 -eq 1 -o 1 -gt 1 ]; then echo true; else echo false; fi # true
    

    这些运算符只能适用于 [],且只能跟关系运算符(-eq, -ne ...)使用

    [[]] 以及 (()) 都不能使用,且如果类似这样使用 ==-o,也是不起作用的:

    if [ 1 > 2 -a 1 == 1 ]; then echo true; else echo false; fi # true,1 > 2 明明不符合,却返回 true 了,所以 -a 这种运算符不能喝 > 这类运算符合用,但使用 -gt 就是正常的了
    
    if [[ 1 -eq 1 -o 1 -gt 2 ]]; then echo true; else echo false; fi 
    #sh: syntax error in conditional expression
    #sh: syntax error near `-o'
    #异常,[[]] 不支持使用布尔运算符
    

    逻辑运算符 && ||

    • 逻辑的 AND 和逻辑的 OR
    if [[ 1 == 1 && 1 > 2 ]]; then echo true; else echo false; fi # false
    

    这种运算符只能适用于 [[]],此时不管是使用 == 这类运算符,还是 -eq 这类,都是允许的

    [](()) 都不适用

    当需要有嵌套的判断时,可以拆开,比如:

    if [[ 1 == 1 ]] && [[ 1 > 3 || 1 > 0 ]]; then echo true; else echo false; fi # true
    # 相当于 if ((1==1) && ((1>3)||(1>0)))
    

    字符运算符 = != -z -n $

    • = != 用于判断字符串是否相等
    • -z 用于判断字符串长度是否为 0,是的话,返回 true
    • -n 用于判断字符串长度是否为 0,不是的话,返回 true
    • $xxx 用于判断 xxx 字符串是否为空,不为空返回 true
    a='abc'
    if [ $a == absc ]; then echo true; else echo false; fi  # true 
    if [ -n $a ]; then echo true; else echo false; fi  # true ,因为长度不为0
    if [ -z $a ]; then echo true; else echo false; fi  # false,因为长度不为0
    if [ $a ]; then echo true; else echo false; fi  # true 
    

    这种运算符适用于 [][[]] 这两种,不适用于 (())

    文件测试运算符 -d -r -w -x -s -e

    • -f 检测文件是否是普通文件(既不是目录,也不是设备文件)

    • -r 检测文件是否可读

    • -w 检测文件是否可写

    • -x 检测文件是否可执行

    • -s 检测文件是否为空

    • -e 检测文件是否存在

    • -d 检测文件是否是目录

    a=test.sh
    if [ -e $a ]; then echo true; else echo false; fi # 检测 test.sh 文件是否存在
    if [ -d $a ]; then echo true; else echo false; fi # 检测 test.sh 是否存在且是否是目录
    

    这类运算符适用于 [][[]] 这两种,不适用于 (())

    涉及计算的判断条件

    大部分场景下,if 的条件判断,使用上述的运算符结合 [[]] 使用就可以了,但有某些场景,比如先进行算术运算之后,再判断结果:

    if ((1+1>2)); then echo true; else echo false; fi # false
    

    如果想使用 [[]] 实现,可以是可以,但有些麻烦:

    if [[ $[1+1] > 2 ]]; then echo true; else echo false; fi # false
    

    就是需要先让 1+1 当做表达式计算结束,并获取结果,然后再来做判断

    (()) 有一点需要注意,它只能进行整数运算,不能对小数或字符串进行运算

    小结

    脚本中使用到 if 条件判断的场景肯定也很多,绝大多数情况下,使用 [[]] 就足够覆盖需求场景了

    不管是需要对文件的(目录、存在、大小)判断,还是需要对字符串或命令执行结果的判断,使用 [[]] 都可以实现了

    其实,[[]] 可以说是 [] 的强化版,后者能办到的,前者都行,而对于 (()),更多是整数运算表达式的使用场景,拿来结合 if 使用,纯粹是因为刚好遇见而已,并不是专门给 if 设计的,毕竟 if 只检测命令执行结果,只要是命令,都可以跟它搭

    语法-函数和参数

    • 函调定义
    function add() {
    	// ...
    }
    
    # 省略 function 关键字
    add(){
    	echo $*
        echo ${12}
    	return 1
    }
    
    • 函数调用
    add 1 2 #sh 1 2
    

    函数调用时,直接函数名即可,如果需要参数,跟其他编程语言不同,定义时不能指明参数,而是函数内部直接通过 $n 来获取参数,需要第几个,n 就是第几

    函数调用时,当需要传参时,直接跟在函数名后面,以空格隔开,函数名不需要带括号

    参数 $n $0 $* $#

    读取参数,参数可以是执行脚本时传递的参数,也可以是执行函数时传递的参数

    • $1 表示第一个参数,以此类推

    • ${10} 当参数个数超过 9 个后,需要用大括号来获取

    • $*$@ 输出所有参数

    • $0 输出脚本文件名

    • $# 输出参数个数

    所以,脚本内部开始,可以用 echo $0 $* 来输出外部使用该脚本时,传递的参数

    语法-脚本文件的 source 和执行

    当前 shell 脚本内,可以导入其他脚本文件,也可以直接执行其他脚本文件

    source

    当某个脚本被其他脚本导入时,其实相当于从其他文件拷贝脚本代码过来当前脚本环境内执行,导入有两种命令:

    . filename # 注意点号 . 和文件名中间有空格
    
    #或者
    
    source filename
    

    被导入的脚本文件不需要是可执行类型的,毕竟执行环境还是当前脚本启动的 shell 进程,只是执行的代码无需再写一遍,直接从其他地方拷贝过来一条条执行而已

    执行

    在当前脚本内,也可以直接执行其他脚本文件,同样有两种类型,如:

    sh ./test.sh
    echo $?  # 脚本执行的退出状态
    
    #或者
    
    ./test.sh
    

    两种的区别就在于:

    • 前者不需要被执行的脚本是可执行类型的,因为已经手动指定 sh 来作为脚本解释器了,脚本内部开头的 #! 声明也会失效掉
    • 后者的话,纯粹就是执行一个可执行文件的方式,那就需要这个脚本文件是可执行类型的,同时脚本的解释器由脚本文件内部开头的 #! 声明

    我们通常都会将不同工作职责写在不同脚本文件中,然后某个脚本文件内,来控制其他脚本文件的执行流程,那么,这时候,就需要知道每个流程的脚本是否执行正常,这时候,就可以借助脚本的 exit 命令和 $? 来实现

    每个脚本,如果正常执行结束,那么脚本内部最后应该通过 exit 0 来退出,表示当前脚本正常执行,如果执行过程出异常了,那么应该执行 exit 1 只要是非 0 即可,来表示当前脚本执行异常

    那么,调用执行这个脚本的,就可以通过 $? 来获取脚本执行结果,如:

    sh ./test.sh
    if [[ $? -ne 0 ]]; then
      echo '异常'
      exit 1
    fi
    

    这样就可以来控制脚本执行流程

    语法-其他

    注释

    • #xxxx

    单个 # 用来注释后面内容

    #!/bin/sh

    脚本文件的顶行,告诉系统,应该去哪里用哪个解释器执行该脚本;

    但如果该脚本不是直接执行,而是作为参数传递给某个解释器,如:

    /bin/sh xxx.sh,那,文件顶头的 #! 声明就会被忽视,毕竟已经明确指定解释器了

    for 循环

    for loop in 1 3 4 5 6
    do
    
    done
    

    $?

    用来获取上个命令的执行之后的退出状态,或者获取上个函数执行的返回值,0 表示正常,非0 表示不正常

    所以,脚本如期结束时,脚本内最后应该 exit 0 来退出命令(每个脚本的执行其实就是执行命令)

    read xxx

    从标准输入中读取一行,并赋值给 xxx 变量

    printf

    输出格式化

    Shell printf 命令

    输入输出

    默认的输入输出都是终端,但可通过 > < 来进行修改,比如

    • ls > file

    将输出写入到文件中,覆盖写入

    • ls >> file

    将输出写入到文件中,追加写入

    • xxx.sh < file

    本来是从键盘输入到终端,转移到从文件读取内容

    • <<EOF
    xxx.sh<<EOF
    ....
    EOF
    

    将两个 EOF 之间的内容作为输入

    • ls > /dev/null

    如果希望执行某个命令,但又不希望在屏幕上显示,那么可以将输出重定向到 /dev/null

    写入 /dev/null 中的内容会被丢弃

    语法-易混淆

    有些语法很容易混淆,在这里列一列:

    ${} 和 $[] 和 $() 和 $(())

    name=dasu
    echo ${name}  # dasu,变量的使用
    
    echo $[2+2]       # 4,执行算术表达式,可认为作用跟 expr 类似,但两者有各自局限,expr 支持字符串的关系运算等
    echo `expr 2 + 2` # 4
    
    echo $((2+2))     # 4,执行整数的算术表达式,可认为作用跟 expr 类似,但两者有各自局限,expr 支持字符串的关系运算等
    echo `expr 2 + 2` # 4
    
    echo $(whoami)    # root,执行命令,可认为作用跟 `` 反引号类似
    echo `whoami`     # root
    

    虽然 $ 后面可以跟随各种各样符号,来实现不同用途,但其实,都可以归纳为 $ 的作用是,提取后面的结果,然后将其作为输入,再次让 shell 解释器处理。

    比如说 ${xxx},就是将读取变量 xxx 的值,然后输入给解释器:

    name=dasu
    ${name} # dasu: command not found
    echo ${name} dasu
    

    是吧,就是提取,然后再输入给解释器,其实也就是变量值的替换,将变量替换为实际的值

    那么,这么理解的话,() 小括号内的其实就是在执行命令,$() 就是将命令执行结果替换命令;(()) 两个小括号内的其实就是在执行表达式,$(()) 就是将表达式执行结果替换掉表达式;$[] 同理;

    那么,可能你就会有疑问了:

    [1+1] # [1+1]: command not found
    ((1+1)) # 无报错也无输出
    

    知道为什么吗?

    因为 (()) 是 shell 解释器可以识别的语法,它知道这不是字符串

    [1+1] 却被解释器当做一整个字符串了,自然就找不到这个命令,shell 解释器能识别的 [] 语法应该是,中括号内部两侧需要有空格,此时就不会认为它是字符串了,如:

    [ 1+1 ] # 无报错也无输出
    

    当有 $ 时,就无需区分字符串的场景了,自然也就可以省略掉空格了,但保留好习惯,都留着空格也是很好的做法

    命令和表达式

    • 命令是指 shell 支持的命令,比如 ls,pwd,whoami 等等
    • 表达式是指通过运算符组合成的各种表达式,如算术表达式,赋值表达式,关系表达式等等

    shell 内的每一行代码都是在执行命令,所以直接在 shell 内书写表达式是会执行异常,因为表达式不是命令

    一些命令跟传入参数,如 echo xxx,echo 后跟随着会被当做字符串处理,如果想让 xxx 这串被作为命令执行,那需要将 xxx 放置于 `xxx` 反引号或者 $(xxx)

    如果想让 xxx 被当做表达式处理,则需要借助一些命令,如 expr;

    如果表达式是算术表达式,那可通过 ((xxx)) 包裹这些表达式,但需要获取表达式结果时,通过 $((xxx)) 在前面加个 $ 实现


    本篇就先介绍一些基础语法吧,当然并不全面,但足够看懂基本的 shell 脚本代码了

    下一篇会介绍一些常用命令,如 expect,scp,ssh,以及再拿个 jenkins 上构建项目的实例脚本来讲讲

  • 相关阅读:
    bzoj 2001 CITY 城市建设 cdq分治
    CodeChef
    CodeForces 293E Close Vertices 点分治
    CodeForces 161D Distance in Tree 树上点分治
    POJ-2104 K-th Number CDQ分治
    CodeForces 669 E Little Artem and Time Machine CDQ分治
    BZOJ 1935 园丁的烦恼
    关于dijkstra的优化 及 多源最短路
    nyoj1000_快速幂_费马小定理
    Common Knowledge_快速幂
  • 原文地址:https://www.cnblogs.com/dasusu/p/11919086.html
Copyright © 2020-2023  润新知