Shell scripts

Shell scripts类似于windows下的批处理文件,可以批量执行shell的命令,除了“将许多命令整合在一起”,shell脚本还能提供循环,条件/逻辑判断等功能,用于实现复杂一些的功能。

需要注意的一些点

  1. 指令执行从上到下,从左向右分析与执行
  2. 指令与选项间的多个空白会被忽略
  3. 空白行会被忽略
  4. 读取到一个Enter符号,就会开始尝试执行
  5. 如果一行钟内容太多,可以使用 Enter 扩展至下一行。
  6. #可作为注释使用
  7. 脚本第一行的!#/bin/bash 用于指明这个脚本使用的shell的名称,程序在执行的时候会载入bash相关的环境配置文件。没有这一行的话,脚本可能会因为无法找到合适的shell而无法执行。

编写脚本的一些建议:

  1. 记得在第一行指明需要使用的shell
  2. 程序内容与用途的说明

    如功能介绍,使用方法,历史版本信息等等

  3. 环境变量宣告 (之后我们可以了解到,一般情况下,脚本是在新的子shell钟执行的,所以这里设置环境变量也不会影响到父shell)

另外因为不同的执行方式,脚本的权限要求可能也不同,如果要使用直接指令来执行 (如绝对路径 相对路径 PATH环境变量下的脚本),脚本需要有rx权限。

而使用bash s.sh时,只需要能够读取脚本文件(r权限)即可。

编写shell脚本

shell脚本的教程已经有很多了,所以这里不做详细的记录,只记录我在看书中发现的一些我个人认为值得单独记录一下的知识点。

  • 交互式脚本

    使用read 命令可以使得脚本以“和用户对话”的方式获取参数并运行。

  • 使用命令行中的参数

    有时候我们希望脚本可以根据执行脚本时设置的命令行参数自动执行(一次读取所有的参数),而不需要等待用户的逐次输入。这就需要用到默认变量。 $0 $1 $2...

    $# 代表之后的参数的个数,从$1开始计算

    [email protected] 代表 "$1" "$2" "$3" ,注意,每个变量是独立的

    $* 代表 "$1c$2c$3" c是分隔符(默认空白),变量是放在一串里面的

    $0 表示的是程序/脚本的文件名

    shift命令可以使参数整体左移,即$0的值变成之前的$1(虽然还不知道有什么用)

  • 数值运算

    如果我们要进行数值的运算,需要使用declare -i来指定变量的类型是数字,另外我们也可以使用$(())的方式来指明括号中的符号串进行的是数值运算 $((${firstnum}*${secondnum})),bash shell默认只支持整数的运算,如果需要使用小数,可以用到bc等命令。

脚本执行方式的差异

不同的运行脚本的方式造成的结果会有差异,尤其是对bash环境的影响。

直接执行的方式运行脚本,无论是直接使用路径来下达命令,还是使用bash命令来运行脚本,都属于直接执行,脚本会创建一个新的子shell来执行命令,所以脚本读取不了父shell中的变量以及对shell环境变量的修改是影响不了父shell的。但是我们有时候需要使用脚本来进行系统环境变量的设置,这种情况就不能使用直接执行的方式了。

使用source来执行脚本,我们有时候修改了系统配置,希望能够不重新登录直接生效时,会用到source命令,因为source执行命令时是在父程序中直接执行的,所以脚本对环境变量的修改也会直接生效。

脚本语句

判断式

在shell中变量$?代表着上一条命令的返回值,我们可以使用&&(返回值为0继续执行)与|| (返回值为1继续执行)来依据前一个语句的执行结果判断是否要执行下一语句。

我们可以配合test命令来使用判断。该指令可以用来测试一个文件是否存在,权限,比较,文件类型等等。

#命令的参数
-e 文件是否存在
-f “文件名”对应的是否是文件
-d “文件名”是否存在且为目录
-b 是否存在并且是块设备
-c 是否存在并且为字符设备
-S 是否存在并且为Socket文件
-p 是否存在并且为pipe文件
-L 是否存在并且为链接文件
#关于文件权限的检测 下面的命令都有检测文件“是否存在的”功能,所以不重复写了
-r 是否具有可读权限
-w 是否具有可写权限
-x 是否具有可执行权限
-u 是否具有SUID属性
-g 是否具有SGID属性
-k 是否具有Sticky bit的特性
-s 是否时“非空白文件”
# 两个文件的比较
-nt test f1 -nt f2 判断f1是否比f2新
-ot 判断一个文件是否比另一个文件旧
-ef 判断是否时同一文件(判断hard link 是否指向同一inode)
# 证书之间的比较
-eq
-ne
-gt
-lt
-ge
-le
#判断字符串
-z 是否为空串
-n 是否为非0 (不是空串则返回真)
== test str1 == str2 判断str1是否等于str2
!=
#多重条件
-a and 两状况同时成立 test -r file -a -x file
-o 两状况任何一个成立
! 反相 test ! -x file

除了使用test,我们还可以使用判断符号[],flag类似于test,如判断一个变量是否为空

[ -z "${HOME}" ]

注意,在bash中使用中括号作为判断式时,中括号的两端要有空白字符分隔,另外,中括号中的变量最好用双引号括起,常数用单/双引号括起来。

双引号很重要,因为如果变量中包含空格,如果没有引号括起来,之后的判断会出错,空格分隔变成了多个参数。

条件判断式

  1. if...then

    if [ 条件判断式 ]; then
        执行的操作
    fi
    还可以使用&&于||使用多个条件判断式 [ 判断式1 ] || [ 判断式2 ]
  2. if else

    if [ 条件判断式 ]; then
        执行操作
    else
        不成立时执行的操作
    fi
  3. elif

    if [ 条件判断式 ]; then
        执行操作
    elif [ 条件判断式 ]; then
        条件二成立时执行的操作
    else
        都不成立时进行的操作
    fi
  4. case

    #如果有多个既定变量内容还可以使用case来进行判断选择
    
    case $变量名称 in
        "第一个变量内容")
            程序段
            ;;
        "第二个变量内容")
            程序段
            ;;
        *)
            都没匹配上时执行的语句
            ;;
    esac

函数

这本书里面对函数的解释貌似有点过于简单了,之后用到了更多的内容再补充进来。

function fname () {
    程序段
}

注意,函数中也有内置变量 $1 $2 $3等,但是他们和脚本的内置变量是不同的,如 fname word, 函数段内的$1的值为word。

循环

  1. 不定循环 while do done, until do done

    属于循环次数一开始不知道,要到满足特定条件的时候才结束循环

    while [ condition ]
    do
        程序段
    done
    
    until [ condition ]
    do
        程序段
    done

    whilie和until的区别就是,while在条件为真的时候会持续执行(为假的时候退出循环),而until则是条件不成立的时候持续执行。

  2. 固定循环 for...do...done

    使用for一般我们已经知道要执行几次循环了。for有多种写法

    for var in con1 con2 con3
    do
        程序段
    done

    这种写法,var会依次以不同的值进入循环

    for (( 初始值;限制值;执行步阶 ))
    do
        程序段
    done

    debug

    我们可以使用命令检查脚本是否存在错误

    bash -nvx script.sh

    -n 不执行脚本,仅查询语法问题

    -v 将脚本内容输出到屏幕上

    -x 将用到的脚本内容输出到屏幕上

脚本记录

这里记录我写的一些脚本,便于之后的重复使用,也算作为实践的例子。

网站数据库自动备份上传脚本

#!/bin/bash
filename="backup_`date '+%Y%m%d%H%M%S'`.tar"
#lftp设置 (使用sftp上传备份到远程服务器)
IP=用来储存备份的远程服务器ip
PORT=22
USER=backupman
PASSWORD=backup
FILE=./${filename}
UPLOADDIR=/home/backupman/backup/
rm -rf /tmp/backup
mkdir /tmp/backup
mkdir /tmp/backup/sql
#notesil
mysqldump -u typecho -ppasswd notetp > /tmp/backup/sql/notesail.sql

tar -zcvf /tmp/backup/sql.tgz -C /tmp/backup sql
#注意,为了只保留网站目录而不包含上级目录,这里使用了-C 相对路径的写法
tar -zcvf /tmp/backup/notesail.tgz -C /var/www/html notesail
tar -cvf ./${filename} -C /tmp/ backup
lftp -u ${USER},${PASSWORD} sftp://${IP}:${PORT} <<EOF
cd ${UPLOADDIR}
put ./${filename}
by
EOF
文章目录