一、Shell简介

Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。
Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。
Shell 脚本(shell script),是一种为 shell 编写的脚本程序。
业界所说的 shell 通常都是指 shell 脚本,但朋友们要知道,shell 和 shell script 是两个不同的概念。
Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止甚至编写一些程序。
Linux 的 Shell 种类众多,常见的有:

  1. Bourne Shell(/usr/bin/sh或/bin/sh)
  2. Bourne Again Shell(/bin/bash)
  3. C Shell(/usr/bin/csh)
  4. K Shell(/usr/bin/ksh)
  5. Shell for Root(/sbin/sh)
  6. ……

Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数Linux 系统默认的 Shell。
在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash
#! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

二、Shell脚本的执行方式

脚本格式要求

  1. 脚本以#!/bin/bash开头
  2. 脚本需要有可执行权限

    编写第一个Shell脚本——创建一个Shell脚本,输出hello world!

  3. 创建hello.sh文件:vim hello.sh

    1. #!/bin/bash
    2. echo "hello world!"
  4. 给hello.sh文件执行权限:chmod u+x hello.sh

  5. 执行hello.sh:./hello.sh

不给hello.sh文件执行权限也可以通过sh执行:sh hello.sh
#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。
echo 命令用于向窗口输出文本。

三、Sheel的变量

Shell变量介绍

  1. Linux Shell中的变量分为:系统变量和用户自定义变量;
  2. 系统变量:$HOME、$PWD、$SHELL、$USER等等,
  3. 显示当前shell中所有的变量:set

    Shell变量的定义

  4. 定义变量:变量名=值

  5. 撤销变量(删除变量):unset 变量
  6. 声明静态变量:readonly变量,注意:静态变量不能unset,使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

    1. #!/bin/bash
    2. #定义变量A
    3. A=100
    4. #输出变量需要在变量前面加上$
    5. echo $A #输出100
    6. echo A=$A #输出A=100
    7. echo "A=$A" #输出A=100
    8. #撤销变量A
    9. unset A
    10. echo "A=$A" #输出A=
    11. #声明静态变量B=2,不能unset
    12. readonly B=2
    13. echo "B=$B" #输出B=2
    14. #将指令返回的结果赋给变量
    15. C='date'
    16. D=$(date)
    17. echo "C=$C" #输出C=2021年 11月 21日 星期日 21:00:00 CST
    18. echo "D=$D" #输出D=2021年 11月 21日 星期日 21:00:00 CST

    定义变量的规则
  7. 变量名可以由字母、数字和下划线组成,但是不能以数字开头;

  8. 等号两侧不能有空格;
  9. 变量名一般习惯为大写;
  10. 不能使用标点符号;
  11. 不能使用bash里的关键字(可用help命令查看保留关键字);

    使用变量

    使用一个定义过的变量,只要在变量名前面加美元符号即可;变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。

    将命令的返回值赋给变量
  12. A=’date’ 反引号,运行里面的命令,把结果返回给变量A

  13. A=$(date) 等价于反引号,即等价于A=’date’

    设置环境变量

  14. 将shell变量输出为环境变量/全局变量:export 变量名=变量值

  15. 让修改后的配置信息立即生效:source 配置文件
  16. 查询环境变量的值:echo $变量名 ```shell

    !/bin/bash

    以 # 开头的行就是注释,即单行注释,会被解释器忽略。

    shell脚本多行注释

    :<<! 注释内容… 注释内容… 注释内容… !

或者

:<<EOF 注释内容… 注释内容… 注释内容… EOF

或者

:<<’ 注释内容… 注释内容… 注释内容… ‘

:<<! echo $A #输出100 echo A=$A #输出A=100 echo “A=$A” #输出A=100 !

  1. <a name="Eowja"></a>
  2. #### 位置参数变量
  3. 当我们执行一个shell脚本时,希望获取到命令行的参数信息,就可以使用到位置参数变量,比如:./myshell.sh 100 200,者就是一个执行shell的命令行,可以在myshell脚本中获取到参数信息;<br />基本语法:
  4. | **参数处理** | **说明** |
  5. | --- | --- |
  6. | $n | n为数字,$0代表命令本身,$1-$9代表第一个到第九个参数,10以上的参数需要用大括号包含,如${10} |
  7. | $# | 这个变量代表命令行中所有的参数的个数 |
  8. | $* | 这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体 |
  9. | $$ | 脚本运行的当前进程ID号 |
  10. | $! | 后台运行的最后一个进程的ID号 |
  11. | $@ | 这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待 |
  12. | $- | 显示Shell使用的当前选项,与set命令功能相同 |
  13. | $? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
  14. **$* 与 $@ 区别:**<br />相同点:都是引用所有参数。<br />不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。 <br />例如:下面的myshell.sh
  15. ```shell
  16. #!/bin/bash
  17. echo "0=$0 1=$1 2=$2"
  18. echo "所有的参数$*"
  19. echo "$@"
  20. echo "参数的个数=$#"

运行 ./myshell.sh 100 200 显示结果如下:

  1. 0=./myshell.sh 1=100 2=200
  2. 所有的参数=100 200
  3. 100 200
  4. 参数的个数=2

四、数组和运算符

Shell数组

数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。
与大部分编程语言类似,数组元素的下标由 0 开始。
Shell 数组用括号来表示,元素用”空格”符号分割开,语法格式如下:array_name=(value1 value2 … valuen)
也可以使用下标来定义数组:
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
读取数组元素值的一般格式是:${array_name[index]}
使用@ 或 可以获取数组中的所有元素:${array_name[@]} 或者 ${array_name[]}
获取数组长度的方法:${#my_array[*]} 或者 ${#my_array[@]}

Shell运算符

Shell 和其他编程语言一样,支持多种运算符,包括:算数运算符、关系运算符、布尔运算符、字符串运算符、文件测试运算符。

  1. “$((运算式))” 或 “$[运算式]” 或 expr m + n (expression表达式);
  2. 注意expr运算符合间要有空格,如果想将expr的结果赋给某个变量, 完整的表达式要被反引号 包含,注意这个字符不是常用的单引号,而是在 Esc 键下边的反引号;
  3. expr *,/,% 乘,除,取余;

    算数运算符

    下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
运算符 说明 举例
+ 加法 expr $a + $b 结果为 30
- 减法 expr $a - $b 结果为 -10
* 乘法 expr $a \\* $b 结果为 200
/ 除法 expr $b / $a 结果为 2
% 取余 expr $b % $a 结果为 0
= 赋值 a=$b 将把变量 b 的值赋给 a
== 相等,用于比较两个数字,相同则返回true [ $a == $b ] 返回 false
!= 不相等,用于比较两个数字,不相同则返回true [ $a != $b ] 返回 true

注意:条件表达式要放在方括号之间,并且要有空格,例如: [$a==$b] 是错误的,必须写成 [ $a == $b ]。乘号(*)前边必须加反斜杠()才能实现乘法运算。

  1. #!/bin/bash
  2. #案例1:计算(2+3)*4的值
  3. #第一种方式
  4. A=$(((2+3)*4))
  5. echo "A=$A" #输出结果A=20
  6. #第二种方式,推荐使用
  7. B=$[(2+3)*4]
  8. echo "B=$B" #输出结果B=20
  9. #第三种方式expr
  10. C=`expr 2 + 3` #注意使用的是反引号 ` 而不是单引号 '
  11. D=`expr $C \* 4`#注意使用的是反引号 ` 而不是单引号 '
  12. echo "C=$C" #输出结果C=5
  13. echo "D=$D" #输出结果D=20
  14. #案例2:请求出命令行的两个参数【整数】的和 20 50
  15. SUM=$[$1+$2]
  16. echo "sum=$SUM" #输出结果70

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
-eq 检测两个数是否相等,相等返回 true [ $a -eq $b ] 返回 false
-ne 检测两个数是否不相等,不相等返回 true [ $a -ne $b ] 返回 true
-gt 检测左边的数是否大于右边的,如果是,则返回 true [ $a -gt $b ] 返回 false
-lt 检测左边的数是否小于右边的,如果是,则返回 true [ $a -lt $b ] 返回 true
-ge 检测左边的数是否大于等于右边的,如果是,则返回 true [ $a -ge $b ] 返回 false
-le 检测左边的数是否小于等于右边的,如果是,则返回 true [ $a -le $b ] 返回 true

布尔运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
非运算,表达式为 true 则返回 false,否则返回 true [ ! false ] 返回 true
-o 或运算,有一个表达式为 true 则返回 true [ $a -lt 20 -o $b -gt 100 ] 返回 true
-a 与运算,两个表达式都为 true 才返回 true [ $a -lt 20 -a $b -gt 100 ] 返回 false

逻辑运算符

以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:

运算符 说明 举例
&& 逻辑的 AND [[ $a -lt 100 && $b -gt 100 ]] 返回 false
|| 逻辑的 OR [[ $a -lt 100 || $b -gt 100 ]] 返回 true

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true [ $a = $b ] 返回 false
!= 检测两个字符串是否不相等,不相等返回 true [ $a != $b ] 返回 true
-z 检测字符串长度是否为0,为0返回 true [ -z $a ] 返回 false
-n 检测字符串长度是否不为 0,不为 0 返回 true [ -n “$a” ] 返回 true
$ 检测字符串是否为空,不为空返回 true [ $a ] 返回 true

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。
属性检测描述如下:

运算符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true [ -b $file ] 返回 false
-c file 检测文件是否是字符设备文件,如果是,则返回 true [ -c $file ] 返回 false
-d file 检测文件是否是目录,如果是,则返回 true [ -d $file ] 返回 false
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true [ -f $file ] 返回 true
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true [ -g $file ] 返回 false
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true [ -k $file ] 返回 false
-p file 检测文件是否是有名管道,如果是,则返回 true [ -p $file ] 返回 false
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true [ -u $file ] 返回 false
-r file 检测文件是否可读,如果是,则返回 true [ -r $file ] 返回 true
-w file 检测文件是否可写,如果是,则返回 true [ -w $file ] 返回 true
-x file 检测文件是否可执行,如果是,则返回 true [ -x $file ] 返回 true
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true [ -s $file ] 返回 true
-e file 检测文件(包括目录)是否存在,如果是,则返回 true [ -e $file ] 返回 true

其他检查符:
-S: 判断某文件是否 socket。
-L: 检测文件是否存在并且是一个符号链接。

五、Shell 流程控制

和 Java、PHP 等语言不一样,sh 的流程控制不可为空,在 sh/bash 里,如果 else 分支没有语句执行,就不要写这个 else。

if else

if 语句语法格式:

  1. if condition
  2. then
  3. command1
  4. command2
  5. ...
  6. commandN
  7. fi

写成一行(适用于终端命令提示符):

  1. if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

末尾的 fi 就是 if 倒过来拼写,后面还会遇到类似的。
if else 语法格式:

  1. if condition
  2. then
  3. command1
  4. command2
  5. ...
  6. commandN
  7. else
  8. command
  9. fi

if else-if else 语法格式:

  1. if condition1
  2. then
  3. command1
  4. elif condition2
  5. then
  6. command2
  7. else
  8. commandN
  9. fi

for 循环

for循环一般格式为:

  1. for var in item1 item2 ... itemN
  2. do
  3. command1
  4. command2
  5. ...
  6. commandN
  7. done

写成一行:

  1. for var in item1 item2 ... itemN; do command1; command2 done;

当变量值在列表里,for 循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的 shell 命令和语句。in 列表可以包含替换、字符串和文件名。in列表是可选的,如果不用它,for循环使用命令行的位置参数。

while 语句

while 循环用于不断执行一系列命令,也用于从输入文件中读取数据。其语法格式为:

  1. while condition
  2. do
  3. command
  4. done

无限循环语法格式:

  1. while :
  2. do
  3. command
  4. done

或者

  1. while true
  2. do
  3. command
  4. done

或者

  1. for (( ; ; ))

until 循环

until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
until 语法格式:

  1. until condition
  2. do
  3. command
  4. done

condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

case … esac

case … esac 为多选择语句,与其他语言中的 switch … case 语句类似,是一种多分枝选择结构,每个 case 分支用右圆括号开始,用两个分号 ;; 表示 break,即执行结束,跳出整个 case … esac 语句,esac(就是 case 反过来)作为结束标记。可以用 case 语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令。
case … esac 语法格式如下:

  1. case in
  2. 模式1)
  3. command1
  4. command2
  5. ...
  6. commandN
  7. ;;
  8. 模式2)
  9. command1
  10. command2
  11. ...
  12. commandN
  13. ;;
  14. esac

case 工作方式如上所示,取值后面必须为单词 in,每一模式必须以右括号结束。取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。

跳出循环

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,Shell使用两个命令来实现该功能:break和continue。

break命令

break命令允许跳出所有循环(终止执行后面的所有循环)。
下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。

  1. #!/bin/bash
  2. while :
  3. do
  4. echo -n "输入 1 到 5 之间的数字:"
  5. read aNum
  6. case $aNum in
  7. 1|2|3|4|5) echo "你输入的数字为 $aNum!"
  8. ;;
  9. *) echo "你输入的数字不是 1 到 5 之间的! 游戏结束"
  10. break
  11. ;;
  12. esac
  13. done

执行以上代码,输出结果为:

  1. 输入 1 5 之间的数字:3
  2. 你输入的数字为 3!
  3. 输入 1 5 之间的数字:7
  4. 你输入的数字不是 1 5 之间的! 游戏结束

continue

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。
对上面的例子进行修改:

  1. #!/bin/bash
  2. while :
  3. do
  4. echo -n "输入 1 到 5 之间的数字: "
  5. read aNum
  6. case $aNum in
  7. 1|2|3|4|5) echo "你输入的数字为 $aNum!"
  8. ;;
  9. *) echo "你输入的数字不是 1 到 5 之间的!"
  10. continue
  11. echo "游戏结束"
  12. ;;
  13. esac
  14. done

运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句 echo “游戏结束” 永远不会被执行。

六、Shell 函数

linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。
shell中函数的定义格式如下:

  1. [ function ] funname [()]
  2. {
  3. action;
  4. [return int;]
  5. }

说明:
1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)。
下面的例子定义了一个函数并进行调用:

  1. #!/bin/bash
  2. demoFun(){
  3. echo "这是我的第一个 shell 函数!"
  4. }
  5. echo "-----函数开始执行-----"
  6. demoFun
  7. echo "-----函数执行完毕-----"

输出结果:

  1. -----函数开始执行-----
  2. 这是我的第一个 shell 函数!
  3. -----函数执行完毕-----

下面定义一个带有return语句的函数:

  1. #!/bin/bash
  2. funWithReturn(){
  3. echo "这个函数会对输入的两个数字进行相加运算..."
  4. echo "输入第一个数字: "
  5. read aNum
  6. echo "输入第二个数字: "
  7. read anotherNum
  8. echo "两个数字分别为 $aNum 和 $anotherNum !"
  9. return $(($aNum+$anotherNum))
  10. }
  11. funWithReturn
  12. echo "输入的两个数字之和为 $? !"

输出类似下面:

  1. 这个函数会对输入的两个数字进行相加运算...
  2. 输入第一个数字:
  3. 1
  4. 输入第二个数字:
  5. 2
  6. 两个数字分别为 1 2 !
  7. 输入的两个数字之和为 3 !

函数返回值在调用该函数后通过 $? 来获得。
注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。

函数参数

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…
带参数的函数示例:

  1. #!/bin/bash
  2. funWithParam(){
  3. echo "第一个参数为 $1 !"
  4. echo "第二个参数为 $2 !"
  5. echo "第十个参数为 $10 !"
  6. echo "第十个参数为 ${10} !"
  7. echo "第十一个参数为 ${11} !"
  8. echo "参数总数有 $# 个!"
  9. echo "作为一个字符串输出所有参数 $* !"
  10. }
  11. funWithParam 1 2 3 4 5 6 7 8 9 34 73

输出结果:

  1. 第一个参数为 1 !
  2. 第二个参数为 2 !
  3. 第十个参数为 10 !
  4. 第十个参数为 34 !
  5. 第十一个参数为 73 !
  6. 参数总数有 11 个!
  7. 作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。
另外,还有几个特殊字符用来处理参数:

参数处理 说明
$# 传递到脚本或函数的参数个数
$* 以一个单字符串显示所有向脚本传递的参数
$$ 脚本运行的当前进程ID号
$! 后台运行的最后一个进程的ID号
$@ 与$*相同,但是使用时加引号,并在引号中返回每个参数
$- 显示Shell使用的当前选项,与set命令功能相同
$? 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误

七、Shell 输入/输出重定向

重定向命令列表如下:

命令 说明
command > file 将输出重定向到 file
command < file 将输入重定向到 file
command >> file 将输出以追加的方式重定向到 file
n > file 将文件描述符为 n 的文件重定向到 file
n >> file 将文件描述符为 n 的文件以追加的方式重定向到 file
n >& m 将输出文件 m 和 n 合并
n <& m 将输入文件 m 和 n 合并
<< tag 将开始标记 tag 和结束标记 tag 之间的内容作为输入

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

八、Shell编程综合案例

需求分析:

  1. 每天凌晨2:30备份数据库mysql到/data/backup/db目录下;
  2. 备份开始和备份结束能够给出相应的提示信息;
  3. 备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式;
  4. 在备份的同时,检查是否有10天前备份的数据库文件,如果有就将其删除。

第一步:编写sh文件mysql_db_backup.sh;

  1. #!/bin/bash
  2. #备份目录
  3. BACKUP=/data/backup/db
  4. #当前时间
  5. DATETIME=$(date +%Y-%m-%d_%H%M%S)
  6. echo $DATETIME
  7. #数据库的地址
  8. HOST=localhost
  9. #数据库的用户名
  10. DB_USER=root
  11. #数据库的密码
  12. DB_PW=123
  13. #备份的数据库名
  14. DATABASE=mysql
  15. #创建备份目录,如果目录不存在,就创建
  16. [ ! -d "${BACKUP}/${DATETIME}" ] && mkdir -p "${BACKUP}/${DATETIME}"
  17. #备份数据库
  18. mysqldump -u${DB_USER} -p${DB_PW} --host=${HOST} -q -R --databases ${DATABASE} | gzip > ${BACKUP}/${DATETIME}/$DATETIME.sql.gz
  19. #将文件处理成 tar.gz
  20. cd ${BACKUP}
  21. tar -zcvf $DATETIME.tar.gz ${DATETIME}
  22. #删除对应的备份目录
  23. rm -rf ${BACKUP}/${DATETIME}
  24. #删除10天前的备份文件
  25. find ${BACKUP} -atime +10 -name "*.tar.gz" -exec rm -rf {} \;
  26. echo "备份数据库${DATABASE} 成功~"

第二步:crontab -e
输入:30 2 * /usr/sbin/mysql_db_backup.sh #每天2点30执行mysql_db_backup.sh