顺序执行
选择执行
循环执行
顺序执行则是最简单的流程,按照输入指令的顺序逐条执行
就是根据一些判断的语句,选择性的执行某些分支命令,不执行某些命令。例:if语句;case语句
根据一些条件来判断,是执行true执行true那一部分,是false则退出循环语句。
二、条件选择if语句If选择语句分为两种,一种就是单分支if,一种就是多分支语句。If语句支持嵌套。
语法格式
if 条件 ;then
命令块1(可以是多个命令)
else
命令块2(可以是多个命令)
fi
如果符合该条件,则执行命令块1处的命令不执行命令块2处的命令,如果条件不符合,则跳过命令块1处的命令不执行,直接执行命令块2处的命令,条件多为一个判断命令,返回值为0(true)执行命令块1,返回值为1(false)执行命令块2
例如:
if $(id $1 &> /dev/null);then
echo "$1 alread exist"
exit 1
else
useradd $1
fi
语法格式:
If 条件1 ;then
命令块1
elif 条件2 ;then
命令块2
elif 条件3;then
命令块3
……
elif 条件n;then
命令块n
else
命令块n+1
fi
执行顺序是,先判断条件1,返回值为true则执行命令块1,然后退出if语句
如果条件1,返回值为false,则继续判断条件2,返回值为true,则执行命令块2,然后退出if语句,若条件2 返回值为false,则继续判断条件3……,如果所有条件返回 值都为false,则执行else后面的命令块n+1,然后退出if语句
例如:
#!/bin/bash
if [ $1 -lt 3 ];then
echo redhat
elif [ $1 -eq 3 ];then
echo green
elif [ $1 -gt 3 -a $1 -lt 5 ];then
echo yellow
else
echo white
fi
case条件判断语句是一个多分支结构的,适合用于分支多的情况,而且比if多分支语句更加简洁一点,但是case语句不可嵌套。
case 变量 in
变量值1)命令块1;;
变量值2)命令块2;;
变量值4)命令块3;;
变量值n)命令块n;;
esac
执行顺序是,先调用一个变量,然后判断变量值是否符合变量值1,如果相等,则执行命令块1,如果不符合则判断变量值2,……一直到变量值n,如果都不符合则则什么都不执行或执行*后面的操作,*代表除了上面值所有值
例如:
case $1 in
1)echo redhat;;
2)echo yellow;;
3)echo green;;
*)echo blue;;
esac
循环执行的特点:
将某代码段重复运行多次
重复运行多少次:
循环次数事先已知
循环次数事先未知
有进入条件和退出条件
for循环语句的格式1:
for 变量名 in 变量值列表;do
循环体
done
执行机制:依次将列表中的元素赋值给“变量名”;每次赋值后即执行一次循环体;直到列表中的元素耗尽,循环结束,for循环是先赋值在判断,
列表生成方式:
(a){start..end}
(b) $(seq [start [step]] end)
$(cmd)
$@,$*
while循环语句语法格式:
while 循环控制条件 ;do
循环体
done
执行机制:进入循环之前,先做一次判断;每一次循环之后会再做判断;条件为true则执行一次循环;知道条件测试状态为false终止循环。因此循环控制条件一般应该有变量,而变量的值会再循环体中发生变化,而且变量的第一次赋值应该在循环的前面,否则无法进行判断,
使用while创建无限循环
while true;do
循环体
done
until循环语句格式:
until 循环控制条件;do
循环体
done
执行机制:until跟while语法格式差不多,而且都是先判断再循环,不同的是while是条件测试返回值为真进入循环,为假则退出循环,而until则是条件测试返回值为假则进入循环,为真时退出循环
使用unti创建无限循环
until false;do
循环体
done
Continue用于循环体中用于提前结束本轮的循环,不再执行后续的命令,而直接进入下一轮循环,而不是将整个循环体都退出,比如在循环体内写入某一个条件,当满足这一条件是,执行continue这个命令,就不会再执行循环体内continue后面的那些命令了,然后直接进入下一轮的循环,一直到循环结束。
示例:编写一个脚本shuzi.sh,输出1到10 的数字内容如下:
如图所示,脚本中输出1到10的数字,但是当在第5轮的循环中,符合条件i等于5将会执行continue命令,而本轮循环中后面的echo $i就不会再执行了,而是退出这第五轮的循环,直接进入下一轮循环。所以这1到10 的数字中不会输出5输出结果如下图:
Break用于循环体内,用退出整个循环体,不再执行后续的循环,比如本来需要执行10次循环的,但当符合第5层循环时,符合某以条件而执行了break命令,那么就会退出当前的循环体,而后续的循环也不再执行了。
例:编写一个脚本shuzi2.sh,输出1到10的数字
如图所示,脚本中依旧是输出1到10的数字,不同的就是,当i等于5时执行break命令,如此就会退出这个循环,而不输出5及之后的所有数字,执行结果如下:
在嵌套循环中,也就是多层循环体中break仅退出包含break命令的当前循环,而不会退出其他的循环体或本层循环的上一层循环。
例:编写一个脚本qt.sh,打印九九乘法表
上图中是有两层循环体的,这就是循环的嵌套使用,循环中包含着一个循环,在这种情况中,break在里面那一层循环中,当符合m不小于i时就会执行break,也就是在第一轮循环中i=1;m=1,然后判断m是否小于等于i ,返回值为true,输出1x1=1;然后执行里面的那个循环的第二轮,m++=2,i=1,判断m是否小于等于i,返回值为false,执行break,退出了当前循环,但是外面那一层循环体并不会退出,会依旧循环执行i++=2,m=1,判断m是否小于等于i,返回值为true,输出1x2=2;然后执行内循环的第二轮循环i=2,m++=2,再次判断m是否小于等于i,返回值为true,输出2x2=4;执行内循环的第三轮,i=2,m++=3,判断m是否小于等于i,返回值为false,执行break,退出内循环,继续外循环的第三轮循环......执行 结果如下图:
区别在于continue是退出某一循环体的某一轮的循环,但是循环体本身不退出,不、妨碍后续的循环,break是果断退出当前的整个循环体,使当循环体不再循环。
六、循环控制shift命令 shift用于将参数列表左移指定次数,缺省为左移一次,。参数列表一旦被移动,最左端的那个参数就从列表中删除。While循环遍历位置参数列表时,常用到shift
用法:shift [n]
如下图中所示,编写一个脚本,使用while循环显示所有的参数时,如果不使用shift,则参数数量不会发生变化,使其成为了死循环,一直显示所有的参数
如下图中所示,在循环中添加一个语句shift,则会在每次循环时,将参数从左向右移一次,而且移动后,坐左边的那个参数就被删除了,也不再显示,
示例:显示所有的参数,一次显示一个参数,一个参数占一行
While循环还有一个特殊用法,可以遍历文件的每一行,下面我们编写一个脚本和一个文件如下图中,使用脚本读取gushi中的每一行美容并显示出来:
然后我们执行以下看看结果,这个执行步骤就是,依次读取/app/gushi文件中的每一行,且将值赋值给变量line,直到把所有的行都赋值给变量然后才结束循环;这个方法就有点跟for循环相似了,给变量赋多个值,每一个值执行一次循环,知道把所有的值读取完循环就结束,
双小括号使用方法,即((...))格式
var=$((算术表达式))
((i++))
for ((控制变量初始值;条件判断表达式;控制变量的修正表达式))
do
循环体
done
控制变量的初始化值:仅在运行到循环的第一轮循环会执行一次,
条件判断表达式:在每一轮循环执行开始之前都会执行一次,这个判断表达式的值为true则执行循环一次,如果值为false,则退出这个循环。
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后在做条件判断,这里通常会使变量的值发生改变。
示例:如下图图1中所示,第一轮循环i=1,然后输出1,;第二轮开始先给执行i++,然后i的值就是2了,再判断i是否小于等于10,值为true则执行一次循环体输出i的值2;然后进入第三轮循环,先执行i++,这个时候i的值编程了3,在判断i的值是否小于等于10,值为true执行循环体输出i的值3,进入下一轮循环……进入第10轮循环,先执行i++,i的值是10 ,判断i是否小于等于10,值为true,执行循环体输出i的值10;进入第十轮循环,执行i++,i的值为11,判断i是否小于等于10,值为false,退出循环。所以虽然最后只输出到10,但是因为for循环是先执行控制变量的修正表达式,然后在执行判断表达式判断值是真是假再决定是否继续执行循环体,所以循环体结束后i的值已经是11了,如下图图2显示,在循环之外再显示一次i的值
Select的特点:
1.Select循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示PS3提示符,等待用户输入
2.用户输入菜单列表中的某个数字,执行相应的命令
3.用户输入被保存在内置变量REPLY中
4.Select是个无限循环,因此可以使用break命令退出循环,或用exit命令终止脚本,也可以按Ctrl+c退出循环
5.Select经常跟case联合使用
6.与for循环类似,可以省略in list ,此时使用位置参量
7.Select的list的参数值以空格分隔,可以是英文,也可以是中文,如参数中包含有空格,得加双引号
8.Select循环可以嵌套
Select的语法格式:
select 变量名 in list
do
循环体命令
done
示例:
如上图中所示,我编写了一个简单的菜单系统,选择要吃的饭;输入提示符可以更改变量PS3的值,在想要执行变量为其中某个值时要做的事,需输入相应的序号,当输入一个没有的序号或其他字符时则输出一行空行继续循环,而且这个循环一旦执行是不能退出的,输入exit或quit都没有用,要退出还是得Ctrl+c强制退出。
之前学过进程管理的应该都知道kill命令,进程管理就是向进程发送一些控制信号,来完成对进程的管理控制,我们可以通过kil -l或trap -l来查看当前系统可用的信号
常用的信号有:
SIGHUP:1 通知进程重读配置文件以让新的配置生效,无需重新启动进程
SIGINT:2终止正在运行中的进程,相当于键盘组合键Ctrl+c
SIGQUIT:3 相当于Ctrl+\
SIGKILL:9 强行终止正在运行中的进程
SIGTREM:15 强行终止正在运行中的进程
SIGSTOP:19 暂停前台的进程,使其在后台休眠,相当于ctrl+z
SIGCONT:18 继续运行指定的进程
信号的表示方法:
1.完整名称,例如:SIGINT
2.简写名称,例如:INT
3.数字表示,例如:2
Trap的作用就在于能够捕捉这些信号,使在使用这些信号对进程进行管理时,不执行原操作,而执行我们所指定的操作,比如在执行一个脚本时按Ctrl+c或者执行命令kill -19会终止或中断这个脚本的运行,但是我想屏蔽掉这个信号,使其在运行过程中这个进程是杀不死的,trap就可以实现这个功能,但是有一个信号无法屏蔽,那就是9 SIGKILL,这个是强制性的终止进程,其它的是可以屏蔽掉的。
Trap的语法:
trap ‘触发指令’ 信号
自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作,当单引号内为空什么都没有时,就是什么都不做,仅忽略那个信号。
trap “ ” 信号 :忽略信号的操作
trap “ - ” 信号 :恢复信号的操作
trap -p :列出自定义信号操作
每隔0.5秒输出一个数字,忽略信号int,也就是Ctrl+c这个信号,前10个数字忽略输出“根本就停不下来”,10至20什么都不做,20至30之间恢复信号的操作
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于:
Shell程序在子shell中运行
而shell函数在当前shell中运行,因此在当前shell中,函数可以对shell中变量进行修改
Shell中的代码都是被逐行读取并运行的,而函数部分的命令在被读取的时候是不会被运行的,只有在调用函数的名字时,才会运行已经定义好的函数中的那一部分代码,适用于当某一段语句块需要多次运行的情况下,这些命令就需要多次被读取执行,而调用函数只用编写一遍,后面需要用到的地方调用函数名即可,这样节省我们编写shell脚本的时间
函数由两部分组成:函数名和函数体
函数名部分是用来调用函数体部分的命令
函数体部分就是由需要被多次执行的命令语句块组合成的
语法一:
function_name (){
...函数体...
}
语法二:
funtion function_name {
...函数体...
}
语法三:
function function_name () {
...函数体...
}
Function_name :函数名
{}中间部分就是函数体,在调用函数名的时候回被执行的命令块
示例:使用函数编写一个脚本,计算从1加到100的和
函数的定义和使用:
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
函数的调用:
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
函数有两种返回值:
使用echo等命令进行输出
函数体中调用命令的输出结果
默认取决于函数中执行的最后一条命令的退出状态码
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
函数的执行结果返回值就是函数体内的所有命令的执行结果的输出结果
函数的退出码就是执行完函数后,返回的一个值,就跟在终端上执行的每一个命令都会有一个返回值一样,执行的命令为0,说明这个命令是正确的,并执行成功了,为除了0以外的所有值皆为错误,错误的返回值再0-255之间,函数也是这样,在函数中没有指定返回值,那么这个函数的返回值皆由这个函数的函数体内的最后一条命令的返回值为函数的返回值,要如果指定函数的返回值,就在函数体内最后一行编辑return命令,自定义函数返回值,return命令一定要在最后一行,因为函数中读取到return命令后,那么其后面的命令就不不会再执行,然后退出函数,函数中return命令就好像脚本的exit命令会退出这个脚本,return命令会退出这个函数,但是函数外的命令依旧会执行。
示例:
交互式环境下定义函数就是不在脚本中定义的函数,在终端上定义的函数
语法:
function_name (){
> 函数体
> }
定义函数后,调用函数是在命令行界面输入函数名即可,就会执行函数体部分的所有命令
该函数在定义后将一直保留到用户从系统推出,或执行了卸载函数的命令:
unset 函数名
示例:
函数在使用前必须定义,因此颖将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用函数名即可
示例:
可以将经常使用的函数存入函数文件然后将函数文件载入shell
文件名可以任意选取,但最好与相关任务有某种联系。例如:functions.main
一旦函数文件载入shell,就可在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
创建函数文件依旧需要在首行编辑/bin/bash
函数文件不需要加执行权限就可以用
一个函数文件中可以定义多个函数,不一定只能有一个,当载入这个函数文件时,这个文件的所有函数都会被载入,可以随时调用使用
函数文件创建好后,要将它载入shell,定位函数文件并载入shell的格式:
. Filename 或 source filenname
注意&#xff1a;<点><空格><文件名> &#xff1a;这里的文件名要带正确路径
示例&#xff1a;
使用set命令检查函数是否已载入&#xff0c;set命令将在shell中显示所有的载入函数
要执行函数&#xff0c;简单的键入函数名即可
对函数做一些改动后&#xff0c;需要先删除函数&#xff0c;使其对shell不可用&#xff0c;使用unset命令完成删除函数&#xff0c;使用unset后&#xff0c;再键入set命令&#xff0c;该函数将不再显示
命令格式&#xff1a;unset 函数名
环境函数&#xff1a;
在当前shell中定义的函数&#xff0c;在子bash中是不可用&#xff0c;想要这个函数在其子进程中也可用可以在定义函数后再使用export -f 函数名&#xff0c;声明全局函数
声明&#xff1a;export -f 函数名
查看&#xff1a;export -f 或declare
示例&#xff1a;
示例&#xff1a;
函数变量就是在函数中定义的变量
变量作用域&#xff1a;
环境变量&#xff1a;当前shell和子shell有效
本地变量&#xff1a;只在当前shell进程有效&#xff0c;为执行脚本会启动专用子shell进程&#xff1b;因此&#xff0c;本地变量的作用范围是当前shell脚本程序文件&#xff0c;包括脚本中的函数
局部变量&#xff1a;函数的生命周期&#xff1b;函数结束时变量被自动销毁
注意&#xff1a;如果函数中有局部变量&#xff0c;如果其名称同本地变量&#xff0c;使用局部变量
在函数中定义局部变量的办法
local NAME&#61;VALUE
示例&#xff1a;
函数内是可以自己调用其他的函数或函数本身&#xff0c;
函数递归&#xff1a;
函数直接或间接调用自身
注意递归层数
示例&#xff1a;利用函数达成无限循环的目的
Fork×××是一种恶意程序&#xff0c;它的内部是一个不断在fork进程的无限循环&#xff0c;是指是一个简单的递归程序&#xff0c;由于程序时递归地&#xff0c;如果没有任何限制&#xff0c;这会导致这个简单的程序迅速耗尽系统里面的所有资源
函数实现
:(){ :|:& };:
Bomb() { bomb|bomb $ }; bomb
脚本实现
#!/bin/bash
./$0|./$0&
示例&#xff1a;
数组是计算机编程语言上&#xff0c;是用于存储多个相同类型数据的集合&#xff0c;把有限个类型相同的变量用一个名字命名&#xff0c;然后用编号区分它们的变量的集合&#xff0c;这个名字称为数组名&#xff0c;编号称为它们的下标&#xff0c;组成数组的各个变量为数组的元素&#xff0c;数组是在程序设计中为了处理方便&#xff0c;把具有相同类型的若干变量按有序的形式组织起来的一种形式&#xff0c;这些按序列排列的同类数据元素的集合称为数组&#xff0c;
变量&#xff1a;存储单个元素的空间
数组&#xff1a;存储多个元素的连续的内存空间&#xff0c;相当于多个变量的集合
索引数组&#xff1a;从0 开始&#xff0c;属于数值索引&#xff0c;下标是数字
关联数组&#xff1a;可支持使用自定义的格式&#xff0c;下标也可以是字符串
注意&#xff1a;数组的下标需放在中括号内
变量需先声明在赋值使用
declare -x &#xff1a;声明或显示环境变量和函数
declare -g &#xff1a;设置函数为全局函数
declare -xf &#xff1a;设置环境函数
declare -a 数组名 &#xff1a;声明索引数组
declare -A 数组名&#xff1a; 声明关联数组
declare -a &#xff1a;查看所有索引数组
declare -A &#xff1a;查看所有关联数组
注意&#xff1a;索引数组和关联数组两者不可相互转换&#xff0c;声明索引数组时&#xff0c;可以省略declare -a&#xff0c;直接赋值就可使用&#xff1b;声明关联数组时&#xff0c;必须使用declare -A声明&#xff0c;否则无法使用
title[0]&#61;1 &#xff1a; 数组名是title&#xff0c;下标是0&#xff0c;这个元素的值是1
title[1]&#61;2 &#xff1a; 数组名是title&#xff0c;下标是1&#xff0c;这个元素的值是2
title&#61;&#xff08;1 2 3 4 5&#xff09; &#xff1a;数组名是title&#xff0c;默认是索引数组&#xff0c;所以下标从0开始&#xff0c;括号内的值分别赋值给title的每个元素&#xff0c;这种方法适用于元素的多个下标连续的情况下
title&#61;([0]&#61;pig [1]&#61;big [3]&#61;bird [5]&#61;fish) &#xff1a;当想要一次性给数组的多个元素赋值但是下标又不连续时
title&#61;({1..10}) &#xff1a;给数组10个元素赋10个连续的值
title&#61;(/root*.sh) &#xff1a;文件名可以使用文件通配符
title&#61;($(echo {1..10})) &#xff1a;把命令的结果赋值给元素时命令必须放在$()中
read -a title &#xff1a;使用read命令手动输入元素的值&#xff0c;以只能赋一个元的值&#xff0c;title是数组名&#xff0c;而且这个值只能赋值下标0这个元素
declare -a &#xff1a;查看所有已声明的数组
declare -a |grep 数组名 &#xff1a;查看某一已声明的索引数组及其所有值
declare -A &#xff1a;查看所有已声明的关联数组
declare -A |grep 数组名 &#xff1a;查看某一已声明的关联数组及其所有值
echo $title &#xff1a;不输入下标&#xff0c;仅引用数组的下标为0的值
echo ${title[n]} &#xff1a;引用指定下标的元素&#xff0c;下标为n
echo ${title[*]} &#xff1a;显示数组的所有值
echo ${title[&#64;]} &#xff1a;显示数组的所有值
echo ${#title[*]} &#xff1a;显示数组总共有多少个元素
echo ${title[${#title[*]}-1]} &#xff1a;显示数组的最后一个值&#xff08;这个数组必须是索引数组&#xff0c;而且下标是连续的数值&#xff09;
echo ${test[&#64;]} &#xff1a;显示数组的每一个值&#xff0c;以空格分隔
unset 数组名&#xff1a;删除这个数组的所有值
unset 数组名[下标] &#xff1a;删除这个数组的其中一个值&#xff0c;导致稀疏模式
数组切片就是数组中有很多个值嘛&#xff0c;想要取出其中几个连续的值。
示例&#xff1a;
test[${#test[*]}]&#61;cat &#xff1a;给索引数组的加值&#xff0c;加到最后一个索引的后面&#xff0c;&#xff08;前提是这个数组的索引必须是连续无空缺的&#xff09;
关联数组赋值前必须先声明&#xff0c;否则会默认为时索引数组调用时会出错
方法一&#xff1a;先声明再赋值
declare -A 数组名 &#xff1a;声明一个关联数组&#xff0c;但是先不赋值
数组名[下标]&#61;元素值 &#xff1a;给这个关联数组其中一个元素赋值
Array_name&#61;([idx_name1]&#61;’val1’ [idx_name2]&#61;’val2’...) &#xff1a;一次性给多个下标赋值&#xff0c;(array_name&#xff1a;数组名&#xff1b;idx_name&#xff1a;数组下标&#xff1b;val&#xff1a;元素值)
方法二&#xff1a;声明并赋值
declare -A 数组名[下标]&#61; 元素值 &#xff1a;声明一个关联数组&#xff0c;并给一个元素赋值
declare -A Array_name&#61;([idx_name1]&#61;’val1’ [idx_name2]&#61;’val2’...) &#xff1a;声明一个数组并同时给数组的多个元素赋值
示例&#xff1a;
声明10 个随机数保存于数组中&#xff0c;并找出其最大值和最小值
${#var}:返回字符串变量var的长度
${var:offset}:返回字符串变量var中从第offset个字符后&#xff08;不包括第offset个字符&#xff09;的字符开始&#xff0c;到最后的部分&#xff0c;offset的取值在0到${#var}-1之间&#xff0c;&#xff08;bash4.2后&#xff0c;允许负值&#xff09;
${var:offset:number}&#xff1a;返回字符串变量var中从第offset个字符后 &#xff08;不包括第offset个字符&#xff09;的字符开始&#xff0c;长度为number的部分
${var: -length}&#xff1a;取字符串的最右侧几个字符 &#xff08;注意&#xff1a;冒号后必须有一空白字符&#xff09;
${var:offset:-length}&#xff1a;从最左侧跳过offset字符&#xff0c;一直向右取到 距离最右侧lengh个字符之前的内容
${var: -length:-offset}&#xff1a;先从最右侧向左取到length个字符开始 &#xff0c;再向右取到距离最右侧第offset个字符之间的内容&#xff08;注意&#xff1a;-length前空格&#xff09;
${var#*word}&#xff1a;其中word可以是指定的任意字符
功能&#xff1a;自左而右&#xff0c;查找var变量所存储的字符串中&#xff0c;第一 次出现的word, 删除字符串开头至第一次出现word字符之间的 所有字符
${var##*word}&#xff1a;同上&#xff0c;贪婪模式&#xff0c;不同的是&#xff0c;删除的 是字符串开头至最后一次由word指定的字符之间的所有内容
${var%word*}&#xff1a;其中word可以是指定的任意字符&#xff1b;
功能&#xff1a;自右而左&#xff0c;查找var变量所存储的字符串中&#xff0c;第一 次出现的word, 删除字符串最后一个字符向左至第一次出现 word字符之间的所有字符&#xff1b;
${var%%word*}&#xff1a;同上&#xff0c;只不过删除字符串最右侧的字符向 左至最后一次出现word字符之间的所有字符&#xff1b;
${var/pattern/substr}&#xff1a;查找var所表示的字符串中&#xff0c;第 一次被pattern所匹配到的字符串&#xff0c;以substr替换之
${var//pattern/substr}: 查找var所表示的字符串中&#xff0c; 所有能被pattern所匹配到的字符串&#xff0c;以substr替换之
${var/#pattern/substr}&#xff1a;查找var所表示的字符串中&#xff0c; 行首被pattern所匹配到的字符串&#xff0c;以substr替换之
${var/%pattern/substr}&#xff1a;查找var所表示的字符串中&#xff0c; 行尾被pattern所匹配到的字符串&#xff0c;以substr替换之
${var/pattern}&#xff1a;删除var所表示的字符串中第一次被 pattern所匹配到的字符串
${var//pattern}&#xff1a;删除var所表示的字符串中所有被 pattern所匹配到的字符串
${var/#pattern}&#xff1a;删除var所表示的字符串中所有以 pattern为行首所匹配到的字符串
${var/%pattern}&#xff1a;删除var所表示的字符串中所有以 pattern为行尾所匹配到的字符串
${var^^}&#xff1a;把var中的所有小写字母转换为大写
${var,,}&#xff1a;把var中的所有大写字母转换为小写
Shell变量一般是无类型的&#xff0c;但是bash shell提供了declare和typeset两个命令用于指定变量的累心&#xff0c;两个命令是等价的
变量可以先声明变量名&#xff0c;然后再赋值&#xff0c;也可以声明时在其后面同时赋值
使用declare或typeset命令声明变量为某一类型后
仅declare命令作用是查看所有函数
语法&#xff1a;declare [选项] 变量名[&#61;value]
选项&#xff1a;
-r&#xff1a;声明或显示只读变量&#xff08;只读变量声明赋值后&#xff0c;值不可更改&#xff0c;unset命令也不可删除&#xff09;
-i&#xff1a;将变量定义为整型数
-a&#xff1a;将变量定义为索引数组
-A&#xff1a;将变量定义为关联数组
-f&#xff1a;显示已定义的所有函数名及其内容
-F&#xff1a;仅显示已定义的所有函数名
-x&#xff1a;声明或显示环境变量和函数
-l&#xff1a;声明变量为小写字母 declare -l var&#61;UPPER (使用该选项声明变量后&#xff0c;无论给变量赋的值是大写还是小写字母都会被转换为小写字母)
-u&#xff1a;声明变量为大写字母 declare -l var lower &#xff08;同上-l选项&#xff09;
${var:-value}或${var-value}&#xff1a;如果变量var为空或为设置&#xff0c;那么返回value&#xff1b;否则返回var的值
${var:&#43;value}&#xff1a;如果var非空&#xff0c;value&#xff0c;否则返回空值
${var:&#61;value}&#xff1a;如果var为空或未设置&#xff0c;那么返回value&#xff0c;并将value赋值给var&#xff0c;否则返回var的值
${var:?value}&#xff1a;如果var为空或未设置&#xff0c;那么在当前终端打印value&#xff1b;否则返回var的值
为脚本程序使用配置文件&#xff0c;实现变量赋值&#xff1a;
定义文本文件&#xff0c;每行定义“name&#61;value”
在脚本中source此文件即可
eval命令将会首先扫描命令行进行所有的置换&#xff0c;然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量&#xff0c;该命令对变量进行两次扫描
这个解释不太容易理解&#xff0c;就是说&#xff0c;有一个变量的值是一条shell中的命令&#xff0c;那么在调用这个变量时&#xff0c;并不是想调用变量本身的值&#xff0c;而是调用变量的值的那一条命令的结果&#xff0c;eval便可以实现这个功能&#xff0c;
比如给变量a赋值为whoami这个命令&#xff1a;a&#61;whoami;在这种情况下a的值就是whoami这串字符串&#xff0c;但是whoami又是shell中一个命令&#xff0c;我想要调用a的值的时候是这条命令的显示结果&#xff0c;而不是这串字符串&#xff0c;就可以使用eval命令
但是eval只能转换一层&#xff0c;不能多层转换&#xff0c;比如&#xff0c;a&#61;b;b&#61;whoami,使用eval调用b的值是可以调出whoami的显示结果&#xff0c;但是调用a时却不能再一层调用为whoami的结果
示例&#xff1a;
间接变量引用&#xff1a;如果第一个变量的值是第二个变量的名字&#xff0c;从第一个变量引用第二个变量的值就称为间接变量引用
比如&#xff1a;var1的值是var2&#xff0c;而var2又是变量名&#xff0c;var2的值为value&#xff0c;间接变量引用是指通过var1获得变量值value的行为
var1&#61;var2
var2&#61;value
Bash shell提供了三种格式实现间接变量引用
eval echo \$$var1
echo ${!var1}
eval echo ${!var1}
示例&#xff1a;
引用当前bash的进程号:echo $$
在编辑脚本时&#xff0c;在脚本中有时还需要穿件一些其他文件供读取使用&#xff0c;或者存放一些输出的信息&#xff0c;创建文件时就可能出现文件名存在冲突&#xff0c;
或者一个程序同时被多个终端多个用户访问时&#xff0c;临时文件名如果一样的话&#xff0c;将会无法同时被编辑使用&#xff0c;所以临时文件最好使用mktemp命令&#xff0c;创建随机文件名的临时文件&#xff0c;可避免冲突
mktemp&#xff1a;创建并显示临时文件&#xff1b;
仅mktemp命令创建的临时文件保存在/tmp下以tmp开头&#xff0c;后缀是10个字符的随机字母或数字&#xff0c;
语法&#xff1a;mktemp [option] [template]
option&#xff1a;
-d&#xff1a;创建临时目录&#xff1b;&#xff08;文件名也可以包括其绝对路径&#xff09;
-p DIR或--tmpdir&#61;DIR&#xff1a;指明临时文件所存放目录位置&#xff0c;创建临时文件
template&#xff1a;filename.XXX
X至少要出现3个&#xff0c;X代表的是一个随机大小写字母或数字&#xff0c;根据自己要创建的临时文件名的长度确定多少个X
示例&#xff1a;
Install命令是安装或升级软件或备份数据&#xff0c;它的使用权限是所有用户。Install命令和cp米宁雷士&#xff0c;都可将文件目录拷贝至指定的路径下&#xff0c;但是install比cp命令更强大的一点就是它可以允许你控制目标文件的属性&#xff0c;install通常用于程序的Makefile&#xff0c;使用它来讲程序拷贝到目标&#xff08;安装&#xff09;目录
语法&#xff1a;install [option] [参数] source dest &#xff1a;将源文件source拷贝至目标目录dest
option&#xff1a;
-d 或 --directory&#xff1a;将后面所有的参数都作为目录处理&#xff0c;然后创建指定目录的所有主目录
-D source dest&#xff1a;当目标文件目录dest不存在时&#xff0c;会自动递归创建创建目标目录&#xff0c;并源文件拷贝至目标文件
-g 组名 或 --group&#61;GROUP&#xff1a;定义复制后的目标文件的所属组
-o 用户名 或 --owner&#61;OWNER &#xff1a;定义复制后的目标文件的所属主
-m 权限 或 --mode&#61;MODE&#xff1a;定义复制后的目标文件的权限&#xff08;默认755&#xff09;
-p &#xff1a;以源文件的文件访问、修改时间作为相应的目标文件的时间属性
-v &#xff1a;显示复制的过程
-t dest_dir source &#xff1a;将source文件件拷贝至dest_dir目录下&#xff0c;dest_dir必须是个已存在的目录&#xff0c;source可以是单个或多个文件文件&#xff0c;单不能是目录&#xff0c;不能拷贝整个目录下的所有文件
-T source dest&#xff1b;单文件传输&#xff0c;将文件source拷贝至dest&#xff0c;并将文件名更改为目标文件名&#xff0c;source只能是一个文件&#xff0c;不能是目录&#xff0c;dest是目标文件&#xff0c;不能是目录&#xff0c;而且目标文件如果已存在&#xff0c;则会以source文件的内容覆盖目标文件&#xff0c;如果目标文件不存在&#xff0c;则目标文件所在的目录必须存在
注意&#xff1a;当install命令什么选项都不带的情况下&#xff0c;语法为&#xff1a;install source dest&#xff1b;目标文件的默认权限为755&#xff0c;更改时间是当前时间&#xff0c;如目标目录不存在不会递归创建。
当源文件source是单个文件&#xff0c;dest是目录时&#xff0c;将源文件复制到dest目录下&#xff0c;dest不能是未存在目录
当源文件source是单个文件&#xff0c;dest是已存在的文件时&#xff0c;则将source的内容覆盖至dest文件中去
当源文件source是单个文件&#xff0c;dest是未存在的文件时&#xff0c;则dest文件的目录必须存在&#xff0c;然后会在dest的目录下创建一个与dest文件同名的文件&#xff0c;并将source的内容覆盖进去
当源文件是多个文件时&#xff0c;dest必须是一个已存在的目录
Expect是由Don Libes基于Tcl&#xff08;Tool Command Language&#xff09;语言开发的&#xff0c;主要应用于自动化交互式才做的场景&#xff0c;借助expect处理交互式的命令&#xff0c;可以将交互式过程&#xff1a;ssh登录&#xff0c;ftp登录等写在一个脚本上&#xff0c;使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中&#xff0c;可以大大提高系统管理人员的工作效率
要使用expect必须先安装软件才可以使用&#xff0c;包名就是expect&#xff0c;这个包还依赖于tcl包
expect [选项] [-c cmds] [[-f|b] cmdfile] [args]
选项
-c &#xff1a;聪明航执行expect脚本&#xff0c;默认expect是交互地执行的
示例&#xff1a;expect -c ‘expect “\n” {send “pressed enter\n”}’
-d &#xff1a;不执行&#xff0c;仅输出调试信息
示例&#xff1a;expect -d ssh.exp
send&#xff1a;用于向进程发送字符串
expect&#xff1a;从晋城接收字符串
spawn&#xff1a;启动新的进程
Interact&#xff1a;允许用户交互
exp_continue&#xff1a;匹配多个字符串在执行动作后加此命令
send命令接收一个字符串参数&#xff0c;并将该参数发送到进程
示例&#xff1a;send “hello there!\n” 输出hello there
Expect命令和send命令正好相反&#xff0c;expect通常是用来等待一个进程的反馈。 Expect可以接收一个字符串参数&#xff0c;也可以接收正则表达式参数。和上文的send命令结合
示例&#xff1a;
expect “hi\n” {send “you said hi\n”} &#xff1a; 当在标准输入中输入hi\n的时候&#xff0c;输出you said hi\n&#xff0c;&#xff08;\n&#xff1a;换行符&#xff09;
expect “hi\n” {send "you ";send “said\n”} &#xff1a; 当在标准输入中输入hi\n时&#xff0c;输出you said;&#xff08;大括号内可以有多个命令以分号隔开&#xff0c;当标准输入或输出中没有空格符号时&#xff0c;可以不加双引号&#xff09;
Expect最常用的语法是来自tcl语言的模式-动作。这种语法及其灵活。
单一分支式语法&#xff1a;
expect “hi\n” {send “you said hi\n”} &#xff1a;匹配到hi时&#xff0c;会输出you said hi
多分支模式语法&#xff1a;
expect “hi\n” {send “you said hi\n”} \
“hello” {send “hello yourself\n”} \
“bye” {send “good bye\n”}
匹配到hi、hello、bye任意一个字符时&#xff0c;执行相应的输出&#xff0c;等同于如下写法&#xff1a;
expect {
“hi\n” {send “you said hi\n”}
“hello” {send “hello yourself\n”}
“bye” {send “good bye\n”}
}
Spawn&#xff1a;上文中的所有演示都是和标准输入输出进行交互&#xff0c;但是我们更希望他可以和某一个进程进行交互,Spawn命令就是用来启动新的进程的,Spawn后send和expect命令都是和spawn打开的进程进行交互
示例&#xff1a;
注意&#xff1a;\r:\r的作用跟\n一样&#xff0c;表示换行符
到现在为止&#xff0c;我们已经可结合spawn、expect、send自动化的完成很多任务了&#xff0c;但是&#xff0c;如何让人在适当的时候干预这个过程了。比如下载完ftp文件时&#xff0c;扔可以停留在ftp命令行状态&#xff0c;以便手动的执行后续命令。Interact可以达到这些目的&#xff0c;
示例&#xff1a;
在expect多分支模式下&#xff0c;虽有多个匹配的字符&#xff0c;但是匹配到哪一个分支的字符&#xff0c;才执行后面相应的动作&#xff0c;然后其他的都没用了&#xff1b;如果想要匹配完上一个字符串执行过动作后继续匹配下一个字符串执行动作&#xff0c;就在动作后加上exp_continue&#xff0c;可以继续执行下面的匹配&#xff0c;相比较写多个单分支模式来实现这个功能&#xff0c;语句简便了许多
示例&#xff1a;
直接输入expect命令&#xff0c;进入expect模式&#xff0c;可以在这个模式下执行expect脚本&#xff0c;exit命令退出这个模式
示例&#xff1a;
使用-c选项&#xff0c;从命令行执行expect脚本&#xff0c;默认expect是交互地执行
示例&#xff1a;
编写expect脚本时&#xff0c;其后缀名必须是.exp&#xff1b;开头的沙邦必须为#!/usr/bin/expect
示例&#xff1a;
在shell脚本中使用expect命令&#xff0c;脚本后缀名依然是.sh&#xff1b;开头是#!/bin/bash&#xff1b;其他命令都正常使用&#xff0c;只有交互部分放在expect \<\
就是已经编辑好了的expect需要早另一个shell脚本中运行执行这个expect脚本
如需要执行expect脚本的绝对路径为/app/lx/login.exp&#xff1b;那么就在shell脚本中编辑命令expect /app/lx/login.exp&#xff1b;在expect命令后跟expect脚本的绝对路径即可&#xff0c;后面也可以跟参数&#xff0c;例如&#xff1a;expect /app/lx/login.exp a b c&#xff1b;
示例&#xff1a;调用login.exp进行批量管理&#xff0c;给多台服务器创建用户
第一步&#xff1a;编写expect脚本
第二步&#xff1a;编辑存储IP和用户密码的文件
第三步&#xff1a;编辑shell脚本&#xff0c;调用执行expect自动化脚本
第四步&#xff1a;执行shell脚本
在expect脚本中定义变量与shell中不同&#xff0c;需使用set命令&#xff1b;语法格式如下&#xff1a;
set 变量名 值
例&#xff1a;set var abcd 给变量var赋值为abcd&#xff0c;变量名和值之间没有等于号
调用变量跟shell脚本中一样在变量名前加上$;例&#xff1a;$var
超时时间timeout是一个固定的变量&#xff0c;它是用来设置超时时间的&#xff0c;就是在执行完这个expect脚本后&#xff0c;再过多少秒后退出这个脚本&#xff0c;timeout为-1时为永不超时&#xff0c;例如设置超时为10秒&#xff0c;命令为set timeout 10 &#xff1b;单位是秒。
Expect脚本可以接受从bash传递过来的参数&#xff0c;这些参数都存储在数组argv中&#xff0c;可以使用[lindex $argv n]获得&#xff0c;n从0开始&#xff0c;分别是第一个&#xff0c;第二个&#xff0c;第三个...参数&#xff1b;这种参数是位置参数
另外expect的命令行参数参考了c语言的&#xff0c;与bash shell有点不一样&#xff0c;argc存储了参数的个数&#xff1b;argv存储了所有的参数&#xff0c;argv0存储了脚本的名字&#xff0c;[lrange $argv 0 0]表示第一个参数&#xff0c;[lrange $argv 0 4]表示第一个参数到第五个参数
示例&#xff1a;
结束符expect eof 的作用是当执行到这一条命令时&#xff0c;超时时间为多少秒&#xff0c;就停顿多少秒后再执行后续的expect语句&#xff0c;停顿期间任何命令也不能执行&#xff0c;用户也不能交互式&#xff0c;输入任何字符都没有反应&#xff0c;就相当于shell脚本中sleep命令&#xff0c;停顿多少秒后继续运行后续命令
退出expect脚本的命令依旧是exit&#xff0c;exit命令后续的所有命令将不再执行
示例&#xff1a;登录成功后&#xff0c;因为timeout是-1&#xff0c;永不超时&#xff0c;所以会一直停顿&#xff0c;
当使用expect匹配一个字符串时&#xff0c;后面相对应的可以执行多个send语句的&#xff0c;这种情况下就是当连续的多个expect匹配的字符串相同&#xff0c;而后面的命令不同时&#xff0c;
示例&#xff1a;
转:https://blog.51cto.com/13570214/2118947