美文网首页
Linux Shell编程学习笔记

Linux Shell编程学习笔记

作者: 我是老薛 | 来源:发表于2019-04-28 20:58 被阅读0次

    本文是对Linux shell编程的一个学习比较,共包括如下章节的内容:

    • 概述
    • 基本语法
    • 结构化语句
    • 函数
    • 小结

    参考资料:一些常见的linux bash命令可参见《Linux常见Shell命令》

    一、概述

    (一)基本概念

    Linux shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行。

    shell既是一种解释器,也是一种编程语言,我们除了可以在命令行下执行单条的shell命令外,如 echo , ls , cpd等命令。我们还可以编写shell程序,shell程序是一种脚本语言,同其它脚本语言一样,shell程序支持变量、结构化语句等特性,我们可以将代码保存到脚本文件中,后续调用执行该脚本。

    linux的shell和linux版本一样,有很多种,常见的如:

    • Bourne Shell(/usr/bin/sh或/bin/sh)
    • Bourne Again Shell(/bin/bash)
    • C Shell(/usr/bin/csh)
    • K Shell(/usr/bin/ksh)
    • Shell for Root(/sbin/sh)

    各种shell支持的命令和编程语言语法是类似的,但也有些细微的差别。这些shell类型中,其中最常用的是bash(即Bourne Again Shell),bash因为易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的Shell。

    当我们在Linux系统本机或远程打开一个linux控制台后,会自动运行默认的shell程序,出现交互式界面,我们可以通过echo $0 来查看当前运行的是哪种shell。如下面例子:

    $ echo $0
    -bash
    

    上面例子显示,我们使用的是bash类型。

    在本文,我们介绍的是bash。

    (二)shell脚本

    下面我们来看下如何编写和执行一个shell脚本文件。

    1、新建一个文本文件

    文件名可以是任意合法的文件名,扩展名习惯为sh,当然也可以没有扩展名。我们这里假设文件名为test.sh。

    文件内容如下:

    #!/bin/bash
    echo hello
    

    上面例子的脚本文件就两行,第一行是个特殊语句,#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell,我们这里是使用bash。所有的shell脚本都必须有这一行内容。

    后面的内容就可以是命令了,我们这里只有一行命令,即echo命令。这样执行该脚本,会在控制台输出hello内容。

    2、设置可执行权限

    脚本要想当作可执行文件被运行,需要设置权限,如下方式:

    chmod +x ./test.sh
    

    使用chmod命令使test.sh文件变为可执行程序。

    3、执行

    在shell提示符下运行 ./test.sh ,就可以执行该脚本。

    在bash脚本中,除#!这个外,所有#后面的内容都会被当作注释内容,执行时会被忽略,如下面脚本:

    #!/bin/bash
    echo hello #输出hello信息
    #定义shell变量
    myid=100
    #输出变量内容
    echo $myid
    

    上面脚本中,第一行#!打头的用于指定shell类型,后面的#都是用来书写注释信息的。

    shell脚本程序实际是由一条条的命令和结构化语句组成,我们可以把这一条条的shell命令调用看作其它编程语言的函数调用,另外bash也支持定义函数。linux系统常见的shell命令可参考《Linux常见Shell命令》

    (三)脚本参数

    在编写shell脚本程序时,很多情况下程序需要获取一些用户输入的信息,这有两种方式,一种是在脚本中使用read命令,当脚本执行到read命令时,会停下等待用户输入后才会继续执行;但在很多时候,脚本往往被自动执行,不适合用户输入,这个可以通过调用脚本时给脚本传入参数来实现,这种场景往往更多。

    下面我们来介绍如何给脚本传参数。

    在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……。另外$0 为执行的文件名。

    下面举例说明:

    假设脚本文件名为test.sh,文件内容如下:

    #!/bin/bash
    echo $0
    echo $1
    echo $2
    

    执行如下的命令:

    $ ./test.sh tom 20
    ./test.sh
    tom
    20
    

    可以看出,我们在调用test.sh传入了两个参数tom和20,在脚本程序中通过1和2获取到传入的参数,并且通过$0获取到文件名。

    为了简单,本文下面的例子演示没有写到脚本文件中进行调用执行,而是直接在控制台$符号下进行的。

    二、基本语法

    (一)shell变量

    变量是任何一门编程语言中的基本特性,变量用于保存值。Linux shell同样支持变量。在shell脚本中,可以创建变量,shell脚本中的变量只在创建它的shell中使用。shell中的变量创建和赋值放在一起,不需要使用任何关键字,只需”变量名=变量值”即可。特别要注意的是,=两边不能有空格。

    使用变量时,通过$符号来引用。如下面示例:

    $ x=hello
    $ echo $x
    hello
    $ y=12
    $ echo $y
    12
    

    需要注意的是,上面例子中以符号开头的行,这打头的符号是shell终端的提示符,该行语句表示执行一条命令。没有$开头行的是命令执行的输出内容。后面的例子一样。

    上面示例代码中,第1行定义了变量x,其值为hello。第2行执行echo命令在控制台输出变量x的值,可以看出,引用变量时要在变量名前加$符号。第3行定义了变量y,第4行调用echo命令输出变量y的值。

    shell是一种脚本语言,弱类型的,定义变量时不需要声明数据类型。赋值给变量的值全部会当作字符串处理。值可以用双引号或单引号括起来,也可以不用(如上面例子没有用引号括起),如下面例子:

    $ x="hello"
    $ echo $x
    hello
    $ x='hello"world'
    $ echo $x
    hello"world
    

    上面例子中,给变量赋值时,值用引号括起来。

    需要注意的是,在shell中,标识符(如变量名)是区分大小写的。

    (二)环境变量

    Linux下的环境变量与windows下的的环境变量类似。当linux内核启动一个程序时,内核会将一个包含字符串数组的列表传递给程序,这个列表称为命令执行环境,简称为环境,以name=value格式保存了一系列名/值对。

    在bash中,我们可以使用这些环境变量。

    linux中的环境变量分为持久和临时的,持久的环境变量是在文件中保存的,因为是持久化的,所以永久有效;而临时的环境变量只在内存中,只针对当前shell会话有效。

    下面我们先看下如何定义临时的环境变量。通过export命令可以定义环境变量。如下面例子:

    $ export myid=100
    $ echo $myid
    100
    

    上面例子第一句定义了环境变量myid,该变量的值为100,注意等号两边不能有空格。使用环境变量和使用shell变量一样,用$加前缀引用。

    在上一节我们介绍了shell变量的使用,那与环境变量有啥区别呢?我们通过例子来说明:

    $ myid=100
    $ echo $myid
    100
    $ bash
    $ echo $myid
    $
    

    上面例子,我们先定义了一个shell变量myid,然后我们在当前会话下运行bash程序(相当于启动了一个子shell),会发现在这子shell中,无法获取到父shell中定义的变量。

    我们看下如果定义成环境变量会如何?如下面例子:

    $ export myid=100
    $ echo $myid
    100
    $ bash
    $ echo $myid
    100
    $
    

    可以看出,定义成环境变量后,在子shell中就可以使用。

    需要说明的是,当在控制台执行一个shell脚本文件时,相当于启动一个子shell,这意味着无法在shell文件中引用外部定义的shell变量,但可以使用环境变量。

    下面我们来看下如何持久化环境变量,即将环境变量的设置信息保存在文件中。Linux中环境变量包括系统级和用户级,系统级的环境变量是每个登录到系统的用户都要读取的系统变量,而用户级的环境变量则是该用户使用系统时加载的环境变量。 所以linux保存环境变量的文件也分为系统级和用户级的。

    持久化环境变量,很简单,只需将export命令写入到相应的配置文件中。需要说明的是,不同版本的linux系统,所使用的配置文件可能不同,而且这些配置文件还存在相互调用的问题。

    用户登陆时,会先执行/etc/profile脚本文件中的命令,为全系统所有用户建立环境变量。我们可以在该文件中设置一些对所有用户都有关的环境变量。需要注意的是,如果修改了该文件中的内容,需要重启系统才能生效。

    每个用户特有的环境变量,在该用户的主目录下的脚本中设置。如.bash_profile脚本文件,该文件会调用.bashrc脚本文件,而.bashrc又会调用/etc/bashrc脚本文件。我们可以在.bash_profile或.bashrc中设置本用户特有的环境变量。每次打开shell,.bash_profile都会被执行。如果修改了.bash_profile或.bashrc文件中的内容,需要退出当前shell重新打开shell才能生效。

    用户主目录下还有一个脚本文件.bash_logout,当用户退出当前shell时,该脚本会被执行,可以在该脚本中进行一些资源释放操作。

    (三)运算符与表达式

    表达式是将常量、变量、运算符组合在一起,产生一个值。在本小节我们来介绍算术运算。bash支持的算术运算符和其它语言差不多,包括 +(加)、-(减)、*(乘)、/(整除)、%(取余)

    在bash中,与其它语言不同,如下的操作是无法进行算术运算的,如:

    $ a=2+3
    $ echo $a
    2+3
    $ a=2 + 3
    bash: +: 未找到命令...
    

    上面例子,直接数字相加不是一个表达式,如果+两边无空格,则会被当作一个字符串;如果+两边有空格,则会报错。

    我们再看下,有变量参与的情况,例子如下:

    $ a=2
    $ b=$a+2
    $ echo $b
    2+2
    $ b=$a + 3
    bash: +: 未找到命令...
    

    从执行情况看,与常量运算差不多,依然无法进行计算。

    在bash中,算术表达式需要有些特殊的写法,第一种方式是利用expr表达式,如下面例子:

    $ echo `expr 2 + 2`
    4
    $ a=3
    $ echo `expr $a + 2`
    5
    

    也就是说要在表达式前加expr关键字,并将整个内容放在倒引号之中,这才是一个合法的算术表达式。另外要特别注意的是,运算符左右要有空格,没有空格就不会被计算,如下面例子:

    $ echo `expr 2+2`
    2+2
    

    可以看出,上面例子+左右无空格,被当作了一个字符串。这使用起来比较别扭。

    还有一种方式是,使用 $[表达式] 这样的方式,如:

    $ echo $[3+5]
    8
    

    采用$[]方式的好处是,运算符,如上面的+左右可以有空格,也可以没空格,这个与其它编程语言一样,用起来就不容易出错。

    另外使用[]时,如果参与运算的有变量,则[]中的变量前面不需要加符号,这样就与其它编程语言中的算术表达式类似,如下面例子:

    $ a=12
    $ echo $[a-4]
    8
    $ echo $[a*a]
    144
    

    通过内置命令let可以将一个表达式赋值给一个变量,变量的值就是表达式的计算结果,如下面例子:

    $ let x=12+14
    $ echo $x
    26
    

    上面例子,通过内置命令let将一个算术表达式赋值给了一个变量x。可以看出,变量的值就是表达式的计算结果。需要注意的是,=和+两边都不能有空格。

    从上面几种算术表达式的编写方式看,还是使用$[]格式最好,不需要关心是否有空格等问题。

    需要注意的是:标准的bash只支持整数运算,不支持浮点运算,如下面例子:

    $ echo $[12+12]
    24
    $ echo $[12+12.5]
    -bash: 12+12.5: 语法错误: 无效的算术运算符 (错误符号是 ".5")
    

    可以看出,上面的表达式中有浮点数12.5,会报错。如果想要支持浮点运算,需要使用特殊的第三方包,这里不再介绍。

    (四)字符串连接

    连接字符串是个常见的操作,不同于很多编程语言,通过+运算符可以将字符串连接,在bash中,连接字符串的方式如下:

    $ x=hello
    $ y=world
    $ z=${x}${y}
    $ echo $z
    helloworld
    

    从上面例子可以看出,连接字符串时,用{}将变量名括起,注意$符号要放在{}外边。下面再看一个将变量与常量连接的例子:

    $ x=hello
    $ z=${x}world
    $ echo $z
    helloworld
    

    (五)数组

    bash支持一维数组,将多个空格分开的值放在()可定义数组,利用下标(从0开始)可获取数组的中等个元素。如下面例子:

    $ arr=(a b c)
    $ echo ${arr[0]}
    a
    $ echo ${arr[2]}
    c
    

    上面例子,先定义了一个数组arr,然后利用 ${数组名[序号]} 这样的方式来获取数组中的元素。

    我们还可以利用下标来新建元素或往元素中添加元素,如:

    myarr[0]=1
    

    上面语句,如果myarr数组不存在,则会新建一个数组,并添加一个元素。如果已经存在,且指定序号的元素存在,则会替换,bash中的元素序号可以不连续,如:

    myarr[5]=2
    

    利用@或*做下标可以获取所有数组的内容,如下面例子:

    $ data=( 2 4)
    $ echo ${data[*]}
    2 4
    $ echo ${data[@]}
    2 4
    

    获取数组长度的语法是 ${#数组名{*}},如:

    echo ${#data[*]}
    

    三、结构化语句

    (一)条件表达式

    条件表达式主要用于if条件语句和循环语句中。

    bash的条件表达式有如下写法,一种是使用[]的条件表达式,如下面例子:

    a=3
    b=4
    if [ $a == $b ]
    then
      echo a==b
    else
      echo a!=b
    fi
    

    上面例子中[]之间是一个条件表达式,==是判断是否相等的运算符。需要注意的是,[]内部的两边必须要有空格,运算符==的两边也必须要有空格。这是使用bash编写程序比较别扭和需要注意的地方。

    下表列出了bash常用的关系运算符,假定变量 a 为 3,变量 b 为 4:

    运算符 说明 举例
    == 检测两个数是否相等,相等返回 true。 a ==b 返回false
    != 检测两个数是否不相等,相等返回 true。 a !=b 返回true
    -eq 检测两个数是否相等,相等返回 true。等同于== [ a -eqb ] 返回 false。
    -ne 检测两个数是否不相等,不相等返回 true。等同于!= [ a -neb ] 返回 true。
    -gt 检测左边的数是否大于右边的,如果是,则返回 true。 [ a -gtb ] 返回 false。
    -lt 检测左边的数是否小于右边的,如果是,则返回 true。 [ a -ltb ] 返回 true。
    -ge 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ a -geb ] 返回 false。
    -le 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ a -leb ] 返回 true。

    bash也支持逻辑运算符,对两个布尔值进行运算,其中 -a 用于“与”运算;-o用于“或”运算;-o用于“非”运算,如下面例子:

    a=3
    b=4
    if [ $a -lt 5 -a $b -gt 2 ]
    then
       echo "a 小于 5 且 b 大于 2 "
    fi
    

    bash也支持字符串的布尔运算。下表列出了常用的字符串运算符,假定变量 a 为 "abc",变量 b 为 "efg":

    运算符 说明 举例
    = 检测两个字符串是否相等,相等返回 true。 [ a =b ] 返回 false。
    != 检测两个字符串是否相等,不相等返回 true。 [ a !=b ] 返回 true。
    -z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
    -n 检测字符串长度是否为0,不为0返回 true。 [ -n "$a" ] 返回 true。
    $ 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true。

    在shell编程中,经常要对文件进行操作。bash提供了很多运算符进行文件的相关测试。相关运算符如下:

    • -e 文件名 如果文件存在则为真
    • ** -r 文件名 如果文件存在且可读则为真**
    • -w 文件名 如果文件存在且可写则为真
    • -x 文件名 如果文件存在且可执行则为真
    • -s 文件名 如果文件存在且至少有一个字符则为真
    • -d 文件名 如果文件存在且为目录则为真
    • -f 文件名 如果文件存在且为普通文件则为真
    • -c 文件名 如果文件存在且为字符型特殊文件则为真
    • -b 文件名 如果文件存在且为块特殊文件则为真

    下面看一个例子:

    if [ -e test.txt ]
    then
     echo test.txt is  exist
    fi
    

    上面代码测试了当前目录下是否存在test.txt文件。

    除了使用[]来表示条件表达式外,还可以使用test命令。test是bash的内置命令,test命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试,如果成立(即为真)。如下面例子:

    a=3
    b=4
    if test $a == $b
    then
      echo a==b
    else
      echo a!=b
    fi
    

    对于数值判断,还支持通过(())来进行条件描述,并支持 > ,>= 等运算符,这些运算符[]和test命令不支持,如下面例子:

    int=1
    while(($int<=5))
    do
        echo $int
        int=$[int+1]
    done
    

    使用(())这种写法,运算符,如上面的<=左右是否有空格无所谓。

    (二)if语句

    bash中的if条件语句,除细微的语法细节不同外,和c,java语言的if语句基本类似。其基本语法格式如下:

    if  条件
      then
          命令...
    fi
    

    如果条件为真,则会执行then后的命令(可以有1条或多条命令),if语句以fi结尾。

    除 if...then...fi语句外,bash还支持 if ...then...else..fi语句,即当if后的条件为真,执行then后的语句,否则执行else后的语句。

    另外还支持 if ..then..[elif ..then]..else..fi 语句,可以有1个或多了elif语句。

    因为if语句的语法和其它编程语言类似,上面小节也有举例了,所以不再详细介绍。

    (三)case语句

    类似c,java中的case语句,用于支持多条件匹配,其语法格式如:

    case 值 in
    匹配值1)
        command1
        command2
        ...
        commandN
        ;;
    ...
    匹配值n)
        command1
        command2
        ...
        commandN
        ;;
    esac
    

    上面语法有如下要求:

    1)case和in之间的值可以是常量或变量

    2)每个匹配值后面跟着)右括号

    3)某个匹配值被匹配,其后的命令被执行,直至碰到;; (相当于其它语言中的break)。需要说明的是,被匹配的到分支,一定要有;;x

    4)如果想匹配任意值,匹配值可写成*,一般放到最后

    5)最后整个case语句需要用esac结尾(即case单词反过来写)。

    下面我们看一个具体的例子:

    num=2
    case $num in
        1)  echo 'num = 1'
        ;;
        2)  echo 'num = 2'
        ;;
        3)  echo 'num = 3'
        ;;
        4)  echo 'num = 4'
        ;;
        *)  echo 'num = other'
        ;;
    esac
    

    执行上面代码,发现会输出2.

    (四)for循环

    bash支持for循环,有如下用法.

    1、遍历多个值,如:

    $ for v in a b c
    > do
    > echo $v
    > done
    a
    b
    c
    

    上面例子用到了for in do done 四个关键字,in后面跟的是要遍历的列表值,for和in之间是用于代表每次循环元素,执行的命令需要位于 do和done之间。

    说明:上面的for语句有换行时,>是控制台自动显示的内容。如果是在脚本中编写,则没有>符号。也可以写在一行,采用如下格式:

    for v in a b c;  do  echo $v; done
    

    2、遍历数组

    可以用for语句来遍历数组,如下面例子:

    $ for v in ${arr[*]}
    > do
    > echo $v
    > done
    3
    6
    12
    

    3、c语言的风格

    也支持类似c语言的for循环风格,如下面例子:

    $ for((i=1;i<=5;i++))
    > do
    > echo $i
    > done
    1
    2
    3
    4
    5
    

    注意,for后面的内容是用两个小括号包围的。

    (五)while循环

    类似c,java中的while语句,满足条件执行循环中的内容。如下面例子:

    int=1
    while [ $int -le 5 ]
    do
        echo $int
        int=$[int+1]
    done
    

    或改为(())的条件写法

    int=1
    while(( $int<=5 ))
    do
        echo $int
        let int=int+1hu
    done
    

    注意,while后面的条件表达式要放在两个()之间。

    同样我们可以使用test内置命令来作为条件,如:

    int=1
    while test $int -le 5
    do
        echo $int
        let int=int+1
    done
    

    (六)break和continue语句

    类似c,java中的break和continue,可以跳出循环,或提前结束本次循环。下面看具体例子:

    int=1
    while [ $int -le 5 ]
    do
        if (( $int==3 ))
        then
           break
        fi
        echo $int
        int=$[int+1]
    done
    

    上面代码执行,只会输出1,2信息。

    再看一个continue的例子:

    int=0
    while [ $int -le 5 ]
    do
        int=$[int+1]
        if (( $[int%2]==0 ))
        then       
           continue
        fi
        echo $int
    done
    

    上面代码执行,只会输出1,3,5信息。

    四、函数

    (一)基础

    bash支持定义函数,其语法格式为:

    [ function ] 函数名 ()
    {
        命令
        [return 整数]
    }
    

    上面语法有如下特点:

    1)关键字function可选

    2)return语句可选,return后面跟0~255的整数值,作为函数返回值。如果无return语句,则以最后一条命令运行结果作为返回值。

    下面我们通过具体的例子来说明。我们先来看一个最简单的例子,如:

    myfun()
    {
      echo "this is a fun"
    }
    

    上面定义了一个函数,函数名为myfun。

    调用函数就同调用shell命令类似,注意不像其它语言,调用时不能加(),如:

    $ myfun
    this is a fun
    

    上面在$提示符下直接输入myfun就是调用函数。

    (二)函数的返回值

    我们先看下shell命令执行的返回值,bash中的每条命令执行后都会有一个返回值,一般情况下成功返回值为0,出错的话返回值就是一个非0整数。命令执行的返回值,通过在命令执行后使用$?来获取。

    如下面例子:

    $ ls test.txt
    test.txt
    $ echo $?
    0
    $ ls test5.txt
    ls: 无法访问test5.txt: 没有那个文件或目录
    $ echo $?
    2
    $ ls test.txt
    test.txt
    $ echo $?
    0
    

    从上面例子我们可以看出,先使用ls命令显示一个正确的文件后,$?的值为0,这是命令返回结果;然后用ls命令显示不存在的文件,ls命令会报错,这时$?的值为非零值。

    bash函数的调用,和执行shell命令类似,其返回值也是通过$?来获取的,这点与其它编程语言差别很大。

    前面我们提到,在函数中通过可选的return语句,后面跟0~255的整数值,作为函数返回值。如果无return语句,则以最后一条命令运行结果作为返回值。

    下面我们通过例子来说明:

    $ myfun()
    > {
    >   echo "this is a fun"
    >   return 100
    > }
    $ myfun
    this is a fun
    $ echo $?
    100
    

    上面例子我们先定义了一个函数myfun,然后调用该函数,最后显示$?的值,这个值就是myfun函数中的return后面的值。

    下面我们再看一个无return语句的例子,如:

    $ myfun()
    > {
    >   rm other.txt
    >   echo hello
    > }
    $ myfun
    rm: 无法删除"other.txt": 没有那个文件或目录
    hello
    $ echo $?
    0
    

    上面例子中定义的myfun函数中有两条命令,调用函数时,rm命令报错,echo命令正常执行,所以调用函数后获取到的$?的值为0。如果我们试着把这两个命令顺序换下,会发现$?的值不为0,这里不再具体的演示。

    (三)函数的参数

    Bash的函数也可以有参数,但参数的使用方法和别的编程语言有较大差别,c/java等编程语言的函数参数是在函数声明中定义,而bash函数的参数由调用时决定。

    在调用bash函数时,可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数,下面我们通过例子来说明:

    $ myfun()
    > {
    >   echo $1
    >   echo $2
    > }
    $ myfun hello 2
    hello
    2
    

    上面代码中函数myfun通过$1和$2引用了两个函数参数,调用函数时,传入了2个参数,正好对应$1和$2.

    如果我们调用时不传入参数值,则函数内的$n的值为空。

    结合前面所学的,可以看出,给函数传递参数和给脚本文件传递参数适用方式类似。

    五、小结

    本文是对linux shell编程的一个学习总结,linux shell是一种脚本语言,具有脚本语言的一些基本特征,如弱类型、解释执行,对变量、结构化语句的支持等,也有一些差异化的语法特点。

    相关文章

      网友评论

          本文标题:Linux Shell编程学习笔记

          本文链接:https://www.haomeiwen.com/subject/qnajnqtx.html