在上一章当中我们讲述了文件系统的管理,以及介绍了一些管理工具的命令,我们现在来总结以下:

   管理工具:mkfs, mke2fs, e2label, tune2fs, dumpe2fs, e2fsck, blkid
      mkfs.xfs, mkfs.vfat, fsck
      mkswap, swapon, swapoff
      mount, umount
      df, du

  之后我们也讲述了/etc/fatab文件的作用以及该配置文件的格式,但凡写在该配置文件中的文件系统,开机时能够自动挂在完成,那么该配置文件的内容格式为:

   设备    挂载点  文件系统类型    挂载选项    转储频率    自检次序

  我们之前也讲过文件系统,在上一次讲到文件系统时,首先目录它也是一个文件,它有元数据和数据,元数据就是一个inode,存放在inode table中,而该文件的数据存储于data blocks中,每一个data blocks也有其相应的编号,每一个文件在inode当中除了有自己的属性之外,还有数据块指针,指向某一个数据块,顺着这个指针可以找到某个数据块,最后能找到其相关的对应数据,而文件名也是存放在上级目录,而在data blocks中,存放着的是下级文件或目录的文件名与其inode的对应关系,形成路径映射(dentry)。

   文件系统:
     目录:文件
        元数据:inode, inode table(inode存储于inode table中)
        数据:data blocks
            下级文件或目录的文件名与其对应的inode之间的关系;
            
            dentry
            
     文件名:上级目录;

  我们现在补充一下删除一个文件就是将data block标记为未使用,然后也将inode也标记为未使用。

   删除文件:将此文件的inode指向的所有data block标记为未使用状态;将此文件的inode标记为未使用;

  而复制和移动,无论是文件或目录,我们也来总结一下:

   复制和移动文件:
     复制:新建文件;
     移动文件:
         在同一文件系统:改变的仅是其路径;
         在不同文件系统:复制数据至目标文件,并删除原始文件;

  我们最后在回顾一下符号链接,符号链接是一种特殊的文件,权限很大,但这不代表指向的原文件会是该链接的权限,所以我们来总结一下:

   符号链接:
     权限:lrwxrwxrwx
        
         符号链接的特点其权限最大为(777),之所以有该权限是因为用户访问时,其实并不取决于该符号链接的访问,而是取决于所指向的目标文件的权限;

  而硬连接则是指向同一个inode,在同一个分区中施行,也会增加文件本身的引用次数,删除一个硬链接并不耽误源文件的正常使用,只不过就是引用次数减去一个而已。

一、bash脚本编程

  我们此前也也讲过bash脚本编程的基础,首先,该脚本问价的格式为在第一行顶格处写上#!/bin/bash我们称之为shebang,如果要是其它的shell脚本可以写其它的shell解释器的路径即可,在脚本中,所有的注释信息用#来标明,加上注释信息的好处在读该脚本时,提示你这个脚本的某一个片段,或者说对整个脚本的功能进行一个了解,为了代码能够美观以及层次分明,我们还可以进行缩进,程序的关联度不太大时,适度的添加空白行,不然的话,代码的凌乱让人为读写的话会很麻烦;这也是一种非常好的习惯。

   脚本文件格式:
     第一行,顶格:#!/bin/bash
     注释信息:#
         如果是其它shell的话,就写其它shell的路径解释器;
     代码注释;
     缩进,适度添加空白行;

  shell脚本也几乎是很简单的一种,与其它语言不同的是,其它的专业语言不止要学习该编程本身的语法格式,还要了解及学习大量的库调用。对于面向对象的编程语言来说,Java有很多的类库,Python也有很多的标准库,为了完成该程序上的某些功能,就要调用该某个库中的功能模块。之后,我们还要了解算法和数据结构,不同的编程语言所支持的算法和数据结构也是不同的,而编程也有其编程的思想,我们将学到的该编程的语法格式,转换为提供解决问题的解决思路。所以,在编程思想的当中,就形成了问题空间和解空间,问题空间是由自然语言提供的,那么解空间就是将自然语言利用专门的编程语言解决这个问题空间中的问题。

   语言:编程语法格式,库,算法和数据结构;
   编程思想:
     问题空间 --> 解空间;

  我们此前讲过脚本基础的语法格式,以及如何编写脚本编程,同时也讲到了变量,几乎每一种编程语言都是离不开变量的,虽然shell并没有什么内置复杂的数据结构,但这也并不妨碍人为的参与组织出一个复杂的数据结构。但是为了组织出复杂的数据结构,都需要一些内建的数据存储机制,比如说变量,还有就是多个变量组织在一起的数据结构,比如说数组。那么变量就是最基本的数据机构,它也是有名字的一种内存空间,而变量则是由小到大进行划分,有以下几种:

   变量:
     局部变量;
     本地变量(当前shell);
     环境变量(当前shell及其子shell);
     (按照作用域划分);
    
     位置参数变量;
     特殊变量;

  之后我们也讲到了变量中的数据类型,不过需要注意一点,shell并不支持浮点数进行运行,只能借助于其它工具来进行实现。shell是一种弱类型的语言,弱到把所有的数据类型都能处理为字符型。

   数据类型:字符型, 数值型;
     弱类型:字符型;

  我们查看一下$PATH的值,在这个值的基础上添加一个新的路径,方法如下:

# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin

  现在添加一个新的值,例如:/usr/local/apache2/bin/目录:

# PATH=$PATH:/usr/local/apache2/bin/
# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/usr/local/apache2/bin/

  将原来的值补上一个新的值,使用echo命令发现,在原有的值中,添加了一个新的值,这也是一种变量引用,将$PATH空间中的值拿出来,做一些处理之后在放进该空间当中;这也是一种引用之后重新赋值的一种机制。
  我们也在后面讲到了算数运算,有以下表示方式:

   算数运算:
     +, -, *, /, %, **

     let VAR=expression
     VAR=$[expression]
     VAR=$((expression))
     VAR=$(expr argu1 argu2 argu3)

     注意:有些时候乘法符号需要转义;

  在变量的应用当中,也有变量赋值,在shell当中也支持增强型赋值,所谓增强型赋值就是自身=自身+数值,示例如下:
  首先,声明一个×××变量i,该变量的值为1;然后在该值的基础上,加1。

# declare -i i=1
# let i=$i+1
# echo $i
2

  这是一种平常的方式,我们还有另一种方式,方法如下:

# let i+=1
# echo $i
3

  那么我们总结一下增强型赋值:

   增强型赋值:   
     变量做某种算数运算后回存至此变量中;
        let i=$i+#
        let i+=#
        
    +=, -=, *=, /=, %=

  那么以上的方法,我们使用let来进行描述,要shell脚本当中。我们还有一种更为简洁的写法,就是自增和自减的运算。

   自增:
     VAR=$[$VAR+1]
     let VAR+=1
     let VAR++
    
   自减:
     VAR=$[$VAR-1]
     let VAR-=1
     let VAR--

二、脚本测试

  我们如果判断一个用户是否存在的话,有很多种方法,如果存在的话做什么样的处理,反之不存在做什么样的处理。所以说条件测试是判断某需求是否满足,需要由某种测试机制来实现。

   条件测试:
     判断某需求是否满足,需要由测试机制来实现;

  专业的测试表达式,需要由测试命令来进行完成的,那么如何编写测试表达式以实现所需要的测试,则方法有以下两种:

   (1) 执行命令,并利用命令状态返回值来判断;
     0:成功
     1-255:失败

  我们如果去判断2是否大于3,肯定在纸上写2>3,但在shell脚本中,这样写肯定是会报错的,因为shell不能识别该命令以及该字符是否为表达式,我们需要专门的测试表达式来对表达式进行运算。

   (2) 测试表达式
      test EXPRESSION
     [ EXPRESSION ]
     [[ EXPRESSION ]]
    
     注意:EXPRESSION两端必须有空白字符,否则为语法错误;

  需要注意的是,判断一个表达式时,><在shell脚本中并没有什么意义,所以,在shell中我们需要专门的测试条件来对表达式进行一个比较条件。
  在bash的测试条件的类型共有三种:

   bash的测试类型:
     数值测试
     字符串测试
     文件测试

  在这几种测试当中,首先来说一下数值测试:

   数值测试:数值比较;
     -eq:是否等于;[ $num1 -eq $num2 ]
     -ne:是否不等于;
     -gt:是否大于;
     -ge:是否大于等于;
     -lt:是否小于;
     -le:是否小于等于;

  第二种为字符串测试,字符串测试也是一样的道理,,不过通常可以用来判断字符串是否为空等。不过需要注意的是,字符测试中,如果没有该变量,则用双引号引用形成一个空的字符串,不然会直接报错,而且要用双中括号来对字符进行测试表达。

   字符串测试:
     ==:是否等于;
     >: 是否大于;
     <: 是否小于;
     !=:是否不等于;
     =~:左侧字符串是否能够被右侧的PATTERN所匹配;
    
     -z "STRING":判断指定的字符串是否为空;不则为真,不空则假;
     -n "STRING":判断指定的字符串是否不空;不空为真,空则为假;
    
     注意:
         (1) 字符串要加引号引用;
         (2) 要使用[[ ]]

  第三种就是文件测试,主要是实现的功能就是对于文件的存在性、权限等相关测试。一般来说文件测试与之前数值测试与字符串测试是有所不同的,数值与字符串测试主要还是用到的
  是双目测试符号,即两个中括号,而使用-z和-n的时候,则用单目测试符。对于文件测试来说,常见的一类就是存在性测试。

   文件测试:
     存在性测试:
        -a FILE
        -e FILE
            文件存在性测试,存在则为真,否则为假;

     存在性及类型测试:
        -b FILE:是否存在并且为 块设备 文件;
         -c FILE:是否存在并且为 字符设备 文件;
        -d FILE:是否存在并且为 目录 文件;
        -f FILE:是否存在并且为 普通 文件;
        -h FILE 或 -L FILE:是否存在并且为 符号链接 文件;
        -p FILE:是否存在并且为 命令管道 文件;
        -S FILE:是否存在并且为 套接字 文件;
    
    文件权限测试:
        -r FILE:是否存在并且为 当前用户 可读;
        -w FILE:是否存在并且为 当前用户 可写;
        -x FILE:是否存在并且为 当前用户 可执行;
        
    特殊权限测试:
        -u FILE:是否存在并且为 拥有suid权限;
        -g FILE:是否存在并且为 拥有sgid权限;
        -k FILE:是否存在并且为 拥有sticky权限;
        
    文件是否有内容:
        -s FILE:文件是否存在且非空(是否有内容);
        
    时间戳:
        -N FILE:文件自从上一次读操作后是否被修改过;
        
    从属关系测试:
        -O FILE:当前用户是否属于 文件的属主;
        -G FILE:当前用户是否属于 文件的属组;
        
    双目测试:
        FILE1 -ef FILE2:FILE1和FILE2是否指向同一个文件系统相同的inode的硬连接;
        FILE1 -nt FILE2:FILE1 是否新于 FILE2;
        FILE1 -ot FILE2:FILE1 是否旧于 FILE2;

  我们此前还讲过组合测试条件,分别为:

   组合测试条件:
     逻辑运算:
         第一种方式:
             COMMAND1 && COMMAND2
             COMMAND1 || COMMAND2
             ! COMMAND

  我们可以测试一下该文件是否为文件属主,并且可读。

   [ -O FILE ] && [ -r FILE ]

  那么第二种方式就是用于以上那几种测试的,编写成测试表达式,而不是命令。

        第二种方式:
            EXPRESSION1 -a EXPRESSION2
            EXPRESSION1 -o EXPRESSION2
            ! EXPRESSION

  测试该文件是否属于当前用户属主,并且拥有执行权限。

   [ -O FILE -a -x FILE ]

  练习:将当前主机名保存至hostName变量中,如果主机名为空,或者localhost.localdomain,则将其设置为node1.china.org
  需要注意的是,如果要做条件连接的话,单中括号要靠谱一点,因为使用双中括号会报语法错误。
  对于shell编程来讲,每一个命令,它都有一个所谓的状态返回值,0表示为真,非0表示为假,而即使不是命令表达式,我们可以添加一个test命令等表达式,也能返回0或非0的状态返回值,同时,我们也可以自定义状态返回值。

   脚本的状态返回值:
     默认是脚本中执行的最后一条命令的状态返回值;
     自定义状态退出状态码:
         exit [n]:n为自己指定的状态码;
             注意:shell进程遇到exit时,即会终止,因此整个脚本执行即为结束;

三、向脚本传递参数

  我们在刚才讲到了变量共有五种类型,那么接下来我们主要讲解的就是位置参数变量和局部变量。

3.1 位置参数变量

  向脚本传递某个参数就是用位置参数变量来实现的,讲某个参数传递给命令参数进行处理,我们就称之为位置参数,对于shell来讲,的确是通过位置来引用的。假如我们在写了一个脚本,给它传递了两个参数,那么我们可以在脚本中直接引用,只要是用户传递的我们就可以在脚本中直接引用该参数。而引用方式就是使用$1和$2等来实现的,当我们运行该脚本时,参数1会自动保存到$1当中。同样,参数2也是,以此类推。不过需要注意的是,参数十多个的话,要想使用10以上的参数(包括10),需要在10以上的数字加上一个大括号,例如;${10}。

   位置参数变量:
    
     myscripts.sh argu1 argu2
         引用方式:
             $1, $2, ..., ${10}, ${11}

  除此之外,位置参数变量还可以做轮替,所谓的轮替就是第二个参数可以踢掉第一个参数,以此类推。默认的话是一次踢掉一个,每一次shift就能踢掉以前的对应的第一位的参数。

#!/bin/bash
#
echo "Please input Arguments: " $1 $2 $3

shift 2

echo "The First Arguments: " $1

3.2 特殊变量

  特殊变量,其实就是就被赋予特殊的意义,那么具体我们来介绍一下。

   特殊变量:
     $0:脚本文件路径本身;我们可以区其基名或目录名,例如:basename $0
     $#:脚本参数的个数;
     $*:所有参数;
     $@:所有参数;

  如果在刚才的例子中,用户只给了一个参数,运行的话则会卡住不动,所以我们就得判断一下用户的参数如果小于2个,则就给予提示错误并退出;

   [ $# -lt 2 ] && echo "At least two arguments." && exit 1

  这就是一种判断语句,但是这样的话有些人会不理解,那么接下来我们写一个真正的判断语句。

四、过程式编程代码执行程序

  对于过程式的编程语言执行分为三部分,第一个是顺序执行,也称为逐条执行,自上而下一个挨着一个的进行。另一种就是选择执行,选择执行就是在代码上存在一个或多个分支,执行时只执行其中一个。还有一种就是循环执行,是将某一个代码片段要执行0次、1次或多次,通过这以上三种执行机制,就可以设计算法来完成一些复杂的代码的功能。

   过程式编程语言的代码执行顺序:
     顺序执行:逐条执行;
    
     选择执行:
         代码有一个分支;条件满足时才会执行;
         两个或以上的分支;只会执行其中一个满足条件的分支;
        
     循环执行:
         代码片段(循环体)要执行0、1或多个来回;

  我们现在主要讲的是选择执行,对于每一种编程语言来说都会用到,循环也亦是如此,在选择执行中,如果这个测试条件为真,则执行该条件内为真的的程序代码。测试条件为假时,则跳过执行该分支语句,以上这是单分支的if语句。
  那么是双分支的if语句也是基本相同,当条件为真时,则执行该条件内为真的程序代码;测试条件为假时,则执行在该条件为假的程序代码。

   选择执行:
     单分支的if语句:
         if 测试条件; then
             代码分支
         fi
    
     双分支的if语句:
         if 测试条件; then
            条件为真时执行的分支
         else
            条件为假时执行的分支
         fi

  示例:通过参数传递一个用户名脚本,此用户不存在时,则添加。

#!/bin/bash
# Author: liuxiangyu8686@gmail.com
# Descriptions: Add username
# Date: 2018-02-06

[ -z "$1" ] && echo "Please add username." && exit 2

if ! id -u $1 &> /dev/null; then
    useradd $1 &> /dev/null
    echo "$1" | passwd --stdin $1
else
    echo "$1 extsis."
fi

  需要注意的是,只有写表达式时才会用到表达测试符,而用命令作为测试条件的话则不用。