更多内容请点击:
Linux学习从入门到打死也不放弃,完全笔记整理(持续更新,求收藏,求点赞~~~~)
https://blog.51cto.com/13683480/2095439
第14章,Shell脚本编程进阶
本章内容:
条件判断
循环
信号捕捉
函数
数组
高级字符串操作
高级变量
Expect
过程式编程语言执行方式:
顺序执行,选择执行,循环执行
条件选择-----------------------------------------------------------------------
if语句:
结构: 可嵌套
单分支:
if 判断条件;then
条件判断为真的执行代码
fi
双分支:
if 判断条件;then
条件判断为真的执行代码
(此段若想为空,使用":")
else
条件判断为假的执行代码
fi
多分支:
if 条件判断1;then
cmd..
elif 条件判断2;then
cmd..
elif 条件判断3;then
cmd..
else
以上条件全为假的分支代码
fi
case语句:
格式:
case 变量引用 in
pat1)
分支1
;;
pat2)
分支2
;;
...
*)
默认分支
;;
esac
注意: case支持glob风格的通配符
* 任意长度任意字符
? 任意单个字符
[abc] 指定范围内的任意单个字符
a|b a或b
循环执行------------------------------------------------------------------------
将某代码段重复多次运行
重复运行多少次
循环次数事先已知
循环次数事先未知
有进入条件和退出条件
for,while,until
for循环
格式:
for 变量名 in 列表;do
循环体代码
done
机制:
依次将列表中的元素赋值给"变量名"
每次赋值后即执行一次循环体,直到列表中的元素耗尽,循环结束
列表生成方式:
1 直接给出列表
2 整数列表
{1..10..2}
$(seq 1 2 10)
3 返回列表的命令
$(cmd)
4 使用glob,如:*.sh
5 变量引用
$@,$*
while循环
格式:
while CONDITION;do
循环体
done
CONDITION :
循环控制条件
进入循环之前,先做一次判断
每一次循环之后会再次做判断
条件为true,则执行一次循环,直到条件测试状态为false 终止循环
因此: CONDITION一般应该有循环控制变量,而此变量的值会在循环体不断的被修正
进入条件:CONDITION为true
退出条件:CONDITION为false
until循环
格式:
until CONDITION;do
循环体
done
进入条件:CONDITION 为 false
退出条件:CONDITION 为 true
循环控制语句:
用于循环体中
continue [N] 默认为1
用于循环体中,提前结束第N层的本轮循环,而直接进入下一轮判断
所在层为第1层
break [N] 默认为1
用于循环体中,提前结束第N层循环,所在层为第1层
shift [N] 默认为1
用于将参数列表list 左移指定的次数,默认1次
参数列表list 一旦被移动,最左端的那个参数就从列表中删除。
while循环遍历位置参数列表时,常用到shift
创建无限循环:
1 while true;do
循环体代码
done
2 until false;do
循环体
done
特殊用法:
while 循环的特殊用法(遍历文件的每一行)
while read 变量名;do
循环体代码
done < /file
一次读取file 文件中的每一行,且将行赋值给变量
PS: 也可以使用 cmd |while read;do
循环体
done
但是循环中的数组值貌似不能输出,即如在done之后echo 数组中的值为空 ,需要注意
双小括号方法,即((....))格式,也可以用于算术运算
也可以是bash实现C语言风格的变量操作
i=10
((i++))
for 循环 的特殊格式:
for((控制变量初始化;条件判断表达式;控制标量的修正表达式));do
循环体代码
done
如: for ((i=1;i<=10;i++));do
echo $i
done
控制变量初始化:
仅在运行到循环代码段时执行一次
控制变量的修正表达式:
条件判断表达式:进入循环先做一次条件判断,true则进入循环
每轮循环结束会先进行控制变量的修正运算,而后在做条件判断
select循环与菜单:
格式:
select 变量 in 列表;do
循环体命令
done
select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示
PS3提示符,等待用户输入
用户输入菜单列表上的某个数字,执行相应的命令
用户输入被保存在内置变量REPLY中
PS2: 多行重定向的提示符
PS3: select的提示符
REPLY 保存select的用户输入
select与case
select是个无限训话,因此要记住用break命令退出循环,或用exit命令终止脚本。
也可以按Ctrl+c退出循环
select 经常和case联合使用
与for循环类似,可以省略in list ,此时使用位置参量
trap 信号捕捉:
trap ‘触发指令’信号
自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作
trap ''信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
注意事项:
信号: 格式可以是 int INT sigint SIGINT 2
信号捕捉之后,虽然不会立刻结束脚本,但是脚本当前运行的命令却会被终止
如: trap 'echo trap a sig2' 2
sleep 10000
ping 192.168.0.1
如果捕捉到2信号,不会退出脚本,但是sleep会被打断,而继续执行ping命令
函数:------------------------------------------------------------------------------
函数介绍:
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,
而是shell程序的一部分
函数和shell程序区别在于:
shell程序在子shell中运行
而shell函数在当前shell中运行。因此在当前shell中,函数可以对shell变量
进行修改
定义函数:
函数由两部分组成:函数名和函数体
help function
语法1:
f_name(){
函数体
}
语法2:
function f_name()
{
函数体
}
语法3:
function f_name{
函数体
}
可以交互式环境下定义函数,如:
[root@centos6 ~/bin]$func_name1(){
> echo nihao
> echo wohenhao
> ping -c1 -w1 192.168.0.1
> }
[root@centos6 ~/bin]$func_name1
nihao
wohenhao
connect: Network is unreachable
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
函数调用:
函数只有被调用才会执行
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:
被调用时创建,返回时终止
在脚本中用户前必须定义,因此应该将函数定义放在脚本开始部分,直至shell首次
发现它之后才能使用
调用函数仅使用其函数名即可
函数返回值:
函数由两种返回值:
函数的执行结果返回值:
1 使用echo等命令进行输出
2 函数体中调用命令的输出结果
函数的退出状态码:
1 默认取决于函数中执行的最后一条命令的退出状态码
2 自定义退出状态码,return命令
return : 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回
return 1-255 有错误返回
删除函数:
unset func_name1
函数文件:
创建函数文件:
#!/bin/bash
# function.main
f_hello()
{
echo helio
}
f_func2(){
...
}
...
系统自带函数文件:/etc/rc.d/init.d/functions
使用函数文件:
可以将进程使用的函数存入函数文件,然后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系,如 function.main
一般函数文件载入shell,就可以在命令行或脚本中调用函数,
可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数,改动完毕后,再重新载入此文件
载入函数:
要想使用已创建好的函数文件,要将他载入shell
使用
. /path/filename
source /path/filename
如果使用source 载入函数之后,对函数文件的某个函数做了修改,需要unset函数之后重新载入
unset func_name1
或者exit 重新登录然后再次source
默认本shell进程有效,如需函数子进程有效,需声明
export -f func_name
declare -xf
如需查看当前所有的全局函数
export -f 或 declare -xf
例如:
[root@centos6 ~/bin]$export -f func_release
[root@centos6 ~/bin]$export -f
func_release ()
{
declare rel=$(cat /etc/centos-release|tr -dc [0-9]|cut -c1);
echo $rel
}
declare -fx func_release
[root@centos6 ~/bin]$
函数参数:
函数可以接受参数:
调用函数时,在函数名后面以空白分隔给定参数列表即可;
例如 func_name arg1 arg2 ...
在函数体当中,可以使用$1,$2..调用这些参数;还可以使用$@,$*,$# 等特殊变量
注意区分脚本的位置参数和传递给函数的位置参数
函数变量:
变量作用域:
环境变量: 当前shell和子shell有效
本地变量: 只在当前shell进程有效,为执行脚本会启动专用shell进程;
因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
局部变量: 函数的生命周期;函数结束时变量会被自动销毁
注意: 如函数中的变量名同本地变量,使用局部变量
函数中定义局部变量:
local name=
declare name= declare自带局部变量属性
declare -ig 在函数中定义普通变量,centos6不支持
函数递归:
函数直接或间接调用自身
注意递归层数
函数递归示例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且有0的
阶乘为1,自然数n的阶乘写作n!
n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n
n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!
示例:
fact.sh
#!/bin/bash
func_factorial()
{
if [ $1 = 0 ];then
echo 1
else
echo $[$1*`func_factorial $[$1-1]`]
fi
}
func_factorial $1
fork×××:
fork×××是一种恶意程序,它的内部是一个不断fork进程的无限循环,实质是一个简单
的递归程序。由于程序是递归的,如果没有任何显示,这会导致整个简单的程序迅速
耗尽系统所有资源
函数实现:
:(){ :|:&};:
bomb(){ bomb|bomb&};bomb
脚本实现:
cat bomb.sh
#!/bin/bash
./$0|./$0&
多种语言版本
数组---------------------------------------------------------------------------------
变量: 存储单个元素的内存空间
数组: 存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引:
索引:编号从0开始,属于数值索引
bash4.0版本之后,索引可以支持使用自定义的格式,而不仅是数值格式,即为关联索引
bash中的数组支持稀疏格式(索引不连续)
声明数组:
declare -a array_name
declare -A ARRAY_NAME 关联索引
彼此不可互相转化
数组赋值:
数组元素的赋值:
1 一次只赋值一个元素
array_name[index]=VALUE
如:
weekdays[0]="sunday"
weekdays[4]="thursday"
2 一次赋值全部元素
array_name=("VAL1" "val2" "val3"...)
3 只赋值特定元素
array_name=([0]=varl [3]=val2...)
4 交互式数组值对赋值
read -a array_name1
如:
[root@centos7 ~]$read -a array_name1
monday tusday wensday thursday
[root@centos7 ~]$echo ${array_name1[@]}
monday tusday wensday thursday
注意:
如果先赋值单个元素array[0]=a,
再使用赋值全部 array=(b c d) 或者特定赋值 array=([1]=e [2]=f [3]=g)
会使之前单个元素array[0]被覆盖消失
索引数组可以无需声明直接赋值使用
关联数组必须先声明之后才能赋值使用
如:[root@centos7 ~]$array3[0]=mage
[root@centos7 ~]$array3[1]=zhangsir
[root@centos7 ~]$echo ${array3[*]}
mage zhangsir
[root@centos7 ~]$echo ${array3[1]}
zhangsir
[root@centos7 ~]$echo ${array3[0]}
mage
[root@centos7 ~]$array4[ceo]=mage
[root@centos7 ~]$array4[cto]=zhangsir
[root@centos7 ~]$echo ${array4[*]}
zhangsir
直接赋值使用关联数组会赋值失败,只显示最后一个值
数组引用:
引用数组元素:
${array_name[index]}
注意:省略[index表示引用下标为0的元素]
引用数组所有元素
${array_name[@]}
${array_name[*]}
数组的长度(数组中元素的个数)
${#array_name[*]}
${#array_name[@]}
删除数组中的某元素:导致稀疏格式
unset array[index]
删除整个数组
unset array
数组数据处理
引用数组中的元素:
数组切片:${array[@]:offset:number}
offset: 要跳过的元素个数
number:要取出的元素个数
${array[0]:offset} 取偏移量之后的所有元素
${array[0]: -n} 取最后n个元素
${array[0]::2} 取开头2个元素
${array[0]: -m:-n} 跳过最后第n+1个到第m个元素间的所有元素
向数组中追加元素:
array[${#array[*]}]=
关联数组:
declare -A array_name
array_name=([idx_name1]=val1 [idx_name2]=val2 ...)
字符串处理-------------------------------------------------------------------------
字符串切片:
${#var}: 返回字符串变量var的长度
${var:offset}: 返回字符串变量var中从第off个字符后(不包括第offset个字符)的字符
开始,到最后的部分,offer的取值在0到${#var}-1之间(bash4.2之后允许为负值)
${var:offset:number}: 返回字符串变量var中第off个之后的num个字符(不包含off)
${var: -n}: 取字符串最右侧那个字符(冒号后需加一个空格)
${var: -n:-m}: 取倒数第m+1个字符 到 倒数第n个字符
基于模式取子串:
${var#*word} 其中var为变量名,不需要加$引用,word可以是指定的任意字符串
功能:自左而右,查找var变量所存储的字符串中,第一次出现的word,删除字符串开头
至第一次出现word字符之间的所有字符
${var##*word}
贪婪模式,删除字符串开头到最后一次”word“指定的字符之间的所有内容
${var%word*} 其中word可以是指定的任意字符串
功能: 自右边而左,查找var变量所存储的字符串中,第一次出现word,删除字符串最后一个字符
向左至第一次出现word字符之间的所有字符
${var%%word*}
自右而左,删除至最后一个word所指定的字符串
查找替换:
${var/pattern/substr}:
查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}:
替换所有能被pattern所匹配到的字符串,以substr替换
${var/#pattern/substr}
行首被pattern匹配,并替换
${var/%pattern/substr}:
行尾被pattern匹配,并替换
查找删除:
${var/pattern}: 删除第一次被pattern匹配到的字符串
${var//pattern}: 删除所有被pattern匹配到的字符串
${var/#pattern}: 删除pattern为行首所匹配到的字符串
${var/%pattern}: 删除pattern为行尾所匹配到的字符串
字符串大小写转换:
${var^^} 把var中所有的小写字母转换为大写
${var,,} 把var中所有的大写字母转换为小写
高级变量用法:-------------------------------------------------------------------
变量赋值:
var=${str-expr} str为变量,expr为字符串
如果str没有没配置,var=expr
如果str配置且为空,var=
如果str配置且非空,var=$str
var=${str:-expr}
如果str没有配置或者为空,var=expr
如果str已配置且非空: var=$str
其他诸多用法,此处不一一列举,如有需要查看相关表格查询
高级变量用法:有类型变量
shell变量一般是无类型的,但是bash shell提供了declare和同样typeset两个命令
用于指定变量的类型,两个命令是等价的
declare:
declare [option] [var]
-r 声明或显示只读变量
-i 整型数
-a 数组
-A 关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数
-xf 全局函数
-l 声明变量为小写字母
-u 声明变量为大写字母
declare -ig 在函数中定义普通变量,centos6不支持
eval:
eval命令将会首先扫描命令进行所有的置换,然后再执行该命令。该命令适用于那些一次
扫描无法实现其功能的变量:该命令对变量进行两次扫描
示例: eval echo {1..$i}
间接变量引用:
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值
就称为间接变量引用
如:
var1=var2
var2=nnn
bash 提供了两种格式实现间接变量引用
eval var=\$$var1
var3=${!var1}
如:
var1=var2
var2=nnn
var3=${!var1} 或者 eval var3=\$$var1
echo $var3
nnn
创建临时文件:---------------------------------------------------------------------
mktemp :
创建并显示临时文件,可避免冲突
mktemp [option]..[template]
template: filenameXXX
-d 创建临时目录
-p DIR 或 --tmpdir=DIR 指明临时文件所存放目录位置
示例:
tmpfile1=`mktemp httptmp-XXX`
tmpdir=`mktemp -d /tmp/roottmp-XXXX`
tmpdir=`mktemp --tmpdir=/tmp roottmp-XXXX`
安装复制文件:
install 命令:
install [options] source dest 单文件
install [] source dir 单文件
install [] -t dir source 单文件
install [] -d dir 创建空目录
选项:
-m mode 默认755
-o owner
-g group
示例:
expect介绍-----------------------------------------------------------------------
expect 是由Don Libes 基于Tcl(Tool Command Language)语言开发的
主要应用于自动化交互式操作的场景,借助expect处理交互的命令,可以将交互过程
如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台
服务器执行相同操作的环境中,可以大大提供系统管理人员的工作效率
expect命令:
expect [选项] [-c cmds] [[ -f[f|b]] cmdfile ] [args]
选项:
-c 从命令行执行expect脚本,默认expect是交互地执行的
示例:expect -c 'expect "hello" { send "you said hello\n" }'
-d 可以输出调试信息
示例:expect -d ssh.exp
expect中的相关命令
spawn 启动新的进程
send 用户向进程发送字符串
expect 从进程接受字符串
interact 允许用户交互
exp_continue 匹配多个字符串,在执行动作后加此命令
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
expect "hi" { send "you said hi \n"}
匹配到hi后,会输出"you said hi",并换行
多分支模式语法:
expect "hi" { send "You said hi\n" } \
"hehe" { send "Hehe yourself\n" } \
"bye" { send "Good bye\n" }
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send "Good bye\n"}
}
expect 示例:
ssh自动键入密码
#!/usr/bin/expect
spawn ssh 192.168.65.132
expect {
"yes/no" { send "yes\n";exp_contunue }
"password" { send "112233\n"; }
}
interact
#expect eof
scp自动键入密码
#!/usr/bin/expect
spawn scp /etc/passwd 192.168.65.132:/data
expect {
"yes/no" { send "yes\n";exp_continue }
"password" {send "112233\n" }
}
expect eof
自动从文件获取ip地址,且登录ip地址机器的root账号,并创建账号
也可以不用条用,直接在bash脚本中引用expect代码
cat ssh.exp
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd user1\n" }
expect "]#" { send "echo nagedu |passwd --stdin user1\n"}
send "exit\n"
#interact
expect eof
cat sshauto.sh
#!/bin/bash
while read ip;do
user=root
password=112233
ssh.exp $ip $user $password
done < /root/bin/ip.txt
自动从文件获取ip地址,并scp同一文件到所有主机的root目录.txt
#!/bin/bash
declare password=112233
while read ip;do
expect < spawn scp /root/bin/scpauto.sh $ip:/root expect { "password" { send "112233\n" } } expect eof EOF done < /root/bin/ip.txt 笔记整理完成时间:2018年5月16日20:21:12