Linux学习之路-Shell编程

 

Shell的编程

 

     Bash是用得最广泛的shell,是大多数Linux 系统的缺省 shell。Bourne Again shell (bash), 正如它的名字所暗示的,是Bourne shell 的扩展。bash 与Bourne shell 完全向后兼容,并且在 Bourne shell 的基础上增加和增强了很多特性。bash 也包含了很多 C 和 Korn shell 里的优点。bash 有很灵活和强大的编程接口,同时又有很友好的用户界面。 下面具体来介绍bash。

 

Bash语法基本介绍

 

脚本的开头,必须以下面的行开始(必须方在文件的第一行):

#!/bin/bash

 

符号#!用来告诉系统它后面的参数是用来执行该文件的程序。当编辑好脚本时,如果要执行该脚本,有2种方式执行:

 

     1.给脚本执行权限:

         chmod +x filename 这样才能用./filename 来运行

     2.直接用bash调用执行
         bash filename

 

注释

在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。

 

         变量

在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明

根据类型又分为环境变量、本地变量、局部变量、位置变量、特殊变量(内置)

 

本地变量

    varname=value:作用域为整个bash进程可以使用;

        变量命名规范:

        1. 只能含字母、数字和下划线,并且以字母和下划线开头

        2. 最好不要跟系统已有的环境变量重名

        3. 见名知意

 

局部变量

    local varname=value:作用域为当前代码段;

 

环境变量:作用域为当前shell进程及其子进程,不能影响到其父进程;

    export varname=value “导出”,如果变量已经定义可以只是用变量名 export     varname,即

        1. export varname=value

        2. varname=value

            export varname

        脚本在执行命令时会启动一个子shell环境变量:

 

位置变量

    $1,$2,$3,……

                cat first.sh test.txt hello

                $1: first.sh

                $2: test.txt

                $3: hello

        shift:踢掉参数

                shift n:踢掉n个参数,默认踢掉一个参数

 

特殊变量

  $?:上一个命令执行状态的返回值:

  程序执行可能有两种返回值:

   1. 程序执行结果

   2.程序状态返回吗(0-255)0则为执行正确,1-255  则执行出错(1,2,127系统预留);还可以使用exit n 自定义返回值

 

   $#:获取当前shell命令行中参数的总个数

   $*:获取当前shell的所有参数 “$1 $2 $3 …,受IFS控制

   $@:这个程序的所有参数 “$1″ “$2″ “$3″ “…”,不受IFS控制

   $0  获取当前执行的shell脚本的文件名

   $n  获取当前执行的shell脚本的第n个参数值,n=1..9

   $$  获取当前shell的进程号(PID)

   $!  执行上一个指令的PID

n  这些特殊变量在编程中是非常重要的,例如执行一个脚本testsh a1 a2 ,可以通过$1和$2获得a1和a2的值,$#可以用于判断是否有带参数,和参数个数

n  还有一个shift [n]:轮替参数,可以把当前输入参数从左至右踢掉一个,可以用来方便的读取各个输入的参数

引用变量:
${varname},括号可以省略,但在某些时候必须使用,用来区分字符还是变量
例:${a}bc

撤销变量

    unset varname

查看当前shell中的变量:

    set  包括环境变量和本地变量

查看当前shell中的环境变量:

        1. printenv    2. env    3. export

 

 

Bash还支持各种条件测试(数值测试、字符串测试、文件测试)

 

根据运行的命令的状态结果

有布尔型: “真”:成功, “假”:失败

例:

  1 #!/bin/bash

  2 if $(cat /etc/fstab &>/dev/null);then

  3     echo $?"ok"

  4 else

  5     echo $?"false"

  6 fi

如果cat /etc/fstab执行成功,条件则为真,输出0OK,执行失败输出1false

 

     测试表达式

         test EXPRESSION

         [ EXPRESSION ]

         [[ EXPRESSION ]]

例:wKiom1YAub2hMLwbAABW6nAh3NY422.jpg

     整数测试隐含着做数值大小比较,所以不要给变量引用加引用;

         $A -gt $B:是否大于;是则为“真”,否则为“假”;

         $A -ge $B: 是否大于等于;

         $A -lt $B:是否小于;

         $A -le $B: 是否小于等于;

         $A -eq $B: 是否等于;

         $A -ne $B:是否不等于;

例:wKioL1YAvAryqLp9AABiMGnXoq4334.jpg

 

     字符串测试ASCII数值越大,字符比较时其值越大;

         "$A" > "$B":是否大于;

         "$A" < "$B":是否小于;

         "$A" == "$B":是否等于;

         "$A" != "$B":是否不等于;

         -z "$A":是否为空;空则为“真”,否则为“假”

         -n "$A":是否不空;不空则“真”,空则为“假”

         注意:应该使用[[ EXPRESSION ]]

例:     1 #!/bin/bash

         2 a="a"

         3 b="b"

         4

         5 if [[ $a < $b ]];then

         6     echo $?"ok"

         7 else

         8     echo $?"false"

         9 fi

    

    文件测试:测试文件的存在性以及属性;

         -e $file: 是否存在;存在则为“真”,否则为“假”;

         -a $file: 同上;

         -f $file:文件是否存在且为普通文件;

         -d $file:文件是否存在且为目录;

         -h $file:是否存在且为符号链接文件;

         -L $file: 同上

         -b $file:是否存在且为块设备文件;

         -c $file:是否存在且为字符设备文件;

         -S $file:是否存在且为套接字文件;

         -p $file: 是否存在且为管道文件;

         -r $file: 当前用户对文件是否拥有读权限;

         -w $file:当前用户对文件是否拥有写权限;

         -x $file:当前用户对文件是否拥有执行权限;

         -u $file:文件是否拥有SUID权限;

         -g $file:文件是否拥有SGID权限;

         -k $file:文件是否拥有sticky权限;

         -O $file: 当前用户是否为指定文件的属主;

         -G $file: 当前用户是否为指定文件的属组;

例:

                wKioL1YAvBmB4hoAAABD92wwBX8941.jpg

        

    双目操作符

         $file1 -nt $file2: file1是否新于file2, file1的最近一次的修改时间戳是否晚于file2的;

         $file1 -ot $file2: file1是否旧于file2, file1的最近一次的修改时间戳是否早于file2的;

         $file1 -ef $file2:file1与file2是否指向了同一个inode;测试二者是否为同一个文件的硬链接;

例:

                wKiom1YAuejydQWPAABPos1sqHY209.jpg

 

 

特殊设备:

u  /dev/null: 空,bit buckets,吞下所有数据,并直接丢弃;我们可以把一些不需要的信息丢弃,例如脚本中语句执行状态判断的时候

            1 #!/bin/bash

            2 if $(cat /etc/fstab &>/dev/null);then

            3     echo $?"ok"

            4 else

            5     echo $?"false"

            6 fi

            cat /etc/fstab的输出全部丢弃了

 

u  /dev/zero:吐出一堆0;

 

提供输入,read命令

     语法:  read [options] VAR...

             -p "PROMPT"

             -t timeout

例:read �Ct 30 �Cp “请在30秒内输入你的名字:” names

     停留30秒让用户输入名字,名字保存到变量names

 

命令引用:把执行的结果拿来使用

     `COMMAND`, $(COMMAND)

     可以使用`号或$(),建议使用$()更直观

     例:times=$(echo “19:00”)

 

 

Bash也包含了for、if、case、等流程控制语句

判断语句

if CONDITION; then

        if-true-分支

    fi

条件为真就执行里面的语句

例:if [ $a == “a”];then echo “ok”;fi

值得说明的是如果你将 if 和 then 简洁的写在一行里面,就必须在 then 前面加上分号,如:if [ expression ]; then ...。

 

if CONDITION; then

        if-true-分支1

    else

        if-false-分支2

    fi

条件为真执行分支1,否则执行分支2

例:if [ $a == “a”];then

         echo “ok”

    else

 

         echo “false”

    fi

 

case语句

    简洁版多分支if语句;           

    使用场景:判断某变量的值是否为多种情形中的一种时使用;

 

    语法:

        case $VARIABLE in

        PATTERN1)

           分支1

           ;;

        PATTERN2)

           分支2

           ;;

        PATTERN3)

           分支3

           ;;

        ...

        *)

           分支n

           ;;

        esac

 

        PATTERN可使用glob模式的通配符:

           *: 任意长度的任意字符;

           ?: 任意单个字符;

           []: 指定范围内的任意单个字符;

           a|b: 多选1;多条件判断

            例:

              #!/bin/bash

              read -p "please input a char: " char

              case $char in

               [a-z])

                      echo "A character."

                      ;;

               [0-9])

                      echo "A digit."

                      ;;

               *)

                      echo "A special character."

                      ;;

               esac         

             注意每个分支语句后面跟;;

 

 

循环语句:

 

for VARIABLE in LIST; do

        循环体

done

n  LIST:是一个或多个空格或换行符分隔开的字符串组成;
把列表的每个字符串逐个赋值给VARIABLE表示的变量;

n  进入条件:列表非空;

n  退出条件:列表遍历结束

 

LIST的生成方法:

l  整数列表
     (a) {start..end}
     (b) $(seq [start [[step]] end)

l  直接给出列表

l  glob

l  命令生成

例:命令生成

    1 #!/bin/bash

    2 for i in $(ls);do

   3     echo $i

    4 done

  

例: 整数列表

    1 #!/bin/bash

    2 for i in {1..20};do

    3     echo $i

    4 done

  ------------

    1 #!/bin/bash

    2 for i in $(seq 1 2 20);do

    3     echo $i

    4 done

例:直接给出列表

    1 #!/bin/bash

    2 user1="aa"

    3 user2="bb"

    4

    5 for i in $user1 $user2;do

    6     echo $i

    7 done

例:glob

    1 #!/bin/bash

    2 for i in /root/test/*;do

    3     echo $i

    4 done

 

while循环

         while CONDTION; do

             循环体

             控制变量的修正表达式

         done

 

         进入条件:当CONDITION为“真”;

         退出条件:当CONDITION为“假”;

例:

  1 #!/bin/bash

  2 i=1

  3 while [ $i -lt 20 ];do

  4     echo $i

  5     let i++

  6 done

----------------------

打印99乘法表

  1 #!/bin/bash

  2 i=1

  3 while [ $i -le 9 ];do

  4   j=1

  5      while [ $j -le $i ];do

  6     echo -n -e "${j}x$i=$[$j*$i]\t"

  7     let j++

  8     done

  9     echo -e "\n"

 10     let i++

 11 done

 

 

unitl循环:

         until CONDITION; do

             循环体

             循环控制变量的修正表达式

         done

 

         进入条件:当CONDITION为“假”时

         退出条件:当CONDITION为“真”时

 

例:打印99乘法表

  1 #!/bin/bash

  2

  3 i=1

  4 j=1

  5

  6 until [ $i -gt 9 ];do

  7     j=1

  8     until [ $j -gt $i ];do

  9         echo -e -n "${j}x$i=$[$j*$i]\t"

 10        

 11     let j++

 12     done

 13     echo -e "\n"

 14     let i++

 15 done

       

循环控制:

         continue [n]:提前结束本轮循环,而直接进入下一轮;

         break [n]:提前结束循环;

例:

   01.#!/bin/bash 

   02. 

   03.LIMIT=19  # 上限 

   04. 

   05.echo "Printing Numbers 1 through 20 (but not 3 and 11)." 

   06. 

   07.a=0 

   08. 

   09.while [ $a -le "$LIMIT" ] 

   10.do 

   11. a=$(($a+1)) 

   12. 

   13. if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # 除了3和11. 

   14. then 

   15.   continue      # 跳过本次循环剩余的语句. 

   16. fi 

   17. 

   18. echo -n "$a "   # 在$a等于3和11的时候,这句将不会执行. 

   19.done 

   20. 

   21.echo  

   22.exit 0

执行结果:

1.  Printing Numbers 1 through 20 (but not 3 and 11).

2.  1 2 4 5 6 7 8 9 10 12 13 14 15 16 17 18 19 20

同样的程序改为break,说明break是程序跳出循环,之后的循环过程无法得到执行,break命令还可以带一个参数,一个不带参数的break命令只能退出最内层的循环,而break N可以退出N层循环。参数 N还是少用吧,比较绕。

01.#!/bin/bash 

   02. 

   03.LIMIT=19  # 上限 

   04. 

   05.echo "Printing Numbers 1 through 20 (but not 3 and 11)." 

   06. 

   07.a=0 

   08. 

   09.while [ $a -le "$LIMIT" ] 

   10.do 

   11. a=$(($a+1)) 

   12. 

   13. if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # 除了3和11. 

   14. then 

   15.   break      # 跳过本次循环剩余的语句. 

   16. fi 

   17. 

   18. echo -n "$a "   # 在$a等于3和11的时候,这句将不会执行. 

   19.done 

   20. 

   21.echo  

   22.exit 0

执行结果:

1.  Printing Numbers 1 through 20 (but not 3 and 11).

2.  1 2

 

循环的一些特殊用法:

 

     死循环:

         while true; do

             循环体

             if CONDTION; then

                 break

             fi

         done

 

         until false; do

             循环体

             if CONDITION; then

                 break

             fi

         done

 

         示例:每隔3秒钟查看当前系统上是否有名为“gentoo”的用户登录;

             如果某次查看gentoo登录了,则显示gentoo已经登录;

             如果未登录,就显示仍然未来,并显示这是已经是第多少次查看了;

 

             #!/bin/bash

             #

             username=$1

             declare -i count=0

 

             while true; do

                 if who | grep "^$username" &> /dev/null; then

                     echo "$username is logged."

                     break

                 else

                     let count++

                     echo "$count $username is not login."

                 fi

                 sleep 3

             done

 

    while循环的特殊用法

         遍历文件的每一行:

             while read VARIABLE; do

                 循环体

             done < /PATH/FROM/SOME_FILE

 

         示例:找出UID为偶数的所有用户,显示其用户名和ID号;

             #!/bin/bash

             #

             while read line; do

                 userid=$(echo $line | cut -d: -f3)

                 if [ $[$userid%2] -eq 0 ]; then

                     echo $line | cut -d: -f1,3

                 fi

             done < /etc/passwd

 

     for循环的特殊用法(C语言风格):

         for ((expr1;expr2;expr3)); do

             循环体

         done

 

         expr1: 定义控制变量,并初始赋值;

         expr2: 循环控制条件;

             进入条件:控制条件为“真”

             退出条件:控制条件为“假”

         expr3: 修正控制变量

 

        示例:求100以内所有正整数之和;

             #!/bin/bash

             #

             declare -i sum=0

 

             for ((i=1;i<=100;i++)); do

                 let sum+=$i

             done

 

             echo "Sum: $sum."

 

 

Bash也支持函数:function:

         把一段具有独立功能代码封装在一起,并给予命名;后续用到时,可直接通过给定函数名来调用整体代码;可实现代码重用,模块化编程

 

函数格式:

   function func () {

     statementsreturn 1;

   }

 

函数返回值

 

1、如果使用函数返回值,return只能返回一个整数(0~255),不能返回字符串,且返回值保存在$?变量中,不能直接赋值给其它变量例如,下面获得函数返回值的写法是错误的

   function func () { return 3; }

   i=`func`

2、如果return没有指定参数,则是最后一行脚本的退出状态值

3、如果要将函数返回值赋值给一个变量,有两种方式:

     a)用$?赋值

         funci=$?

 

     b)在函数中,用echo打印返回值,再赋值

         function func () { echo 3; }

         i=`func`

 

函数参数

 

1、向函数传递的参数被当作位置参量来处理,在函数中是本地变量2、函数参数用$1, $2 ,..., $n来表示,但和通过命令行传递给脚本的参数不同。调用方式如下:

   func param1 param2

 

函数中的变量

 

1、在一个shell中的变量无局部和全局之分,随用随声明,无作用域的概念。例如,在一个if...fi块中定义的变量,出了这个块的作用域仍然有效。所以我们尽量在函数中定义局部变量,使用local来定义

         func() {

             local count

             echo $count

         }

 

函数调用

 

1、使用function只是定义函数,要执行函数中的命令必须在脚本中或命令行上调用函数,例如: func param1 param2

 

   a) 将函数单独放入一个脚本里,再在命令行上执行脚本(直接执行,或使用.,或source),是不会执行函数里的命令的

   b) 将函数单独放入一个脚本,然后执行,相当于在执行该脚本的shell环境中定义了该函数

 

            例如:下面的命令只是在shell环境中定义函数,并不会调用函数

                ./func_script.sh

               或

                source ./func_script.sh

 

2、函数可以递归:函数可以自己调用自己,调用次数没有限制

3、函数中使用exit命令退出整个脚本。

 

一些函数相关的命令

1、查看定义了哪些函数

   declare -f

   declare -F //只列出函数名

2、撤消函数定义unset func_name

3、将函数输出给子export -f func_name 

 

脚本调试的一些技巧:

     bash    -n: 检查脚本中的语法错误;

     bash    -x:调试执行脚本;


你可能感兴趣的:(linux,shell,bash,for)