Linux Shell常用技巧 Shell编程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux Shell常用技巧 Shell编程相关的知识,希望对你有一定的参考价值。

Linux Shell常用技巧(十二) Shell编程

二十三. Bash Shell编程:

    1.  读取用户变量:
    read命令是用于从终端或者文件中读取输入的内建命令,read命令读取整行输入,每行末尾的换行符不被读入。在read命令后面,如果没有指定变量名,读取的数据将被自动赋值给特定的变量REPLY。下面的列表给出了read命令的常用方式:

命令格式描述
read answer从标准输入读取输入并赋值给变量answer。
read first last从标准输入读取输入到第一个空格或者回车,将输入的第一个单词放到变量first中,并将该行其他的输入放在变量last中。
read从标准输入读取一行并赋值给特定变量REPLY。
read -a arrayname把单词清单读入arrayname的数组里。
read -p prompt打印提示,等待输入,并将输入存储在REPLY中。
read -r line允许输入包含反斜杠。


    见下面的示例(绿色高亮部分的文本为控制台手工输入信息):
    /> read answer        #等待读取输入,直到回车后表示输入完毕,并将输入赋值给变量answer
    Hello                       #控制台输入Hello
    /> echo $answer      #打印变量
    Hello

    #等待一组输入,每个单词之间使用空格隔开,直到回车结束,并分别将单词依次赋值给这三个读入变量。
    /> read one two three
    1 2 3                      #在控制台输入1 2 3,它们之间用空格隔开。
    /> echo "one = $one, two = $two, three = $three"
    one = 1, two = 2, three = 3

    /> read                  #等待控制台输入,并将结果赋值给特定内置变量REPLY。
    This is REPLY          #在控制台输入该行。
    /> echo $REPLY      #打印输出特定内置变量REPLY,以确认是否被正确赋值。
    This is REPLY

    /> read -p "Enter your name: "    #输出"Enter your name: "文本提示,同时等待输入,并将结果赋值给REPLY。
    Enter you name: stephen            #在提示文本之后输入stephen
    /> echo $REPLY
    stephen

    #等待控制台输入,并将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
    /> read -a friends
    Tim Tom Helen
    /> echo "I have ${#friends} friends"
    I have 3 friends
    /> echo "They are ${friends[0]}, ${friends[1]} and ${friends[2]}."
    They are Tim, Tom and Helen.

   2.  状态判断:
    test是Shell中提供的内置命令,主要用于状态的检验,如果结果为0,表示成功,否则表示失败。见如下示例:
    /> name=stephen
    /> test $name != stephen
    /> echo $?
    1
    需要注意的是test命令不支持Shell中提供的各种通配符,如:
    /> test $name = [Ss]tephen
    /> echo $?
    1
    test命令还可以中括号予以替换,其语义保持不变,如:
    /> [ $name = stephen ]
    /> echo $?
    0   
    在Shell中还提供了另外一种用于状态判断的方式:[[ expr ]],和test不同的是,该方式中的表达式支持通配符,如:
    /> name=stephen
    /> [[ $name == [Ss]tephen ]]
    /> echo $?
    0
    #在[[ expression ]]中,expression可以包含&&(逻辑与)和||(逻辑或)。
    /> [[ $name == [Ss]tephen && $friend == "Jose" ]]
    /> echo $?
    1
    /> shopt -s extglob   #打开Shell的扩展匹配模式。
    /> name=Tommy
    # "[Tt]o+(m)y"的含义为,以T或t开头,后面跟着一个o,再跟着一个或者多个m,最后以一个y结尾。
    /> [[ $name == [Tt]o+(m)y ]]
    /> echo $?
    0
    在Shell中还提供了let命令的判断方式: (( expr )),该方式的expr部分,和C语言提供的表达式规则一致,如:
    /> x=2
    /> y=3
    /> (( x > 2 ))
    /> echo $?
    1
    /> (( x < 2 ))
    /> echo $?
    0  
    /> (( x == 2 && y == 3 ))
    /> echo $?
    0
    /> (( x > 2 || y < 3 ))
    /> echo $?
    1

    下面的表格是test命令支持的操作符:

判断操作符判断为真的条件
字符串判断
[ stringA=stringB ]stringA等于stringB
[ stringA==stringB ]stringA等于stringB
[ stringA!=stringB ]stringA不等于stringB
[ string ]string不为空
[ -z string ]string长度为0
[ -n string ]string长度不为0
逻辑判断
[ stringA -a stringB ]stringA和stringB都是真
[ stringA -o stringB ]stringA或stringB是真
[ !string ]string不为真
逻辑判断(复合判断)
[[ pattern1 && pattern2 ]]pattern1和pattern2都是真
[[ pattern1 || pattern2 ]pattern1或pattern2是真
[[ !pattern ]]pattern不为真
整数判断
[ intA -eq intB ]intA等于intB
[ intA -ne intB ]intA不等于intB
[ intA -gt intB ]intA大于intB
[ intA -ge intB ]intA大于等于intB
[ intA -lt intB ]intA小于intB
[ intA -le intB ]intA小于等于intB
文件判断中的二进制操作
[ fileA -nt fileB ]fileA比fileB新
[ fileA -ot fileB ]fileA比fileB旧
[ fileA -ef fileB ]fileA和fileB有相同的设备或者inode值
文件检验
[ -d $file ] or [[ -d $file ]]file为目录且存在时为真
[ -e $file ] or [[ -e $file ]]file为文件且存在时为真
[ -f $file ] or [[ -f $file ]]file为非目录普通文件存在时为真
[ -s $file ] or [[ -s $file ]]file文件存在, 且长度不为0时为真
[ -L $file ] or [[ -L $file ]]file为链接符且存在时为真
[ -r $file ] or [[ -r $file ]]file文件存在且可读时为真
[ -w $file ] or [[ -w $file ]]file文件存在且可写时为真
[ -x $file ] or [[ -x $file ]]file文件存在且可执行时为真

    注:在逻辑判断(复合判读中),pattern可以包含元字符,在字符串的判断中,pattern2必须被包含在引号中。

    let命令支持的操作符和C语言中支持的操作符完全相同,如:
    +,-,*,/,%            加,减,乘,除,去模
    >>,<<                右移和左移
    >=,<=,==,!=      大于等于,小于等于,等于,不等于
    &,|,^                  按位与,或,非
    &&,||,!                逻辑与,逻辑或和取反
    还有其含义和C语言等同的快捷操作符,如=,*=,/=,%=,+=,-=,<<=,>>=,&=,|=,^=。

    3.  流程控制语句:
    if语句格式如下:
    #if语句的后面是Shell命令,如果该命令执行成功返回0,则执行then后面的命令。
    if command        
    then
        command
        command
    fi
    #用test命令测试其后面expression的结果,如果为真,则执行then后面的命令。
    if test expression
    then
        command
    fi
    #下面的格式和test expression等同
    if [ string/numeric expression ]
    then
        command
    fi
    #下面的两种格式也可以用于判断语句的条件表达式,而且它们也是目前比较常用的两种。
    if [[ string expression ]]
    then
        command
    fi

    if (( numeric expression ))           #let表达式
    then
        command
    fi
    见如下示例:
    /> cat > test1.sh                       #从命令行直接编辑test1.sh文件。
    echo -e "Are you OK(y/n)? \c"
    read answer
    #这里的$answer变量必须要用双引号扩住,否则判断将失败。当变量$answer等于y或Y时,支持下面的echo命令。
    if [ "$answer" = y -o "$answer" = Y ]   
    then
        echo "Glad to see it."
    fi
    CTRL+D  
    /> . ./test1.sh
    Are you OK(y/n)? y
    Glad to see it.
    上面的判断还可以替换为:
    /> cat > test2.sh
    echo -e "Are you OK(y/n or Maybe)? \c"
    read answer
    # [[ ]]复合命令操作符允许其中的表达式包含元字符,这里输入以y或Y开头的任意单词,或Maybe都执行then后面的echo。
    if [[ $answer == [yY]* || $answer = Maybe ]]  
    then
        echo "Glad to hear it.
    fi
    CTRL+D
    /> . ./test2.sh
    Are you OK(y/n or Maybe)? yes
    Glad to hear it.
    下面的例子将使用Shell中的扩展通配模式。
    /> shopt -s extglob        #打开该扩展模式
    /> answer="not really"
    /> if [[ $answer = [Nn]o?( way |t really) ]]
    > then
    >    echo "I am sorry."
    > fi
    I am sorry.
    对 于本示例中的扩展通配符,这里需要给出一个具体的解释。[Nn]o匹配No或no,?( way|t really)则表示0个或1个( way或t   really),因此answer变量匹配的字符串为No、no、Not really、not really、No way、no way。
    下面的示例使用了let命令操作符,如:
    /> cat > test3.sh
    if (( $# != 2 ))                    #等同于 [ $# -ne 2 ]
    then
        echo "Usage: $0 arg1 arg2" 1>&2
        exit 1                         #exit退出值为0-255之间,只有0表示成功。
    fi
    if (( $1 < 0 || $1 > 30 ))      #等同于 [ $1 -lt 0 -o $1 -gt 30 ]
    then
        echo "arg1 is out of range."
        exit 2
    fi
    if (( $2 <= 20 ))                  #等同于 [ $2 -le 20 ]
    then
        echo "arg2 is out of range."
    fi
    CTRL+D
    /> sh ./test3.sh
    Usage: ./test3.sh arg1 arg2
    /> echo $?                          #Shell脚本的退出值为exit的参数值。
    1
    /> sh ./test3.sh 40 30
    arg1 is out of range.
    /> echo $?
    2
    下面的示例为如何在if的条件表达式中检验空变量:
    /> cat > test4.sh
    if [ "$name" = "" ]                #双引号就表示空字符串。
    then
        echo "name is null."
    fi
    CTRL+D
    /> . ./test4.sh
    name is null.

    if/elif/else语句的使用方式和if语句极为相似,相信有编程经验的人都不会陌生,这里就不再赘述了,其格式如下:
    if command
    then
        command
    elif command
    then
        command
    else
        command
    fi
    见如下示例脚本:
    /> cat > test5.sh
    echo -e "How old are you? \c"
    read age
    if [ $age -lt 0 -o $age -gt 120 ]                #等同于 (( age < 0 || age > 120 ))
    then
        echo "You are so old."
    elif [ $age -ge 0 -a $age -le 12 ]               #等同于 (( age >= 0 && age <= 12 ))
    then
        echo "You are child."
    elif [ $age -ge 13 -a $age -le 19 ]             #等同于 (( age >= 13 && age <= 19 ))
    then
        echo "You are 13--19 years old."
    elif [ $age -ge 20 -a $age -le 29 ]             #等同于 (( age >= 20 && age <= 29 ))
    then
        echo "You are 20--29 years old."
    elif [ $age -ge 30 -a $age -le 39 ]             #等同于 (( age >= 30 && age <= 39 ))
    then
        echo "You are 30--39 years old."
    else
        echo "You are above 40."
    fi
    CTRL+D
    /> . ./test5.sh
    How old are you? 50
    You are above 40.

    case语句格式如下:
    case variable in
    value1)
        command
        ;;            #相同于C语言中case语句内的break。
    value2)
        command
        ;;
    *)                #相同于C语言中switch语句内的default
       command
        ;;
    esac
    见如下示例脚本:
    /> cat > test6.sh
    #!/bin/sh
    echo -n "Choose a color: "
    read color
    case "$color" in
    [Bb]l??)
        echo "you select blue color."
        ;;
    [Gg]ree*)
        echo "you select green color."
        ;;
    red|orange)
        echo "you select red or orange."
        ;;
    *)
        echo "you select other color."
        ;;
    esac
    echo "Out of case command."
    /> . ./test6.sh
    Choose a color: green
    you select green color.
    Out of case command.

   4.  循环语句:
    Bash Shell中主要提供了三种循环方式:for、while和until
    for循环声明格式:
    for variable in word_list
    do
        command
    done
    见如下示例脚本:
    /> cat > test7.sh
    for score in math english physics chemist   #for将循环读取in后面的单词列表,类似于Java的for-each。
    do
        echo "score = $score"
    done
    echo "out of for loop"
    CTRL+D
    /> . ./test7.sh
    score = math
    score = english
    score = physics
    score = chemist
    out of for loop

    /> cat > mylist   #构造数据文件
    tom
    patty
    ann
    jake
    CTRL+D
    /> cat > test8.sh
    #!/bin/sh
    for person in $(cat mylist)                 #for将循环读取cat mylist命令的执行结果。
    do
        echo "person = $person"
    done
    echo "out of for loop."
    CTRL+D
    /> . ./test8.sh
    person = tom
    person = patty
    person = ann
    person = jake
    out of for loop.

    /> cat > test9.sh
    for file in test[1-8].sh                        #for将读取test1-test8,后缀为.sh的文件
    do
        if [ -f $file ]                              #判断文件在当前目录是否存在。
        then
            echo "$file exists."
        fi
    done
    CTRL+D
    /> . ./test9.sh
    test2.sh exists.
    test3.sh exists.
    test4.sh exists.
    test5.sh exists.
    test6.sh exists.
    test7.sh exists.
    test8.sh exists.

    /> cat > test10.sh
    for name in $*                                  #读取脚本的命令行参数数组,还可以写成for name的简化形式。
    do
        echo "Hi, $name"
    done
    CTRL+D
    /> . ./test10.sh stephen ann
    Hi, stephen
    Hi, ann

    while循环声明格式:
    while command  #如果command命令的执行结果为0,或条件判断为真时,执行循环体内的命令。
    do
        command
    done
    见如下示例脚本:
    /> cat > test1.sh  
    num=0
    while (( num < 10 ))               #等同于 [ $num -lt 10 ]
    do
        echo -n "$num "
        let num+=1
    done
    echo -e "\nHere‘s out of loop."
    CTRL+D
    /> . ./test1.sh
    0 1 2 3 4 5 6 7 8 9
    Here‘s out of loop.

    /> cat > test2.sh
    go=start
    echo Type q to quit.
    while [[ -n $go ]]                     #等同于[ -n "$go" ],如使用该风格,$go需要被双引号括起。
    do
        echo -n How are you.
        read word
        if [[ $word == [Qq] ]]      #等同于[ "$word" = Q -o "$word" = q ]
        then
            echo Bye.
            go=                        #将go变量的值置空。
        fi
    done
    CTRL+D
    /> . ./test2.sh
    How are you. Hi
    How are you. q
    Bye.

    until循环声明格式:
    until command                         #其判断条件和while正好相反,即command返回非0,或条件为假时执行循环体内的命令。
    do
        command
    done
    见如下示例脚本:
    /> cat > test3.sh
    until who | grep stephen           #循环体内的命令将被执行,直到stephen登录,即grep命令的返回值为0时才退出循环。
    do
        sleep 1
        echo "Stephen still doesn‘t login."
    done
    CTRL+D

    shift命令声明格式:shift [n]
    shift命令用来把脚本的位置参数列表向左移动指定的位数(n),如果shift没有参数,则将参数列表向左移动一位。一旦移位发生,被移出列表的参数就被永远删除了。通常在while循环中,shift用来读取列表中的参数变量。
    见如下示例脚本:
    /> set stephen ann sheryl mark #设置4个参数变量。
    /> shift                                    #向左移动参数列表一次,将stephen移出参数列表。
    /> echo $*
    ann sheryl mark
    /> shift 2                                 #继续向左移动两位,将sheryl和ann移出参数列表
    /> echo $*
    mark
    /> shift 2                                 #继续向左移动两位,由于参数列表中只有mark了,因此本次移动失败。
    /> echo $*
    mark

    /> cat > test4.sh
    while (( $# > 0 ))                    #等同于 [ $# -gt 0 ]
    do
        echo $*
        shift
    done
    CTRL+D
    /> . ./test4.sh a b c d e
    a b c d e
    b c d e
    c d e
    d e
    e        

    break命令声明格式:break [n]
    和C语言不同的是,Shell中break命令携带一个参数,即可以指定退出循环的层数。如果没有指定,其行为和C语言一样,即退出最内层循环。如果指定循环的层数,则退出指定层数的循环体。如果有3层嵌套循环,其中最外层的为1,中间的为2,最里面的是3。
    见如下示例脚本:
    /> cat > test5.sh
    while true
    do
        echo -n "Are you ready to move on?"
        read answer
        if [[ $answer == [Yy] ]]
        then
            break
        else
            echo "Come on."
        fi
    done
    echo "Here we are."
    CTRL+D
    /> . ./test5.sh
    Are you ready to move on? y
    Here we are

    continue命令声明格式:continue [n]
      和C语言不同的是,Shell中continue命令携带一个参数,即可以跳转到指定层级的循环顶部。如果没有指定,其行为和C语言一样,即跳转到最内 层循环的顶部。如果指定循环的层数,则跳转到指定层级循环的顶部。如果有3层嵌套循环,其中最外层的为3,中间的为2,最里面的是1。
    /> cat  maillist                       #测试数据文件maillist的内容为以下信息。
    stephen
    ann
    sheryl
    mark

    /> cat > test6.sh
    for name in $(cat maillist)
    do
        if [[ $name == stephen ]]; then
            continue
        else
            echo "Hello, $name."
        fi
    done
    CTRL+D
    /> . ./test6.sh
    Hello, ann.
    Hello, sheryl.
    Hello, mark.

    I/O重新定向和子Shell:
    文件中的输入可以通过管道重新定向给一个循环,输出也可以通过管道重新定向给一个文件。Shell启动一个子Shell来处理I/O重新定向和管道。在循环终止时,循环内部定义的任何变量对于脚本的其他部分来说都是不可见的。
    /> cat > demodata                        #为下面的脚本构造测试数据
    abc
    def
    ghi
    CRTL+D
    /> cat > test7.sh
    if (( $# < 1 ))                                #如果脚本参数的数量小于1,则给出错误提示后退出。
    then
        echo "Usage: $0 filename " >&2
        exit 1
    fi
    count=1
    cat $1 | while read line                   #参数一中的文件被cat命令输出后,通过管道逐行输出给while read line。
    do
        let $((count == 1)) && echo "Processing file $1..." > /dev/tty  #该行的echo将输出到当前终端窗口。
        echo -e "$count\t$line"              #将输出行号和文件中该行的内容,中间用制表符隔开。
        let count+=1
    done > outfile                               #将while循环中所有的输出,除了>/dev/tty之外,其它的全部输出到outfile文件。
    CTRL+D
    /> . ./test7.sh demodata                #只有一行输出,其余的都输出到outfile中了。
    Processing file demodata...
    /> cat outfile
    1       abc
    2       def
    3       ghi

    /> cat > test8.sh
    for i in 9 7 2 3 5 4
    do
        echo $i
    done | sort -n                                #直接将echo的输出通过管道重定向sort命令。
    CTRL+D
    /> . ./test8.sh
    2
    3
    4
    5
    7
    9

    5.  IFS和循环:
      Shell的内部域分隔符可以是空格、制表符和换行符。它可以作为命令的分隔符用在例如read、set和for等命令中。如果在列表中使用不同的分隔 符,用户可以自己定义这个符号。在修改之前将IFS原始符号的值保存在另外一个变量中,这样在需要的时候还可以还原。
    见如下示例脚本:
    /> cat > test9.sh
    names=Stephen:Ann:Sheryl:John   #names变量包含的值用冒号分隔。
    oldifs=$IFS                                   #保留原有IFS到oldifs变量,便于后面的还原。
    IFS=":"                            
    for friends in $names                     #这是遍历以冒号分隔的names变量值。    
    do
        echo Hi $friends
    done
    IFS=$oldifs                                   #将IFS还原为原有的值。
    set Jerry Tom Angela
    for classmates in $*                      #再以原有IFS的值变量参数列表。
    do
        echo Hello $classmates
    done
    CTRL+D
    /> . ./test9.sh
    Hi Stephen
    Hi Ann
    Hi Sheryl
    Hi John
    Hello Jerry
    Hello Tom
    Hello Angela

    6.  函数:
    Shell中函数的职能以及优势和C语言或其它开发语言基本相同,只是语法格式上的一些差异。下面是Shell中使用函数的一些基本规则:
    1) 函数在使用前必须定义。
    2) 函数在当前环境下运行,它和调用它的脚本共享变量,并通过位置参量传递参数。而该位置参量将仅限于该函数,不会影响到脚本的其它地方。
  

以上是关于Linux Shell常用技巧 Shell编程的主要内容,如果未能解决你的问题,请参考以下文章

Linux 第十三天

Linux Shell常用技巧 交互式使用shell

shell编程中常用的技巧

Linux Shell常用技巧 系统运行状况

Linux Shell常用技巧 管道组合

SHELL脚本编程的常识和VI常用技巧