暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Linux学习笔记(十三)Shell编程

ITPro进化论 2021-12-07
76

一、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)

  1. C Shell(/usr/bin/csh)

  2. K Shell(/usr/bin/ksh)

  1. Shell for Root(/sbin/sh)

  2. ……

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!

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

#!/bin/bash
echo "hello world!"

  1. 给hello.sh文件执行权限:chmod u+x hello.sh

  2. 执行hello.sh:./hello.sh

不给hello.sh文件执行权限也可以通过sh执行:sh hello.sh

#! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell。

echo 命令用于向窗口输出文本。

三、Sheel的变量

Shell变量介绍

  1. Linux Shell中的变量分为:系统变量和用户自定义变量;

  2. 系统变量:$HOME、$PWD、$SHELL、$USER等等,

  1. 显示当前shell中所有的变量:set

Shell变量的定义

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

  2. 撤销变量(删除变量):unset 变量

  1. 声明静态变量:readonly变量,注意:静态变量不能unset,使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

#!/bin/bash
#定义变量A
A=100
#输出变量需要在变量前面加上$
echo $A #输出100
echo A=$A #输出A=100
echo "A=$A" #输出A=100
#撤销变量A
unset A
echo "A=$A" #输出A=
#声明静态变量B=2,不能unset
readonly B=2
echo "B=$B" #输出B=2
#将指令返回的结果赋给变量
C='date'
D=$(date)
echo "C=$C" #输出C=2021年 11月 21日 星期日 21:00:00 CST
echo "D=$D" #输出D=2021年 11月 21日 星期日 21:00:00 CST

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

  2. 等号两侧不能有空格;

  1. 变量名一般习惯为大写;

  2. 不能使用标点符号;

  1. 不能使用bash里的关键字(可用help命令查看保留关键字);

使用变量

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

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

  2. A=$(date) 等价于反引号,即等价于A='date'

设置环境变量

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

  2. 让修改后的配置信息立即生效:source 配置文件

  1. 查询环境变量的值:echo $变量名

#!/bin/bash
以 # 开头的行就是注释,即单行注释,会被解释器忽略。
#shell脚本多行注释
:<<!
注释内容...
注释内容...
注释内容...
!

或者

:<<EOF
注释内容...
注释内容...
注释内容...
EOF

或者

:<<'
注释内容...
注释内容...
注释内容...
'

:<<!
echo $A #输出100
echo A=$A #输出A=100
echo "A=$A" #输出A=100
!

位置参数变量

当我们执行一个shell脚本时,希望获取到命令行的参数信息,就可以使用到位置参数变量,比如:./myshell.sh 100 200,者就是一个执行shell的命令行,可以在myshell脚本中获取到参数信息;

基本语法:

参数处理

说明

$n

n为数字,$0代表命令本身,$1-$9代表第一个到第九个参数,10以上的参数需要用大括号包含,如${10}

$#

这个变量代表命令行中所有的参数的个数

$*

这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体

$$

脚本运行的当前进程ID号  

$!  

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

$@

这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待

$-  

显示Shell使用的当前选项,与set命令功能相同  

$?  

显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误  

$* 与 $@ 区别:

相同点:都是引用所有参数。

不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 "1 2 3"(传递了一个参数),而 "@" 等价于 "1" "2" "3"(传递了三个参数)。

例如:下面的myshell.sh

#!/bin/bash
echo "0=$0 1=$1 2=$2"
echo "所有的参数$*"
echo "$@"
echo "参数的个数=$#"

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

0=./myshell.sh 1=100 2=200
所有的参数=100 200
100 200
参数的个数=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 键下边的反引号;

  1. 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 ]。乘号(*)前边必须加反斜杠(\)才能实现乘法运算。

#!/bin/bash
#案例1:计算(2+3)*4的值
#第一种方式
A=$(((2+3)*4))
echo "A=$A" #输出结果A=20
#第二种方式,推荐使用
B=$[(2+3)*4]
echo "B=$B" #输出结果B=20
#第三种方式expr
C=`expr 2 + 3` #注意使用的是反引号 ` 而不是单引号 '
D=`expr $C \* 4`#注意使用的是反引号 ` 而不是单引号 '
echo "C=$C" #输出结果C=5
echo "D=$D" #输出结果D=20
#案例2:请求出命令行的两个参数【整数】的和 20 50
SUM=$[$1+$2]
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 语句语法格式:

if condition
then
command1
command2
...
commandN
fi

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

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

末尾的 fi 就是 if 倒过来拼写,后面还会遇到类似的。

if else 语法格式:

if condition
then
command1
command2
...
commandN
else
command
fi

if else-if else 语法格式:

if condition1
then
command1
elif condition2
then
command2
else
commandN
fi

for 循环

for循环一般格式为:

for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done

写成一行:

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

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

while 语句

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

while condition
do
command
done

无限循环语法格式:  

while :
do
command
done

或者

while true
do
command
done

或者

for (( ; ; ))

until 循环

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

until 语法格式:

until condition
do
command
done

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

case ... esac

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

case ... esac 语法格式如下:

case 值 in
模式1)
command1
command2
...
commandN
;;
模式2)
command1
command2
...
commandN
;;
esac

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

跳出循环

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

break命令

break命令允许跳出所有循环(终止执行后面的所有循环)。

下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。

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

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

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

continue

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

对上面的例子进行修改:

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

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

六、Shell 函数

linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。

shell中函数的定义格式如下:

[ function ] funname [()]

{

action;

[return int;]

}

说明:

1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。

2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)。

下面的例子定义了一个函数并进行调用: 

#!/bin/bash
demoFun(){
echo "这是我的第一个 shell 函数!"
}
echo "-----函数开始执行-----"
demoFun
echo "-----函数执行完毕-----"

输出结果:

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

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

#!/bin/bash
funWithReturn(){
echo "这个函数会对输入的两个数字进行相加运算..."
echo "输入第一个数字: "
read aNum
echo "输入第二个数字: "
read anotherNum
echo "两个数字分别为 $aNum 和 $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

输出类似下面:

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

函数返回值在调用该函数后通过 $? 来获得。

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

函数参数

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

带参数的函数示例:

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

输出结果: 

第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11 个!
作为一个字符串输出所有参数 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. 备份开始和备份结束能够给出相应的提示信息;

  1. 备份后的文件要求以备份时间为文件名,并打包成 .tar.gz 的形式;

  2. 在备份的同时,检查是否有10天前备份的数据库文件,如果有就将其删除。

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

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

第二步:crontab -e

输入:30 2 * * * /usr/sbin/mysql_db_backup.sh    #每天2点30执行mysql_db_backup.sh

文章转载自ITPro进化论,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论