前言:

问题一:什么是shell?

答:从程序员的角度来看, Shell本身是一种用C语言编写的程序,从用户的角度来看,Shell是用户与Linux操作系统沟通的桥梁。用户既可以输入命令执行,又可以利用 Shell脚本编程,完成更加复杂的操作。在Linux GUI日益完善的今天,在系统管理等领域,Shell编程仍然起着不可忽视的作用。深入地了解和熟练地掌握Shell编程,是每一个Linux用户的必修功课之一。

    目前最流行的Shell称为bash Shell,bash Shell脚本编程以其简洁、高效而著称,多年来成为Linux程序员和系统管理员解决实际问题的利器。


问题二、为什么要使用shell脚本?

答:使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象。之所以要使用shell脚本是基于:

    简单性:shell是一个高级语言,通过它,你可以简洁地表达复杂的操作。

    可移植性:使用POSIX所定义的功能,可以做到脚本无须修改就可以在不同的系统上执行。

    开发容易:可以在短时间内完成一个功能强大有好用的脚本。


一、初识shell脚本编程

利用vim文本编辑器编写Shell脚本的格式是固定的,如下:

#!/bin/bash

#This is a shell file

echo "Hello World!"

第一行一定要使用#!指明系统需要哪种shell解释用户的shell程序,如:#!/bin/sh,#!/bin/bash,#!/bin/csh,#!/bin/tcsh和#!/bin/ksh等。如果首行没有这句话,在执行脚本文件的时候,将会出现错误。

除首行外,以#开头的行就是注释行。

后续的部分就是主程序,Shell脚本像高级语言一样,也有变量赋值,也有控制语句。

通常shell脚本多以.sh为后缀。

执行shell的方式有两种:第一种是为shell脚本加上可执行权限并执行,第二种是通过bash命令执行shell脚本。


二、shell的基本语法知识

1、变量类别

    (1)本地变量只对当前shell进程有效的变量;对其它shell进程无效,包当前shell进程的子进程;

     VAR_NAME=VALUE

     变量赋值:向变量的存储空间保存数据

     变量引用:${VAR_NAME}

     "":弱引用,里面的变量会被替换;

     '':强引用,里面的所有字符都是字面量,直接输出;


    (2)环境变量:对当前shell进程及其子shell有效,对其它的shell进程无效;

     定义:export VAR_NAME=VALUE

       导出:export VAR_NAME 

    (当定义了一个本地变量,要在当前shell的子shell进程中使用,可用导出:               export VAR_NAME) 

     撤消变量:unset VAR_NAME(可撤销本地变量和环境变量) 

      设置只读变量:readonly VAR_NAME(标志变量为不可改变的,即只读变量)


    (3)局部变量:shell脚本中某代码片断有效;通常用于函数本地;

      local VAR_NAME=VALUE(当函数调用结束,局部变量会随之消失。也能用unset 撤销局部变量。)


    (4)位置变量

      $1, $2, ..., ${10}(当两位数或超过两位数时,最好用{}括起来)


    (5)特殊变量 

               $#:  传递到脚本的参数个数

     $*:  以一个单字符串显示所有向脚本传递的参数

     $$:  脚本运行的当前进程ID号

     $!:  后台运行的最后一个进程的进程ID号

     $@:  与$*相同,但是使用时加引号,并在引号中返回每个参数

     $-:  显示shell使用的当前选项,与set命令功能相同

     $?:  显示最后命令的退出状态,0表示成功,其他任何值都表示有错误


    2、变量赋值

    变量=

        任何变量无需事先声明,可直接使用

        值默认都是字符型

        例如:a=abc, b=3

        a=3(不能写成 a = 3 ,即中间不能有空格。因在bash中是基于命令行的,中间有空格,bash会把其后的 和 看做选项或参数)

    赋值:

        a=4

    增强型赋值:

        +=, -=, *=, /=, %=

    a=$[$a+1] 相当于 let a+=1  (let a+=1相当于let a=$a+1

    自加:var++, var--, ++var, --var

    

    3、算术运算 

    (1)如何定义整型变量

    let VAR_NAME=INTEGER_VALUE

        例如:let a=3 

    declare -i VAR_NAME=INTEGER_VALUE

        例如:declare -i a=3 

    注意:即使没有定义为整型变量,字符型的数字依然可以参与算术运算;bash会执行变量类型的隐式类型转换;

  

    (2)实现算术运算的方式

    let VAR_NAME=ARITHMATIC_EXPRESSION

    ( 使用let进行算术运算,要使用结果,必须把运算结果存储在变量中,再去引用变量(不可用` `引用该命令的执行结果) )

    VAR_NAME=$[ARITHMATIC_EXRESSION]

    (不是命令,而是表达式)

    VAR_NAME=$((EXPRESSION))

     (不是命令,而是表达式)

    VAR_NAME=$(expr $num1 + $num2)  

    (使用expr进行算术运算,可用` `引用该命令的执行结果)


    (3)算术运算符

    + :加

    - :减

    * :乘

    / :除

    %:取模,取余数

    **: 次方,如: 2**2 


   4、条件测试 

    命令执行成功与否即为条件测试

        test EXPR

        [ EXPR ]

        [[ EXPR ]] 

    比较运算:

        >, <, >=, <=, ==, !=

     

    (1)测试类型:根据比较时的操作数的类型

             整型测试:整数比较

             字符测试:字符串比较

             文件测试:判断文件的存在性及属性等

           注意:比较运算通常只在同一种类型间进行

     

    (2)整型测试

        -gt: 大于,例如 [ $num1 -gt $num2 ]

        -lt:  小于

        -ge:  大于等于

        -le:  小于等于

        -eq:  等于

        -nq:  不等于

     

    (3)字符串测试

        双目

            >      大于,例如[[ "$str1" > "$str2" ]]

            <      小于

            >=     大于等于    

            <=     小于等于

            ==     等于

           !=     不等于

        单目:

              -n String: 是否不空,不空则为真,空则为假

              -z String: 是否为空,空则为真,不空则假     

        字串测试中的模式匹配:

           [[ "$var" =~ PATTERN ]]


    (4)文件测试: 

          -a FILE :存在为真,否则为假;

          -e FILE: 存在则为真;否则为假; 

        -f FILE: 存在并且为普通文件,则为真;否则为假;

        -d FILE: 存在并且为目录文件,则为真;否则为假;

        -L/-h FILE: 存在并且为符号链接文件,则为真;否则为假;

        -b: 块设备

        -c: 字符设备

        -S: 套接字文件

          -p: 命名管道 

        -s FILE: 存在并且为非空文件则为值,否则为假;

        -r FILE :是否可读

        -w FILE :是否可写

        -x FILE :是否可执行

 

         file1 -nt file2: file1mtime新于file2则为真,否则为假;

        file1 -ot file2:file1mtime旧于file2则为真,否则为假;


     5、if语句 

    (1)单分支的if语句:
            if 测试条件; then
                 选择分支
            fi
     表示条件测试状态返回值为0时,则执行选择分支
         
         或者另外一种格式:
            if 测试条件
            then
                 选择分支
            fi
     
     (2)双分支的if语句:
        if 测试条件; then
            选择分支1
        else
            选择分支2
        fi 
      两个分支仅执行其中之一。
      
     (3)多分支的if语句:
        if 条件1; then
            分支1
        elif 条件2; then
            分支2
        elif 条件3; then
            分支3
        ...
        else
            分支n
        fi

     5、case语句

    case 语句:有多个测试条件时,case语句会使得语法结构更明晰
    
    格式:
    case 变量引用 in
    PATTERN1)
        分支1
        ;;
    PATTERN2)
        分支2
        ;;
    ...
    *)
        分支n
        ;;
    esac
     
    PATTERN:类同于文件名通配机制,但支持使用|表示或者;
    a|b: a或者b
    *:匹配任意长度的任意字符
    ?: 匹配任意单个字符
    []: 指定范围内的任意单个字符 (区分字母大小写)
    t-size:14px; font-family:'宋体'; " >**: 2**2
    
    
    (分支后的“;;”表示分支结束,不再往下执行,相当于Java中的switch 中的break。如果进入分支1,不加“;;”,则会继续往下执行。)
    (注意:在bash编程中,case的分支中必须加上“;;”,如果不加“;;”或仅加了“;”,会有语法错误!!!)

     6、for语句

    格式:
        for VAR_NAME in LIST    
        do
            循环体
        done 
        
    或者可以写成:
        for VAR_NAME in LIST ;do
            循环体
        done 
    
    LIST:列表,中间包括一个或多个元素
     
    退出条件:遍历结束
    
    for的第二种使用格式 :
        for ((初始条件;测试条件;修改表达式)); do
            循环体
        done

    7、while语句

    while  测试条件; do
        循环体
    done
     
    如测试结果为“真”,则进入循环;退出条件为,测试条件为假;

    8、until语句

    until 测试条件; do
        循环体
    done
     
    如果测试结果为“假”,则进入循环;退出条件为,测试条件为真;

     9、函数   

    (函数要先定义,然后才能使用) 

    语法:    
        两种格式:
        function F_NAME {
            函数体
        }
         
        F_NAME() {
            函数体
        }

    可调用:使用函数名

        函数名出现的地方,会被自动替换为函数; 


函数中的变量

(1)本地变量:作用域为整个bash进程
          varname=value
   (2)局部变量:作用域只对当前代码段有效(只在函数中有效)
          local varname=value (使用local关键字定义)

(3)环境变量:作用域为当前shell进程及其子进程
          export varname=value 


    函数的返回值

        函数的执行结果返回值:代码的输出

        函数中的打印语句:echo, print

        函数中调用的系统命令执行后返回的结果

    执行状态返回值

        函数体中最后一次执行的命令状态结果

        自定函数执行状态的返回值:return #

  

    函数可以接受参数

        在函数体中调用函数参数的方式同脚本中调用脚本参数的方式:位置参数

        $1, $2, ...

        $# :参数个数 

         $*, $@ :取所有参数