shell脚本,循环语句,条件语句以及正则表达式

154 阅读12分钟

shell简介及其作用

简单说,shell就是一个壳,一类软件的总称,作为程序时主要把人类使用的语言翻译给机器,再把机器语言翻译成人类使用的语言,人机交流的翻译层。

Linux 系统中的 Shell 是一个特殊的应用程序,它介于操作系统内核与用户之间,充当了一个“命令解释器”的角色,负责接收用户输入的操作指令(命令)并进行解释,将需要执行的操作传递给内核执行,并输出执行结果。 常见的 Shell 解释器程序有很多种,使用不同的 Shell 时,其内部指令、命令行提示符等方面会存在一些区别。通过 /etc/shells 文件可以了解当前系统所支持的 Shell 脚本种类

1.png

  • bash:基于gun的框架下发展的shell
  • csh:类似c语言的shell
  • tcsh:整合了csh提供了更多功能
  • sh:已经被bash替换
  • nologin:让用户无法登录

一、shell脚本

1.shell脚本是什么

Shell脚本是一种用于编写和执行命令序列的脚本语言 Shell脚本可以通过文本文件的形式保存,并使用shell解释器来执行。 Shell脚本可以包含命令、条件判断、循环、函数等,可以使用变量、数组、字符串处理、文件操作等功能。

3. shell脚本的用途

  • 将简单的命令组合完成复杂的工作,自动化执行命令,提高工作效率
  • 减少手工命令的重复输入,一定程度上避免人为错误
  • 将软件或应用的安装及配置实现标准化
  • 用于实现日常性的,重复性的运维工作,如:文件打包压缩备份,监控系统运行状态并实现遇到问题警告等

3.shell脚本的构成

2.png

3.png

上面就是一个简单的shell脚本,这个脚本非常简单

基本格式:

#!/bin/bash
#注释
可执行的语句

以上面的简单脚本hello.sh示例:

#!/bin/bash :使用Bash解释器执行脚本,第一行的开头,脚本申明(解释器)

echo "当前日期和时间:" :使用echo命令打印一段文本,即"当前日期和时间:"。

date :输出当前的日期和时间。

hello.sh:脚本的名字,.sh是shell脚本的结尾格式(一般都使用这个)

总结:

  • 就是将命令按顺序一一列出,最后自动执行
  • 执行需要权限,也可以直接使用绝对路径
  • 脚本其实不复杂,通用脚本环境改变后依然可以使用的脚本

4.shell脚本的执行逻辑与执行方式

4.1 执行逻辑:

  1. 顺序执行:程序按从上到下顺序执行
  2. 选择执行:程序执行过程中,根据条件的不同,进行选择不同分支继续执行
  3. 循环执行:程序执行过程中需要重复执行多次某段语句

4.2 执行的三种方式:

  1. 指定路径去执行文件(需要有执行权限)

以上面的简单脚本hello.sh举例

[root@localhost ~]# chmod +x /root/hello.sh    加权限
./host.sh           //指定相对路径
/root/host.sh       //指定绝对路径 

2. 指定解释器去执行(不需要执行权限)

以上面的简单脚本hello.sh举例

脚本中指定的shell为bash(#!/bin/bash),
则bash不需要权限也可以执行
[root@localhost ~]#bash hello.sh //就可以直接执行
注意这里是在当前目录下,如果脚本不在当前目录下,bash后需要跟目录,列如在脚本hello.sh在/data目录下,执行就需要变成bash /data/hello.sh

3. source 和 . (不需要权限)

注意,这里我们一般不推荐使用 . 和source,因为它们会影响到当前的环境

以上面的简单脚本hello.sh举例

source 脚本名(绝对路径)
source /root/hello.sh 
或者
. /hello.sh 

PS:如果想要使用tab键补全脚本需要做软链接,将目标脚本链接到$PATH的目录下(需要执行权限)

5.shell脚本错误调试

一般脚本的错误有三种

  • 语法错误导致后续的命令不继续执行,可以用bash -n 检查错误,提示的出错行数不一定是准确的。
  • 命令错误 默认后续的命令还会继续执行,用bash -n 无法检查出来 ,可以使用 bash -x 进行观察。
  • 逻辑错误 只能使用 bash -x 进行排错

6.运算符和运算法则

6.1 运算符

1.加法 +
2.减法 -
3.乘法 \ *
4.除法 /(只会显示整数,除不尽为0)
5.取余 (取模)% 

6.2 运算法则

1.加法: num1 + num2
2.减法:num1 - num2
3.乘法:num1 \ * num2
4.整除:num1 / num2
5.取余(求模):num1 % num2 (求 num1 除以 num2 的余数)

6.3 算数表达式

(1) let var=算术表达式
let sum=1+2
sum=1+2
(2) $((var=算术表达式)) 和上面等价
((sum=1+2))
echo $sum
(3) var= $[算术表达式]
(4) var=$((算术表达式))
(5) var=$(expr arg1 arg2 arg3 ...)
(6) var= expr arg1 arg2 arg3 ...
(7) echo '算术表达式' | bc
​
  $[ ]
  $(())
  $(expr 1 2 3)
​
#let
[root@localhost ~]#a=1
[root@localhost ~]#b=2
[root@localhost ~]#let z=a+b
[root@localhost ~]#echo $z
3
[root@localhost ~]#let z=$[a-b]
[root@localhost ~]#echo $z
-1
[root@localhost ~]#let z=$((a-b))
[root@localhost ~]#echo $z
-1#expr
[root@localhost ~]#a=1
[root@localhost ~]#b=2
[root@localhost ~]#expr $a + $b
#加减乘除前后有空格
3[root@localhost ~]#echo "6*3"|bc
18
echo  $[RANDOM%34 +1]let 支持加加减减 使用较多
[root@localhost ~]#i=1
[root@localhost ~]#let i++
[root@localhost ~]#echo $i
2
[root@localhost ~]#i=1;let i++;echo $i
2
[root@localhost ~]#i=1;let ++i;echo $i
2
[root@localhost ~]#i=100;let j=i++;echo $i $j
101 100
[root@localhost ~]#i=100;let j=++i;echo $i $j
101 101
#i++ 是先赋值给j后再加     ++i是加后再赋值
​
i++  是先赋值再加
++i  是加后再赋值
​
i=$(expr 12  \ *  5 )
i=$((12 * 5))
i=$[12 * 5]
let i=12*5
i++ 相当于 i=$[ $i+1 ]
i-- 相当于 i=$[ $i - 1 ]
i+=2 相当于 i=$[ $i+2 ]

7.重定向与管道符

类型设备文件文件描述编号默认设备
标准输入/dev/stdin0键盘
标准输出/dev/stdout1显示器
标准错误输出/dev/stderr2显示器

7.1 重定向

类型操作符用途
重定向输入<从指定的文件读取数据,而不是从键盘输入
重定向输出将输出结果保存到指定的文件(覆盖原有内容)
重定向输出>>将输出结果换行添加在文件尾部
重定向错误输出2>将错误信息保存到指定的文件(覆盖原有内容)
重定向错误输出2>>将错误信息追加到指定的文件中
混合输出&>将标准输出、标准错误的内容保存到同一个文件中
重定向正确输出(这里还使用tty终端输出)

4.png

补充:

tty 是一个用于显示当前终端设备的命令。它会返回当前 shell 运行的终端设备的名称。

当你在终端中运行 tty 命令时,它会输出类似于 /dev/pts/0/dev/tty1 的结果。这表示你当前正在使用的终端设备。

tty 命令在以下情况下可能会有用:

  • 确定当前 shell 运行的终端设备,以便在脚本中执行与终端相关的操作。
  • 检查是否连接到远程终端或通过 SSH 登录。
  • 确定当前 shell 是否在交互模式下运行。

请注意,tty 命令只能在终端中运行,它不适用于非交互式的环境或脚本中。

错误定重向

会在另一边也显示提示信息也是错误的

5.png

多重重定向

Here Document 概述 使用 I/O 重定向的方式将命令列表提供给交互式程序或命令,比如 ftp、cat 或 read 命令。 HereDocument是标准输入的一种替代品, 可以帮助脚本开发人员不必使用临时文件来构建输入信息, 而是直接就地生产出一个文件并用作命令的标准输入。

6.png

内容导入文件

7.png

更改用户密码

8.png

8.变量

变量是计算机语言中能储存计算结果或能表示值的抽象概念。保存将来会变化的数据,即使数据变化,直接调用变量即可。

Shell 变量用来存放系统和用户需要使用的特定参数(值),而且这些参数可以根据用户的设定或系统环境的变化而相应变化。通过使用变量,Shell 程序能够提供更加灵活的功能,适应性更强。

8.1 变量的作用

用来存放系统和用户需要使用的特定参数(值)

  • 变量名:使用固定的名称,由系统预设或用户定义
  • 变量值:能够根据用户设置、系统环境的变化而变化

8.2 常见的shell变量的类型

自定义变量:由用户自己定义,修改和使用

环境变量:由系统维护,用于设置工作环境

只读变量:只可以读取不可以更改

位置变量:通过命令行给脚本传递参数

预定义变量:Bash中内置的一类变量,不能修改 有些规定好的变量 放在那里让你使用

8.3 变量的命名要求

  • 区分大小写
  • 不能使程序中的保留字和内置变量:如:if, for,hostname 命令 a=
  • 只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”,和主机名相反
  • 不要使用内置的变量,使用英文尽量使用词义通俗易懂,PATH
  • 大驼峰 StudentFirstName
  • 小驼峰 studentFirstName
  • 下划线 student_name
name='value' 
变量名=变量值
直接字串:name='root'
变量引用:name="$USER"
命令引用:name=`COMMAND` 或者 name=$(COMMAND)
注意:变量赋值是临时生效,当退出终端后,变量会自动删除,无法持久保存,脚本中的变量会随着脚本结束,也会自动删除
​
变量引用:
$name
${name}
​
弱引用和强引用
"$name " 弱引用,其中的变量引用会被替换为变量值
'$name ' 强引用,其中的变量引用不会被替换为变量值,而保持原字符串

9.png

8.4 read -p 读取屏幕输出

将键盘输出的内容变成变量

10.png

8.5 变量的作用范围

默认情况下,新定义的变量只在当前的shell环境中有效,因此称为局部变量,当进入子程序或新的shell环境中,局部变量将无法再起作用。

可以通过内部命令export将指定的变量为全局变量( 即在文件中/etc/profile添加配置),使用户定义的变量在所子shell环境中可以继续使用

11.png

8.6 环境变量

  1. 由系统提前创建,用来设置用户的工作环境
  2. 可以使用env查看环境变量

需要记住的常用环境变量

$USER 表示用户名称
​
$HOME 表示用户的宿主目录
​
$LANG 表示语言和字符集
​
$PWD 表示当前所在工作目录
​
$PATH 表示可执行用户程序的默认路径

环境变量

  • 可以使子进程(包括孙子进程)继承父进程的变量,但是无法让父进程使用子进程的变量
  • 一旦子进程修改从父进程继承的变量,将会新的值传递给孙子进程
  • 一般只在系统配置文件中使用,在脚本中较少使用
[root@localhost opt]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
[root@localhost opt]# echo $USER
root
[root@localhost opt]# echo $PWD
/opt
[root@localhost opt]# echo `pwd`
/opt
[root@localhost opt]# echo $HOME
/root
[root@localhost opt]# echo $LANG
zh_CN.UTF-8
​
​
​
​
系统可以通过$PATH 来执行文件 PATH=$PATH:/root/
[root@localhost opt]# PATH=$PATH:/root/
[root@localhost opt]# echo $PATH 
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin:/root/

8.7 只读变量

变量值不允许修改(重新赋值)的情况

无法使用 unset删除

一般不会设置只读变量,只读变量都是常量,在只读变量是局部变量的情况下,最快的解决方法就是重启

12.png

8.8 预定义(状态)变量

变量含义
$*表示所有位置参数的内容看成一个整体返回 返回所有
$@表示所有位置参数的内容分割成n份每份作为一个独立的个体返回 返回所有
$?表示前一条命令执行后的返回状态,返回值为 0 表示执行正确,返回任何非 0值均表示执行出现异常
$#表示命令行中位置参数的总个数
$0表示当前执行的脚本或程序的名称 当前脚本的名字
$$当前进程id
$!后台任务最后一个id

需要注意*和@的区别

$?使用较为频繁

$?上一条命令或者脚本执行后返回的状态码

13.png

二、条件语句

1 条件语句

1.1 测试

test 测试文件的表达式 是否成立

格式:test 选项 文件

操作符:
-d测试是否为目录(Directory)
-e测试目录或文件是否存在(Exist)
-a测试目录或文件是否存在(Exist)
-f测试是否为文件(File)
-r测试当前用户是否有权限读取(Read)
-W测试当前用户是否有权限写入(Write)
-x测试当前用户是否有权限执行(eXcute)
-L测试是否为软连接文件
属性测试补充:
-sFILE #是否存在且非空
-tfd #fd 文件描述符是否在某终端已经打开
-NFILE #文件自从上一次被读取之后是否被修改过
-OFILE #当前有效用户是否为文件属主
-GFILE #当前有效用户是否为文件属组

条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成

测试过程,实现评估布尔声明,以便用在条件性环境下进行执行

若真,则状态码变量 $? 返回0

若假,则状态码变量 $? 返回非0

条件测试命令

14.png

1.2 比较整数数值

格式:[ 整数1 操作符 整数2 ] 公式

左右两边[]和中间内容(整数)之间要有空格

  • -eq:第一个数等于(Equal)第二个数
  • -ne:第一个数不等于(Not Equal)第二个数
  • -gt:第一个数大于(Greater Than)第二个数
  • -lt:第一个数小于(Lesser Than)第二个数
  • -le:第一个数小于或等于(Lesser or Equal)第二个数
  • -ge:第一个数大于或等于(Greater or Equal)第二个数

15.png

1.3 字符串比较

=:字符串内容相同

!=:字符串内容不同,! 号表示相反的意思

-z:字符串内容为空

-n: 字符是否存在

格式:

[ 字符串1 = 字符串2 ] 是否相同

[ 字符串1 != 字符串2 ] 是否不相同

[ -z 字符串 ] 是否为空

[ -n 字符串 ] 字符是否存在

[root@localhost opt]#str1=aaa
[root@localhost opt]#str2=bbb
[root@localhost opt]#[ str1 = str2 ]
[root@localhost opt]#echo $?
1
[root@localhost opt]#[ $USER = root ]
[root@localhost opt]#echo $?
0

1.4 if 语句

if语句的结构

16.png

#单分支if语句:
       if
       then
       fi#双分支if语句:if [ ]判断条件1
then
条件1为真的分支代码
else
条件1为假的分支代码
if#多分支if [ ]判断条件1
then
条件1为真的分支代码
​
elif 判断条件2
then
条件2为真的分支代码
​
elif 判断条件3;then
条件3为真的分支代码
​
...
else
 以上条件都为假的分支代码
​
fi

1.5 case

case支持glob风格的通配符: * 任意长度任意字符 ? 任意单个字符 [0-9] 指定范围内的任意单个字符 | 或者,例如: a|b

格式:

case 变量引用 in
PAT1)
 分支1
 ;;
PAT2)
 分支2
 ;;
...
*)
 默认分支
 ;;
​
yes  no

1.6 echo

  • echo -n 表示不换行输出
  • echo -e 表示输出转义符

常用的转义符

选项作用
\r光标移至行首,并且不换行
\s当前shell的名称,如bash
\t插入Tab键,制表符
\n输出换行
\f换行,但光标仍停留在原处
\表示插入""本身转义
\b表示退格 不显示前一个字符
\c抑制更多的输出或不换行
[root@localhost ky15]#echo -e "12345\b678"
##退格删除前面的字符
1234678
[root@localhost ky15]#echo -e "12345\b\b678"
123678
[root@localhost ky15]#echo -e "12345\b\b\b678"
12678
[root@localhost ky15]#echo -e "12345\b\b\b\b678"
16785
###注意退格键和末尾的字符相关,超过末尾的字符数量 会出bug 了解即可
​
[root@localhost ky15]#echo -e "12345678\c"
12345678[root@localhost ky15]#echo -e "1234\c5678"
1234[root@localhost ky15]#
###\c 注意 使用在数字中间会把后面内容删除
​

补充

echo    -e "n\n\n\n\n\nw"   |   fdisk     /dev/sdb           #自动硬盘分区

1.7 date

date查看当前系统时间

  • -d 你描述的日期,显示指定字符串所描述的时间,而非当前时间
  • %F 完整日期格式,等价于 %Y-%m-%d
  • % T 时间(24小时制)(hh:mm:ss)

17.png

三、循环语句

循环含义

将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件

重复运行次数

  • 循环次数事先已知
  • 循环次数事先未知

1.for循环

执行机制:遍历

执行机制:
依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束如果省略 [in WORDS ... ] ,此时使用位置参数变量 in "$@"for 已知循环次数
​
for  tlj(变量)   循环次数(取值列表)
​
do
需要循环的事件
​
done
​
格式2:
for (( 表达式1; 表达式2; 表达式3 )); do 命令; donefor ((expr1;expr2;expr3))
do
       command
done
​
expr1:定义变量并赋初值
expr2:决定是否循环
expr3:决定循环变量如何改变,决定循环什么时候退出
​

示例:

for i in {1..10}
> do
> echo $i
> done
1
2
3
4
5
6
7
8
9
10
​
​
#!/bin/bash
for ((a=1;a<=9;a++))
do
for ((i=1;i<=$a;i++))
do
echo -e "${a}*${i}=$[i*a]\t\c"
done
echo
done
//九九乘法表
​
for ((i=0;i<=10;i++))
do
sum=$[i+sum]
done
echo $sum
//1--10累加
​
//打印正方形
#!/bin/bash
for j in {1..9}
do
for i  in {1..9}
do
echo -e " * \c"
#\c换行
done
echo
#换行
done

2.while和until

使用场景:当我们只知道停止条件,不知道次数,就需要使用while

while
当命令判断为假时停止
​
until
当命令判断为真时停止
​
//偶数求和
i=0
sum=0
while [ $i -le 100 ]
do
let sum+=$i
let i+=2
done
echo $sum
​
//对比for
for ((i=0,sum=0;i<=100;i++))
do
let sum+=$i
done
echo $sum
​
//累加求和
i=0
sum=0
while [ $i -le 100 ]
do
let sum+=$i
let i++
done
echo $sum
​
//对比while
i=0
sum=0
until [ $i -gt 100 ]
do
let sum+=$i
let i++
done
echo $sum

双重循环及跳出循环

  • Break:跳出整个循环

break 概述:跳出当前整个循环或结束当前循环,在 for、while 等循环语句中,用于跳出当前所在的循环体,执行循环体之后的语句,后面如果什么也不加,表示跳出当前循环等价于 break 1,也可以在后面加数字,假设 break3 表示跳出第三层循环.

  • Continue:跳过本次循环,进行下次循环

continue 概述:忽略本次循环剩余的代码,直接进行下一次循环;在 for、while 等循环语句中,用于跳出当前所在的循环体,执行循环体之后的语句,如果后面加的数字是 1,表示忽略本次条件循环,如果是 2 的话,忽略下来 2 次条件的循环.

  • exit:直接退出

示例:

cat case.sh
​
#!/bin/bashfor ((i=0;i<=4;i++)) ; doecho $icase $i in
​
1)
​
echo "This is one"
​
;;
​
2)
​
continue  //跳出本次循环
​
echo "This is two"
​
;;
​
3)
​
break  //跳出整个循环
​
echo "This is three"
​
;;
​
4)
​
echo "This is four"
​
;;
​
esacdone
​
[root@localhost ~]#sh case.sh
​
0
​
1
​
This is one
​
2
​
3
​

3.菜单

[root@localhost data]#help select
select: select NAME [in 词语 ... ;] do 命令; done
    从列表中选取词并且执行命令。
    
    WORDS 变量被展开,生成一个词的列表。展开的词集合被打印
    在标准错误输出设备上,每个以一个数字做前缀。如果没有 `in WORDS'
    则假定使用`in "$@"'。PS3提示符会被显示并且从标准输入读入一行
    如果该行由被显示的词对应的数字组成,则 NAME 变量被设定为相应
    的词。如果行为空,则 WORDS 变量和提示符被重新显示。如果读取了
    文件结束符,则命令完成。读入任何其他的值会导致 NAME 变量被设定
    为空。读入的行被存放在变量 REPLY 中。COMMANDS 命令在每次选择
    之后执行直到执行一个 break 命令。
    
    退出状态:
    返回最后一个执行的命令的状态。
​
​
一定要使用$REPLY
​
PS1 提示符
PS2 多行重定向
PS3 菜单选择
​
示例:
[root@localhost ~]#select menu in 配置网卡 配置yum源;do echo $REPLY;done
1) 配置网卡
2) 配置yum源
#? 1
1
#? 2
2
#? ^C
​
[root@localhost ~]#select menu in 配置网卡 配置yum源;do echo $menu;done
1) 配置网卡
2) 配置yum源
#? 1
配置网卡
#? 2
配置yum源
​

四、函数

在编写脚本时,有些脚本可以反复使用,可以调用函数来解决

语句块定义成函数约等于别名

1.函数的使用方法

  1. 定义函数
  2. 再引用函数

1.1 基本格式

function 函数名{
​
• 命令序列
​
}

2.

函数名(){
​
命令序列
​
}

3.

function func_name () {
​
…函数体…
​
}

注意事项:

  1. 直接写 函数中调用函数 直接写函数名
  2. 同名函数 后一个生效
  3. 调用函数一定要先定义
  4. 只要先定义了调用的 其他函数定义顺序无关

示例:

func_name (){
...函数体...
}
​
[root@yyds opt]#func1 (){ hostname;date;}
[root@yyds opt]#func1
yyds
2023年 08月 15日 星期二 18:19:33 CST
​
[root@yyds opt]#vim func.sh
​
h () {
​
echo "hello"
​
}
nihao () {
echo `h`  `w`
​
}
​
w () {
echo "world"
}
​
nihao
[root@yyds opt]#bash func.sh 
hello world
​
//同名函数会进行覆盖
​

2.查看函数

[root@yyds opt]#declare -F
declare -f __HOSTNAME
declare -f __SIZE
declare -f __SLAVEURL
declare -f __VOLNAME
declare -f __expand_tilde_by_ref
declare -f __get_cword_at_cursor_by_ref
declare -f __git_aliased_command
declare -f __git_aliases
..............
//函数列表,可以declare -f __HOSTNAME查看内容

3.删除函数

格式:unset func_name 函数

[root@localhost ~]#func1 () { hostname;hostname -I; }
[root@localhost ~]#func1 
localhost.localdomain
192.168.1.10 192.168.122.1 
[root@localhost ~]#unset func1
[root@localhost ~]#func1
​
​
[root@localhost ~]#func1(){ hostname;hostname -I;}    ###将这两条命令执行一遍

4.函数的返回值

return表示退出函数并返回一个退出值,脚本中可以用$?变量表示该值

使用原则:

函数一结束就去返回值,应为$?变量只返回执行的最后一条命令的退出返回码

退出码必须是0-255,超出的值将为除以256取余

test1 () {
        read -p "请输入一个数字:" num
        return $[$num*2]
​
​
}
​
test1
echo $?
​
[root@yyds opt]#bash return.sh 
请输入一个数字:34
68

5.函数的传参数

函数变量的作用范围:

函数在shell脚本中仅在当前的shell环境中有效

shell脚本中函数的变量默认全局有效

将变量限定在函数内部使用local命令

#!/bin/bash
sum1 (){
echo $1 
echo $2
​
}
​
sum1 $2 $1
​
[root@yyds opt]#bash chuan.sh 2 5
5
2
​
//sum1 后面的$2对应这sum1函数里面的$1
[root@localhost opt]# vim demo8.sh
#!/bin/bash
myfun(){
i=8
echo $i
​
}
myfun
​
[root@localhost opt]# chmod +x demo8.sh 
[root@localhost opt]# ./demo8.sh 
8
[root@localhost opt]# vim demo8.sh
#!/bin/bash
myfun(){
i=8
echo $i
​
}
myfun
echo $i
[root@localhost opt]# ./demo8.sh 
8
8
[root@localhost opt]# ./demo8.sh 
8
8
[root@localhost opt]# cat demo8.sh 
#!/bin/bash
myfun(){
i=8
echo $i
​
}
i=9
myfun
​
echo $i
[root@localhost opt]# ./demo8.sh 
8
8
[root@localhost opt]# cat demo8.sh 
#!/bin/bash
myfun(){
i=8
echo $i
​
}
myfun
i=9
echo $i
​
[root@localhost opt]# ./demo8.sh 
8
9
​
[root@localhost ~]#name=666
[root@localhost ~]#func1 () { name=99 ; echo $name; }
[root@localhost ~]#func1
99
[root@localhost ~]#echo $name 
99
[root@localhost ~]#name=666;func1 () { local name=99 ;echo $name; };echo $name
#加上local变量即可将变量限制在函数内
666
​

6.函数文件(方便调用函数)

建立一个文件用来存储函数,下次要使用函数时可以 . 文件名或函数名 进行使用

vim  func #建立专门的函数文件
ostype () {
if grep -i -q centos /etc/os-release
then
echo centos
elif grep -i -q ubuntu /etc/os-release
then
echo ubuntu
else
   echo this os is not centos and ubuntu
fi
}
​
//
ostype #可以直接调用
前面起得什么名字就可以用什么名字直接调用

7.函数递归

函数调用自己本身的函数

​
阶乘
使用for循环
#!/bin/bash
sum=1
read -p "请输入一个数:" num
for i in `seq $num`
do
let sum=$[i*sum]
done
echo $sum
​
函数调用自己
#!/bin/bash
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ] 
then
echo 1
else
echo $[$1*$(fact $[$1-1])]
 fi
}
fact $1
​
​
阶乘
fact() {
  if [ $1 -eq 1 ]
  then
    echo 1
  else
   local temp=$[$1 - 1]
   local result=$(fact $temp)
   echo $[$1 * $result]
  fi
}
read -p "请输入:" n
result=$(fact $n)
echo $result

五、数组

数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小(与 PHP 类似)。

与大部分编程语言类似,数组元素的下标由 0 开始。

Shell 数组用括号来表示,元素用"空格"符号分割开

在shell语句中,使用、遍历数组的时候,数组格式要写成 ${arr[@]}${arr\[\*]}

1.数组的格式

  1. 数组名=(value1 value2 ... valuen)

    arr_number=(1 2 3 4 5 6 7 8 9)

  1. 数组名=([0]=value0 [1]=value0 [2]=value0 ...)

    arr_number=([0]=1 [1]=2 [2]=3 [3]=4)

  1. 列表名:“value1 value2 valueN ..."

    数组名=($列表名)

    list_number="1 2 3 4 5 6"

    arr_number=($list_number)

  1. 数组名[0]="value"

    数组名[1]="value"

    数组名[2]="value"

    arr_number[0]=1

    arr_number[1]=2

    arr_number[2]=3

2.数组的数据类型

  • 数值型
  • 字符型
  • 混合型数值加字符

使用" "或’ '定义单引号或双引号括起来

2.1 获取数组的长度

  • echo ${#数组名[*]}
  • echo ${#数组名[@]}

示例:

[root@centos7 ~]#a=(1 2 3 4 5)
[root@centos7 ~]#echo $a
1
//不加下表默认第一个[root@centos7 ~]#echo ${!a[*]}
0 1 2 3 4
//查看下标
​
​
//列出所有数值,*和@作用一样
[root@centos7 ~]#echo ${a[*]}
1 2 3 4 5
​
​
//数组长度
[root@centos7 ~]#echo ${#a[@]}
5//数组分隔
[root@centos7 ~]#echo ${a[@]:0:5}
1 2 3 4 5
[root@centos7 ~]#echo ${a[@]:0:4}
1 2 3 4
[root@centos7 ~]#echo ${a[@]:1:5}
2 3 4 5
​
​
//数组切片
${ARRAY[@]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素 
{ARRAY[@]:offset}
[root@centos7 ~]#num=({0..10})
[root@centos7 ~]#echo ${num[*]:2:3}
2 3 4
[root@centos7 ~]#echo ${num[*]:6}
6 7 8 9 10

3.冒泡排序

#!/bin/bash
a=(90 20 30 40 50)
l=${#a[@]}
for ((i=1;i<$l;i++))
do
        for ((j=0;j<$l-1;j++))
        do
        first=${a[$j]}
        k=$[$j+1]
        second=${a[$k]}
​
        if [ $first -lt $second ]
        then
        temp=$first
        a[$j]=$second
        a[$k]=$temp
        fi
        done
doneecho ${a[@]}
​
//降序排列
​
​
取出最大数   
#!/bin/bash
a=( 10 70 30 40 50 60 )
#定义一个数组
max=${a[0]}for ((i=0;i<${#a[*]};i++))
do    
if [[ $max -lt ${a[$i+1]} ]]
then
max=${a[$i+1]}
fi
done
echo $max

六、免交互

1.多行重定向实现免交互

免交互第一种就是使用多行重定向

使用I/O重定向的方式将命令列表提供给交互式程序

作为标准输入的一种替代品

语法格式
​
命令<<   标记
​
....
​
......
​
标记
​

注意事项:

标记可以使用任意的合法字符(通用的字符是EOF)

结尾的标记一定要顶格写,前面不能有任何字符(包括空格)

结尾的标记后面也不能有任何字符(包括空格)

开头标记前后空格会被省略掉

示例:

//将...内容传给wc -l,wc -l 统计行数
[root@centos7 ~]#wc -l <<EOF
> line 1
> line 2
> EOF
2
​
​
​
//接受输入值然后输出
[root@centos7 ~]#read i <<EOF
> hi
> EOF
[root@centos7 ~]#echo $i
hi//
[root@yyds tmp]#passwd test1 <<EOF
> 123123
> 123123
> EOF//使用 passwd命令设置密码
注意,EOF 标记之间的两行是输入的密码和确认密码,两行内容必须保持一致,否则密码设置无法成功
[root@yyds tmp]#passwd test1 <<EOF
> 123123
> 123123
> EOF//编写yum仓库的内容
cat <<EOF >centos7.repo

2.ecpect实现免交互

是建立在tcl(tool command language)语言基础上的一个工具,常被用于进行自动化控制和测试,解决shell脚本中交互的相关问题

需要安装

yum -y install experct

格式:

expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]

2.1 相关命令

  • spawn :启动新的进程(监控,捕捉)
  • expect :从进程接收字符串
  • send :用于向进程发送字符串
  • exp_continue :匹配多个字符串在执行动作后加此命令
  • interact :允许用户交互
  • expect eof:直接返回

示例:

//免交互传输文件
#!/usr/bin/expect
spawn scp /etc/fstab 192.168.1.10:/mnt
expect {
  "yes/no" { send "yes\n";exp_continue }
  "password" { send "123123\n" }
}
​
expect eof 
//解释
(1)脚本解释器
expect 脚本中首先引入文件,表明使用的事哪一种shell
#!/usr/bin/expect
​
(2)spawn
spawn 后面通常跟一个Linux执行命令,表示开启一个会话、进程,并跟踪后续交互信息
例: spawn passwd root
​
(3)expect
判断上次输出结果中是否包含指定的字符串,如果有则立即返回,否则就等待超时时间后返回;只能捕捉有swpan启动的进程输出;
用于接受命令执行后的输出,然后和期望的字符串匹配
​
(4send
向进程发送字符串,用于模拟用户的输入:该命令不能自动回车换行,一般要加 \r (回车) 或者\ n
​
//send的三种方式
方式一:
expect "密码" {send "123123\r"}     //同一行send部分要有{}
​
方式二:
expect "密码"  
send "123123\r"                    //换行send部分不需要有{}
​
​
方式三:
expect 支持多个分支
expect          //只要匹配了其中一个情况,执行相应的send 语句后退出该expect 语句
只匹配一次
expect
{
{"密码1"  {send "abc123\r"}
{"密码2"  {send "123123\r"}
{"密码3"  {send "123456\r"}
​
}
​
(5) 结束符
expect eof
表示交互结束,等待执行结束,退回到原用户,与spawn对应
比如切换到root用户,expect 脚本默认的等待时间是10s,当执行王命令后,默认停留10s后,自动切回原用户.
​
interact:
执行完成后保持交互状态, 把控制权交给控制台,会停留在目标终端而不是退回到原终端,这时候就可以手工操作了,interact后命令不再起作用,比如interact后添加exit,并不会退出root用户。而如果没有interact则登录完成后会退出,而不是留在远程终端上。
使用interact会保持在终端而不会退回原终端,比如切换到root用户,会一直在root用户状态下;比如ssh到另一台服务器,会一直在目标服务器终端而不会切回原服务器。
#需要注意,expect eof 与 interact 只能二选一。
​
(6)set
expect 默认的超时时间是10秒,通过set 命令可以设置会话超时时间,若不限制超时时间则应设置为-1
例如: set time out 30
​
(7) exp_continue
exp_continue 表示允许 expect 继续向下执行指令.
exp_continue附加于某个expect 判断选项之后,可以是该项被匹配后还能继续匹配expect 判断语句内的其他项。exp_continue类似于控制语句的continue语句。表示允许expect继续向下执行命令。
例如:
expect
{
“(yes/no)” {send “yes\r”;exp_continue;}
“*password” {set timeout 300; send “abc123\r”}
}
​
**注意:**使用exp_continue时,如果跟踪像passwd这样输入密码后就结束进程的命令,expect {}外不要加上expect eof 因为spawn进程结束后悔默认向expect 发送eof,会导致后面的expect eof执行报错
​
(8)send_user
表示回显命令与echo相同
​
(9)接收参数(位置变量)
expect 脚本可以接受从bash命令行传递参数,使用 [lindex $argv n]获得。其中你从0开始,分别表示第一个,第二个,第三个…参数
​
set hostname [lindex $argv 0] 相当于hostname=$1
​
set password [lindex $argv 1] 相当于passswd=$2

七、正则表达式(过滤文本)

REGEXP: Regular Expressions,由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符)不表示字符字面意义,而表示控制或通配的功能,类似于增强版的通配符功能,但与通配符不同,通配符功能是用来处理文件名,而正则表达式是处理文本内容中字符。

正则表达式被很多程序和开发语言所广泛支持:vim, less,grep,sed,awk, nginx,mysql 等

1.正则表达式的作用

主要用来匹配字符串(命令结果,文本内容)

通配符匹配文件(而且是已存在的文件)

  • 基本正则表达式
  • 扩展正则表达式

可用 man 7 regex man手册帮助

2.元字符(字符匹配)

元字符

.   匹配任意单个字符,可以是一个汉字  
[]   匹配指定范围内的任意单个字符,示例:[zhou][0-9][][a-zA-Z]   [:alpha:]
[^] 匹配指定范围外的任意单个字符,示例:[^zhou] [^a.z] [a.z]
特殊含义
[:alnum:]字母和数字
[:alpha:]代表任何英文大小写字符,亦即 A-Z, a-z
[:lower:]小写字母,示例:[[:lower:]],相当于[a-z]
[:upper:]大写字母
[:blank:]空白字符(空格和制表符)
[:space:]包括空格、制表符(水平和垂直)、换行符、回车符等各种类型的空白,比[:blank:]包含的范围
[:cntrl:]不可打印的控制字符(退格、删除、警铃...)
[:digit:]十进制数字
[:graph:]可打印的非空白字符
[:print:]可打印字符
[:punct:]标点符号
[:xdigit:]十六进制数字

使用示例:

//元字符 .  此处的点(.)代表字符
[root@localhost opt]#ls /etc/|grep rc[.0-6]
rc0.d
rc1.d
rc2.d
rc3.d
rc4.d
rc5.d
rc6.d
rc.d
rc.local
​
​
//r..t   ..代表任意两个字符 
[root@localhost opt]#grep r..t /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
​
​
//表示原来的点需要加\转义
[root@localhost ~]# echo abc |grep a.c        
abc//不加引号有时匹配会有出入
[root@localhost ~]# echo abc |grep a.c
abc//标准格式需要加'' 或者""
[root@localhost ~]# echo abc |grep 'a.c'          
​
//通配符
[root@localhost ~]# ls [a-d].txt               
a.txt  A.txt  b.txt  B.txt  c.txt  C.txt  d.txt//真正的小写在正则表达式中
[root@localhost ~]# ls |grep '[a-d].txt'       
a.txt
b.txt
c.txt
d.txt

3.表示次数

*    匹配前面的字符任意次,包括0次,贪婪模式:尽可能长的匹配
.*   任意长度的任意字符,不包括0次
?   匹配其前面的字符出现0次或1次,即:可有可无
+   匹配其前面的字符出现最少1次,即:肯定有且 >=1 次
{n}匹配前面的字符n次
{m,n} 匹配前面的字符至少m次,至多n次
{,n}  匹配前面的字符至多n次,<=n
{n,}  匹配前面的字符至少n次

示例:

[root@localhost opt]#echo google |grep 'go{2}gle'
google
[root@localhost opt]#echo gooogle |grep 'go{2}gle'
[root@localhost opt]#
//表示指定o出现2次
​
[root@localhost opt]#echo gooooogle |grep 'go{2,5}gle'
gooooogle
[root@localhost opt]#echo gooooooogle |grep 'go{2,5}gle'
[root@localhost opt]#
//指定o出现2次及以上,5次及以下
​
[root@localhost opt]#echo google |grep "go+gle"
google
​

4.位置锚定

符号含义
^行首锚定, 用于模式的最左侧
$行尾锚定,用于模式的最右侧
^PATTERN$用于模式匹配整行 (单独一行 只有root)
^$空行
^[[:space:]]*$空白行
\ < 或 \b词首锚定,用于单词模式的左侧(连续的数字,字母,下划线都算单词内部)
\ > 或 \b词首锚定,用于单词模式的左侧(连续的数字,字母,下划线都算单词内部)
\ 匹配整个单词

示例:

过滤出不是以#号开头的非空行
[root@localhost opt]#grep "^[^#]" /etc/fstab
/dev/mapper/centos-root /                       xfs     defaults        0 0
UUID=e45c4df7-fe8f-41e0-ab99-5f16e1968a8f /boot                   xfs     defaults        0 0
/dev/mapper/centos-swap swap                    swap    defaults        0 0
​
​
//[^#]表示不是#的字符,^[^#]表示不以#开头

5.分组和其他

分组:() 将多个字符捆绑在一起,当作一个整体处理,如:(root)+ 后向引用:分组括号中的模式匹配到的内容会被正则表达式引擎记录于内部的变量中,这些变量的命名 方式为: \1, \2, \3, ... \1 表示从左侧起第一个左括号以及与之匹配右括号之间的模式所匹配到的字符

示例

[root@localhost ~]#echo abccc |grep "abc{3}"
abccc[root@localhost ~]#echo abcabcabc |grep "(abc){3}"
//分组 匹配abc
abcabcabc[root@localhost ~]#echo 1abc |grep  "1|2abc"
//只匹配了1
1abc[root@localhost ~]#echo 1abc |grep  "(1|2)abc"
//1abc或者2abc
1abc[root@localhost ~]#ifconfig ens33|grep netmask|grep -o '([0-9]{1,3}.){3}[0-9]{3}'|head -1
192.168.82.100

扩展正则表达式

格式:

grep -E

egrep

字符方面与正则表达式字符的相差不大

表示次数

符号含义
*匹配前面字符任意次
?0或1次
+1次或多次
{n}匹配n次
{m,n}至少m,至多n次
{,n}匹配前面的字符至多n次,<=n,n可以为0
{n,}匹配前面的字符至少n次,<=n,n可以为0

表示分组

也与正则表达式相差不大

() 分组
分组:() 将多个字符捆绑在一起,当作一个整体处理,如:(root)+
后向引用:\1, \2, ...
| 或者  
a|b ab
C|cat C或cat
(C|c)at Cat或cat
​

扩展正则表达式示例:

18.png

19.png