Linux-Shell脚本编程进阶
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-Shell脚本编程进阶相关的知识,希望对你有一定的参考价值。
本章内容
条件判断
循环
信号捕捉
函数
数组
高级字符串操作
高级变量
Expect
流程控制
过程式编程语言:
顺序执行
选择执行
循环执行
条件选择if语句
选择执行:
注意:if语句可嵌套
单分支
if 判断条件;then
条件为真的分支代码
fi
双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
if 语句
多分支
if 判断条件1; then
条件为真的分支代码
elif 判断条件2; then
条件为真的分支代码
elif 判断条件3; then
条件为真的分支代码
else
以上条件都为假的分支代码
fi
逐条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
If示例
根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null; then
echo 'Station1 is UP'
elif grep "station1" ~/maintenance.txt &> /dev/null
then
echo 'Station1 is undergoing maintenance‘
else echo 'Station1 is unexpectedly DOWN!' exit 1
fi
条件判断:case语句
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac
case支持glob风格的通配符:
*: 任意长度任意字符
?: 任意单个字符
[]:指定范围内的任意单个字符
a|b: a或b
练习
1、编写脚本/root/bin/createuser.sh,实现如下功能:使用一个用户名做为参数,如果指定参数的用户存在,就显示其存在,否则添加之;显示添加的用户的id号等信息
答:
#!/bin/bash
#FileName: createuser.sh
[ $# -ne 1 ] && echo "1 user" && exit
useradd $1 &> /dev/null
if [ $? -ne 0 ];then
echo "user $1 existed"
else
echo "user $1 added successfully"
getent passwd $1
fi
执行结果如下:
[[email protected] bin]#./createuser.sh user1
user user1 added successfully
user1:x:1021:1022::/home/user1:/bin/bash
[[email protected] bin]#./createuser.sh user1
user user1 existed
2、编写脚本/root/bin/yesorno.sh,提示用户输入yes或no,并判断用户输入的是yes还是no,或是其它信息
答:
#!/bin/bash
#FileName: yesorno.sh
read -p "Do you agree? yes or no: " ANS
if [[ $ANS =~ ^([Yy]([Ee][Ss])?)$ ]];then
echo "your name is yes"
elif [[ $ANS =~ ^([Nn][Oo]?)$ ]]:then
echo "your answer is no"
else
echo "Please input yes or no"
fi
执行结果如下
[[email protected] bin]#./yesorno.sh
Do you agree? yes or no: y
your name is yes
[[email protected] bin]#./yesorno.sh
Do you agree? yes or no: n
your answer is no
[[email protected] bin]#./yesorno.sh
Do you agree? yes or no: a
Please input yes or no
3、编写脚本/root/bin/filetype.sh,判断用户输入文件路径,显示其文件类型(普通,目录,链接,其它文件类型)
答:
#!/bin/bash
#FileName: filetype.sh
read -p "Please input a filepath: " path
ls -ld $path &> /dev/null
if [ $? -ne 0 ];then
echo "The file is not existed"
else
type=`ls -ld $path | head -c1`
fi
case $type in
-)
echo "$path is a regular file"
;;
d)
echo "$path is a directory"
;;
b)
echo "$path is a block device file"
;;
c)
echo "$path is a character device file"
;;
l)
echo "$path is a symbolic link"
;;
p)
echo "$path is a named pipe"
;;
s)
echo "$path is a local socket file"
;;
*)
echo "error"
esac
执行结果如下:
[[email protected] bin]#./filetype.sh
Please input a filepath: /etc/fstab
/etc/fstab is a regular file
[[email protected] bin]#./filetype.sh
Please input a filepath: /root
/root is a directory
[[email protected] bin]#./filetype.sh
Please input a filepath: /dev/sda
/dev/sda is a block device file
[[email protected] bin]#./filetype.sh
Please input a filepath: /dev/zero
/dev/zero is a character device file
[[email protected] bin]#./filetype.sh
Please input a filepath: /bin
/bin is a symbolic link
4、编写脚本/root/bin/checkint.sh,判断用户输入的参数是否为正整数
#!/bin/bash
#FileName: checkint.sh
read -p "Please input a positive integer: " INT
if [[ $INT =~ ^[0-9]+$ ]];then
echo "\"$INT\" is a positive integer"
else
echo "\"$INT\" is not a positive integer"
fi
执行结果如下:
[[email protected] bin]#./checkint.sh
Please input a positive integer: 23
"23" is a positive integer
[[email protected] bin]#./checkint.sh
Please input a positive integer: -16
"-16" is not a positive integer
[[email protected] bin]#./checkint.sh
Please input a positive integer: abc
"abc" is not a positive integer
循环
循环执行
将某代码段重复运行多次
重复运行多少次
循环次数事先已知
循环次数事先未知
有进入条件和退出条件
for, while, until
for循环
for 变量名 in 列表;do
循环体
done
执行机制:
依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
列表生成方式:
(1) 直接给出列表
(2) 整数列表:
(a) {start..end}
(b) $(seq [start [step]] end)
(3) 返回列表的命令
$(COMMAND)
(4) 使用glob,如:*.sh
(5) 变量引用;
例:for file in `ls /boot`; do echo The filename is $file; done
练习:用for实现
1、判断/var/目录下所有文件的类型
#!/bin/bash
#FileName: filetype_var.sh
for path in `ls -d /var/*`;do
type=`ls -ld $path | head -c1`
case $type in
-)
echo "$path is a regular file"
;;
d)
echo "$path is a directory"
;;
b)
echo "$path is a block device file"
;;
c)
echo "$path is a character device file"
;;
l)
echo "$path is a symbolic link"
;;
p)
echo "$path is a named pipe"
;;
s)
echo "$path is a local socket file"
;;
*)
echo "error"
;;
esac
done
执行结果如下:
[[email protected] bin]#./filetype_var.sh
./filetype_var.sh: line 12: [: too many arguments
/var/account is a directory
/var/adm is a directory
/var/cache is a directory
/var/crash is a directory
/var/db is a directory
/var/empty is a directory
/var/games is a directory
/var/gopher is a directory
/var/kerberos is a directory
/var/lib is a directory
/var/local is a directory
/var/lock is a symbolic link
/var/log is a directory
/var/mail is a symbolic link
/var/nis is a directory
/var/opt is a directory
/var/preserve is a directory
/var/run is a symbolic link
/var/spool is a directory
/var/target is a directory
/var/tmp is a directory
/var/www is a directory
/var/yp is a directory
2、添加10个用户user1-user10,密码为8位随机字符
#!/bin/bash
#FileName: createusers.sh
for num in {1..10};do
useradd user$num &> /dev/null
if [ $? = 0 ];then
echo user$num created successfully
echo `openssl rand -base64 6` | passwd --stdin user$num &> /dev/null
else
echo user$num existed
fi
done
执行结果如下:
[[email protected] bin]#./createusers.sh
user1 created successfully
user2 created successfully
user3 created successfully
user4 created successfully
user5 created successfully
user6 created successfully
user7 created successfully
user8 created successfully
user9 created successfully
user10 created successfully
3、/etc/rc.d/rc3.d目录下分别有多个以K开头和以S开头的文件;分别读取每个文件,以K开头的输出为文件加stop,以S开头的输出为文件名加start,如K34filename stop S66filename start
#!/bin/bash
#FileName: rc3.d_KS.sh
for i in `ls /etc/rc.d/rc3.d`;do
if [[ $i =~ ^K.*$ ]];then
echo $i stop;
elif [[ $i =~ ^S.*$ ]];then
echo $i start;
else
:
fi
done
[[email protected] bin]#./rc3.d_KS.sh
K50netconsole stop
S10network start
4、编写脚本,提示输入正整数n的值,计算1+2+…+n的总和
#!/bin/bash
#FileName: sum_n.sh
while true; do
read -p "Please input a positive integer: " n
[[ $n =~ ^[1-9][0-9]*$ ]] && break
done
sum=0
for i in `seq 1 $n`;do
sum=$[sum+i]
done
echo "n=$n"
echo "1+2+...+$n=$sum"
执行结果如下:
[[email protected] bin]#./sum_n.sh
Please input a positive integer: 100
n=100
1+2+...+100=5050
5、计算100以内所有能被3整除的整数之和
#!/bin/bash
#FileName: sum_%3=0.sh
sum=0
for i in {0..100};do
if [ $[i%3] -eq 0 ];then
sum=$[sum+i]
else
:
fi
done
echo sum=$sum
执行结果如下:
[[email protected] bin]#./sum_%3\=0.sh
sum=1683
6、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态
#!/bin/bash
#FileName: scanip.sh
# 提示输入网络地址,用正则表达式判断网段合法性
while true; do
read -p "Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: " address
if [[ $address =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]];then
echo -e "ip is legal, start scanning...\n"
break
else
echo -e "wrong ip address\n"
fi
done
# 开始扫描
net=`echo $address | sed -r 's/^(.*)\..*$/\1/'`
for i in {1..254};do {
if ping -c1 -w1 $net.$i &> /dev/null ;then
echo "$net.$i is up"
else
echo "$net.$i is down"
fi } &
done
wait
执行结果如下:
[[email protected] bin]#./scanip.sh
Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: 192.168.0.0
ip is legal, start scanning...
192.168.0.1 is up
192.168.0.103 is up
192.168.0.104 is up
192.168.0.113 is up
192.168.0.114 is up
192.168.0.5 is down
192.168.0.2 is down
……
……
192.168.0.177 is down
192.168.0.245 is down
192.168.0.232 is down
192.168.0.228 is down
7、打印九九乘法表
#!/bin/bash
#Filename: 9×9.sh
for i in {1..9};do
for j in `seq 1 $i`;do
result=$[j*i]
echo -e "${j}×${i}=$result\t\c"
done
echo
done
执行结果如下:
8、在/testdir目录下创建10个html文件,文件名格式为数字N(从1到10)加随机8个字母,如:1AbCdeFgH.html
#!/bin/bash
#Filename: touch_Nrand.html.sh
mkdir /testdir/ &> /dev/null
rand=0
for i in {1..10};do
rand=`openssl rand -base64 20 | tr -dc [[:alpha:]] | head -c8`
touch "/testdir/$i$rand.html"
done
执行结果如下:
[[email protected] bin]#./touch_Nrand.html.sh
[[email protected] bin]#cd /testdir
[[email protected] testdir]#ls
10InrYzLKS.html 3rlecXfCR.html 6MjgPCXtX.html 9qQGRIxir.html
1maGPaaSn.html 4LZssBMjM.html 7ImLLSRih.html
2vjaOmtIO.html 5goBpJYRs.html 8OXhFdlTZ.html
9、打印等腰三角形
#!/bin/bash
#Filename: triangle.sh
while true;do
read -p "Please input the high of the triangle: " high
[[ $high =~ ^[0-9]+$ ]] && break
done
for i in `seq $high`;do
for j in `seq $[high-i]`;do echo -e " \c";done
for k in `seq $[2*i-1]`;do echo -e "*\c";done
echo
done
执行结果如下:
while循环
while CONDITION; do
循环体
done
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环
因此:CONDTION一般应该有循环控制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为true
退出条件:CONDITION为false
练习:用while实现
1、编写脚本,求100以内所有正奇数之和
#!/bin/bash
#FileName: while_sum.sh
i=0
sum=0
while [ $i -le 100 ] ;do
if [ $[i%2] -eq 1 ];then
sum=$[sum+i]
fi
let i++
done
echo sum=$sum
执行结果如下:
[[email protected] bin]#./while_sum.sh
sum=2500
2、编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少
#!/bin/bash
#FileName: scanup.sh
# 提示输入网络地址,用正则表达式判断网段合法性
while true; do
read -p "Please input an ip net, such as 192.168.0.0, the script will scan
ip net 192.168.0.0/24: " address
if [[ $address =~ ^(([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.){3}([1-
9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]];then
echo -e "ip is legal, start scanning...\n"
break
else
echo -e "wrong ip address\n"
fi
done
# 开始扫描
net=`echo $address | sed -r 's/^(.*)\..*$/\1/'`
i=1
touch ./scanup_tmp
while [ $i -le 254 ];do {
if ping -c1 -w1 $net.$i &> /dev/null ;then
echo "$net.$i is up" | tee -a ./scanup_tmp
else
echo "$net.$i is down" | tee -a ./scanup_tmp
fi } &
let i++
done
wait
up=`grep up ./scanup_tmp | wc -l`
down=`grep down ./scanup_tmp | wc -l`
rm -f ./scanup_tmp &> /dev/null
echo
echo up=$up
echo down=$down
# 扫描结束
执行结果如下:
[[email protected] bin]#./scanup.sh
Please input an ip net, such as 192.168.0.0, the script will scan ip net 192.168.0.0/24: 192.168.0.0
ip is legal, start scanning...
192.168.0.1 is up
192.168.0.104 is up
192.168.0.103 is up
192.168.0.5 is down
……
192.168.0.243 is down
192.168.0.251 is down
192.168.0.247 is down
up=6
down=248
3、编写脚本,打印九九乘法表
#!/bin/bash
#FileName: 9×9_while.sh
declare -i i=1
while [ $i -le 9 ];do
declare -i j=1
while [ $j -le $i ];do
result=$[j*i]
echo -e "${j}×${i}=$result\t\c"
let j++
done
echo
let i++
done
执行结果如下:
4、编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值
#!/bin/bash
#FileName: 10_random_num.sh
declare -i rand min max i=1
echo "10 random numbers: "
while [ $i -le 10 ]; do
rand=$RANDOM
echo $i $rand
[ $i -eq 1 ] && { min=$rand; max=$rand; }
[ "$rand" -lt "$min" ] && min=$rand
[ "$rand" -gt "$max" ] && max=$rand
let i++
done
echo min $min
echo max $max
执行结果如下:
[[email protected] bin]#./10_random_num.sh
10 random numbers:
1 1309
2 23514
3 21965
4 6954
5 25446
6 19996
7 16356
8 4571
9 32208
10 8522
min 1309
max 32208
5、编写脚本,实现打印国际象棋棋盘
#!/bin/bash
#FileName: chess.sh
# 给国际象棋棋盘选2种颜色
# 打印8种颜色
echo "Please select 2 colour for the chessboard: "
for i in {0..7};do
echo -e "$[i+1]\033[1;4${i}m \033[0m \c"
done
echo -e "\n"
# 选择颜色1
while true;do
read -p "Select the first colour (1-8): " C1
[[ "$C1" =~ ^[1-8]$ ]] && break
done
echo -e "The first colour is $C1\033[1;4$[C1-1]m \033[0m\n"
# 选择颜色2
while true;do
read -p "Select the second colour (1-8): " C2
[[ "$C2" =~ ^[1-8]$ ]] && break
done
echo -e "The second colour is $C2\033[1;4$[C2-1]m \033[0m\n"
# 选择棋盘大小
while true;do
read -p "Now select the size of the chessboard (1-9): " S
[[ "$S" =~ ^[1-9]$ ]] && break
done
# 打印棋盘
echo "Print the chessboard: "
echo
for i in `eval echo {0..$[8*S-1]}`;do
for j in `eval echo {0..$[8*S-1]}`;do
if [ $[(i/S+j/S)%2] -eq 0 ];then
echo -e "\033[1;4$[C1-1]m \033[0m\c"
else
echo -e "\033[1;4$[C2-1]m \033[0m\c"
fi
done
echo
done
# 打印结束
执行结果如下:
6、后续六个字符串:efbaf275cd、4be9c40b8b、44b2395c46、f8c8873ce0、b902c16c8b、ad865d2f63是通过对随机数变量RANDOM随机执行命令: echo $RANDOM|md5sum|cut -c1-10 后的结果,请破解这些字符串对应的RANDOM值
#!/bin/bash
#FileName: rand_md5sum.sh
declare -i i=0
declare md5
while [ $i -le 32767 ]; do {
md5=`echo $i | md5sum | cut -c1-10`
case $md5 in
efbaf275cd)
echo "efbaf275cd ---> $i" | tee -a ./md5sum.txt
;;
4be9c40b8b)
echo "4be9c40b8b ---> $i" | tee -a ./md5sum.txt
;;
44b2395c46)
echo "44b2395c46 ---> $i" | tee -a ./md5sum.txt
;;
f8c8873ce0)
echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt
;;
b902c16c8b)
echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt
;;
ad865d2f63)
echo "f8c8873ce0 ---> $i" | tee -a ./md5sum.txt
;;
*)
;;
esac } &
let i++
done
wait
cat ./md5sum.tx
脚本结束,输出结果如下:
f8c8873ce0 ---> 1000
f8c8873ce0 ---> 3000
f8c8873ce0 ---> 6000
44b2395c46 ---> 9000
4be9c40b8b ---> 12000
efbaf275cd ---> 15000
until循环
until CONDITION; do
循环体
done
进入条件: CONDITION 为false
退出条件: CONDITION 为true
循环控制语句continue
用于循环体中
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done
循环控制语句break
用于循环体中
break [N]:提前结束第N层循环,最内层为第1层
while CONDTIITON1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done
循环控制shift命令
shift [n]
用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
./doit.sh a b c d e f g h
./shfit.sh a b c d e f g h
示例:doit.sh
#!/bin/bash
# Name: doit.sh
# Purpose: shift through command line arguments
# Usage: doit.sh [args]
while [ $# -gt 0 ] # or (( $# > 0 ))
do
echo $*
shift
done
示例:shift.sh
#!/bin/bash
#step through all the positional parameters
until [ -z "$1" ]
do
echo "$1"
shift
done
echo
创建无限循环
while true; do
循环体
done
until false; do
循环体
Done
练习
1、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本
#!/bin/bash
#FileName: hacker_login.sh
until false; do
if who | grep "^hacker\ " &> /dev/null; then
who | grep "^hacker\ " >> /var/log/login.log
break
fi
sleep 3
done
如果想让脚本在后台运行,可输入nohup ./hacker_login.sh &> /dev/null &
hacker用户登录后脚本退出,执行结果如下:
[[email protected] bin]#cat /var/log/login.log
hacker pts/1 2018-05-10 16:20 (192.168.30.1)
2、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出
#!/bin/bash
#FileName: guess.sh
num=$[RANDOM%11]
while true;do
read -p "Please guess the number between 0 and 10: " guess
if [[ ! $guess =~ [[:digit:]]+ ]];then
:
elif [ $guess == $num ];then
echo "You are right! The number is ${num}!"
break
elif [ $guess -gt $num ];then
echo "too large"
elif [ $guess -lt $num ];then
echo "too small"
else
:
fi
done
执行结果如下:
[[email protected] bin]#./guess.sh
Please guess the number between 0 and 10: aaa
Please guess the number between 0 and 10: 5
too large
Please guess the number between 0 and 10: 3
too large
Please guess the number between 0 and 10: 1
You are right! The number is 1!
3、用文件名做为参数,统计所有参数文件的总行数
#!/bin/bash
#FileName: sum_line.sh
[ $# -eq 0 ] && echo "No args" && exit
declare sum=0
until [ -z $1 ]; do
if [ -e $1 ]; then
sum=$[sum+`cat $1 | wc -l`]
else
echo "file '$1' is not existed"; exit
fi
shift
done
echo "There are all $sum cows."
执行结果如下:
[[email protected] bin]#./sum_line.sh /etc/fstab /etc/passwd
There are all 70 cows.
4、用二个以上的数字为参数,显示其中的最大值和最小值
#!/bin/bash
#FileName: max_and_min.sh
[ $# -lt 2 ] && echo "at least 2 args" && exit
min=$1
max=$1
until [ -z $1 ]; do
[[ ! $1 =~ ^-?[0-9]+$ ]] && echo $1 is not a number && exit
[ $1 -lt $min ] && min=$1
[ $1 -gt $max ] && max=$1
shift
done
echo min number is $min
echo max number is $max
特殊用法
while循环的特殊用法(遍历文件的每一行):
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
练习
扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填充用户名和单位电话为62985600,并提示该用户的GECOS信息修改成功
双小括号方法,即((…))格式,也可以用于算术运算
双小括号方法也可以使bash Shell实现C语言风格的变量操作
I=10
((I++))
for循环的特殊格式:
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
例:
for (( i=1,sum=0;i<=100;i++ ));do
let sum+=i
done
echo sum=$sum
select循环与菜单
select variable in list
do
循环体命令
done
select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入被保存在内置变量 REPLY 中
select与case
select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list,此时使用位置参量
例:
#!/bin/bash
#FileName: select_menu.sh
PS3="Please choose the menu (1-4):"
select menu in baoyu yanwo renshen jitang;do
case $menu in
baoyu)
echo $REPLY:$menu price is 1000
break
;;
yanwo)
echo $REPLY:$menu price is 2000
break
;;
renshen)
echo $REPLY:$menu price is 3000
break
;;
jitang)
echo $REPLY:$menu price is free
break
;;
*)
echo $REPLY:$menu is empty
;;
esac
done
信号捕捉trap
trap '触发指令' 信号
自定义进程收到系统发出的指定信号后,将执行触发指令,而不会执行原操作
trap '' 信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap示例
#!/bin/bash
trap 'echo “signal:SIGINT"' int
trap -p
for((i=0;i<=10;i++))
do
sleep 1
echo $i
done
trap '' int
trap -p
for((i=11;i<=20;i++))
do
sleep 1
echo $i
done
trap '-' int
trap -p
for((i=21;i<=30;i++))
do
sleep 1
echo $i
done
函数介绍
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于:
Shell程序在子Shell中运行
而Shell函数在当前Shell中运行。因此在当前Shell中,函数可以对shell中变量进行修改
定义函数
函数由两部分组成:函数名和函数体
help function
语法一:
f_name (){
...函数体...
}
语法二:
function f_name {
...函数体...
}
语法三:
function f_name () {
...函数体...
}
函数的优先级比别名、外部命令和内部命令都高
一般生产中函数名可定义为func_name ()
local var可使变量var只在本函数中生效
declare -i var定义的var默认自带local属性,除非加个选项declare -g,但-g选项在CentOS 6上不兼容
函数使用
函数的定义和使用:
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行
调用:给定函数名
函数名出现的地方,会被自动替换为函数代码
函数的生命周期:被调用时创建,返回时终止
函数返回值
函数有两种返回值:
函数的执行结果返回值:
(1) 使用echo等命令进行输出
(2) 函数体中调用命令的输出结果
函数的退出状态码:
(1) 默认取决于函数中执行的最后一条命令的退出状态码
(2) 自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
return 0 无错误返回。
return 1-255 有错误返回
交互式环境下定义和使用函数
示例:
dir() {
> ls -l
> }
定义该函数后,若在$后面键入dir,其显示结果同ls -l的作用相同
dir
该dir函数将一直保留到用户从系统退出,或执行了如下所示的unset命令
unset dir
在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用
调用函数仅使用其函数名即可
示例:
cat func1
#!/bin/bash
# func1
hello()
{
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello"
hello
echo “back from the function”
使用函数文件
可以将经常使用的函数存入函数文件,然后将函数文件载入shell
文件名可任意选取,但最好与相关任务有某种联系。例如:functions.main
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用set命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
创建函数文件
函数文件示例:
cat functions.main
#!/bin/bash
#functions.main
findit()
{
if [ $# -lt 1 ] ; then
echo "Usage:findit file"
return 1
fi
find / -name $1 -print
}
载入函数
函数文件已创建好后,要将它载入shell
定位函数文件并载入shell的格式
. filename 或 source filename
注意:此即<点> <空格> <文件名>
这里的文件名要带正确路径
示例:
上例中的函数,可使用如下命令:
. functions.main
检查载入函数
使用set命令检查函数是否已载入。set命令将在shell中显示所有的载入函数
示例:
set
findit=( )
{
if [ $# -lt 1 ]; then
echo "usage :findit file";
return 1
fi
find / -name $1 -print
}
…
执行shell函数
要执行函数,简单地键入函数名即可
示例:
findit groups
/usr/bin/groups
/usr/local/backups/groups.bak
删除shell函数
现在对函数做一些改动后,需要先删除函数,使其对shell不可用。使用unset命令完成删除函数
命令格式为:
unset function_name
示例:
unset findit
再键入set命令,函数将不再显示
环境函数
使子进程也可使用
声明:export -f function_name
查看:export -f 或 declare -xf
函数参数
函数可以接受参数:
传递参数给函数:调用函数时,在函数名后面以空白分隔给定参数列表即可;例如“testfunc arg1 arg2 ...”
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用[email protected], $*, $#等特殊变量
函数变量
变量作用域:
环境变量:当前shell和子shell有效
局部变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,局部变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
本地变量:函数的生命周期;函数结束时变量被自动销毁
注意:如果函数中有本地变量,如果其名称同局部变量,使用本地变量
在函数中定义局部变量的方法
local NAME=VALUE
函数递归示例
函数递归:
函数直接或间接调用自身
注意递归层数
递归实例:
阶乘是基斯顿·卡曼于 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
#
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ]; then
echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1
fork×××
fork×××是一种恶意程序,它的内部是一个不断在fork进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
函数实现
:(){ :|:& };:
bomb() { bomb | bomb & }; bomb
脚本实现
cat Bomb.sh
#!/bin/bash
./$0|./$0&
练习
编写函数,实现OS的版本判断
release () {
ver=`sed -r 's/^.* ([0-9]+)\..*/\1/' /etc/redhat-release`
echo $ver
}
编写函数,实现取出当前系统eth0的IP地址
eth0_IP () {
IP=`ifconfig eth0 | sed -nr 's/^.*inet addr:([^ ]+) .*$/\1/p'`
echo $IP
}
编写函数,实现打印绿色OK和红色FAILED
OK_FAILED () {
if [[ $1 =~ OK ]];then
echo -e "[ \033[32mOK\033[0m ]"
elif [[ $1 =~ FAILED ]];then
echo -e "[\033[31mFAILED\033[0m]"
else
:
fi
}
# OK_FAILED OK打印绿色OK,OK_FAILED FAILED打印红色FAILED
编写函数,实现判断是否无位置参数,如无参数,提示错误
args () {
if [ -z $1 ]; then
echo "No args"
fi
}
args $1
练习
编写服务脚本/root/bin/testsrv.sh,完成如下要求
(1) 脚本可接受参数:start, stop, restart, status
(2) 如果参数非此四者之一,提示使用格式后报错退出
(3) 如是start:则创建/var/lock/subsys/SCRIPT_NAME, 并显示“启动成功”
考虑:如果事先已经启动过一次,该如何处理?
(4) 如是stop:则删除/var/lock/subsys/SCRIPT_NAME, 并显示“停止完成”
考虑:如果事先已然停止过了,该如何处理?
(5) 如是restart,则先stop, 再start
考虑:如果本来没有start,如何处理?
(6) 如是status, 则如果/var/lock/subsys/SCRIPT_NAME文件存在,则显示“SCRIPT_NAME is running...”
如果/var/lock/subsys/SCRIPT_NAME文件不存在,则显示“SCRIPT_NAME is stopped...”
其中:SCRIPT_NAME为当前脚本名
(7)在所有模式下禁止启动该服务,可用chkconfig 和 service命令管理
注:
/etc/(rc.d/)init.d/functions中有一个action函数,机器启动时各服务启动OK或者FAILED就是用这个函数打印的,可用于testsrv脚本的编写
例:
[[email protected] ~]#action "rm -rf /*"
rm -rf /* [ OK ]
[[email protected] ~]#action "rm -rf /*" /bin/false
rm -rf /* [FAILED]
答:
# 在/etc/rc.d/init.d/下创建如下脚本,然后就可以用service testsrv start|stop|status|restart命令来管理了。输命令chkconfig add testsrv,即可用chkconfig来管理了,chkconfig --list testsrv查看testsrv状态,chkconfig testsrv off即可禁止在所有模式下启动testsrv
#!bin/bash
#chkconfig: 2345 96 3
#discription: test service
#filename: testsrv
source "/etc/init.d/functions"
case $1 in
start)
[ -f /var/lock/subsys/testsrv ] && echo "testsrv is already started:" && exit
touch /var/lock/subsys/testsrv
action "Starting testsrv:"
;;
stop)
[ ! -f /var/lock/subsys/testsrv ] && echo "testsrv is already stopped:" && exit
rm -f /var/lock/subsys/testsrv
action "Stopping testsrv:"
;;
status)
[ -f /var/lock/subsys/testsrv ] && echo "testsrv is running..." || echo "testsrv is stopped"
;;
restart)
[ -f /var/lock/subsys/testsrv ] && action "Stopping testsrv:" || action "Stopping testsrv:" /bin/false
rm -f /var/lock/subsys/testsrv
touch /var/lock/subsys/testsrv
action "Starting testsrv:"
;;
*)
echo "usage: service testsrv start|stop|restart|status"
;;
esac
执行结果如下:
练习
编写脚本/root/bin/copycmd.sh
(1) 提示用户输入一个可执行命令名称
(2) 获取此命令所依赖到的所有库文件列表
(3) 复制命令至某目标目录(例如/mnt/sysroot)下的对应路径下
如:/bin/bash ==> /mnt/sysroot/bin/bash
/usr/bin/passwd ==> /mnt/sysroot/usr/bin/passwd
(4) 复制此命令依赖到的所有库文件至目标目录下的对应路径下: 如:/lib64/ld-linux-x86-64.so.2 ==> /mnt/sysroot/lib64/ld-linux-x86-64.so.2
(5)每次复制完成一个命令后,不要退出,而是提示用户键入新的要复制的命令,并重复完成上述功能;直到用户输入quit退出
#!/bin/bash
#FileName: copycmd.sh
input () {
while true; do
read -p "Please input a command or quit: " cmd
[[ $cmd =~ ^quit$ ]] && exit
which $cmd &> /dev/null && break
echo "command $cmd not found"
done
}
while true; do
input
path=`which --skip-alias $cmd`
des=/mnt/sysroot
tmp=`mktemp ./tmpXXX`
ldd $path | sed -rn '[email protected][^\/]*(\/[^ ]+) .*@\[email protected]' &> $tmp
mkdir -p "$des${path%/*}"
cp $path "$des${path%/*}"
while read line; do
mkdir -p "$des${line%/*}" &> /dev/null
cp $line "$des${line%/*}" &> /dev/null
done < ./$tmp
echo "copy command $cmd successfully!"
rm -f ./$tmp
done
练习
1、编写函数实现两个数字做为参数,返回最大值
# 定义并载入函数后,输F_max $1 $2即可返回$1和$2的最大值
F_max () {
if [ $# -ne 2 ]; then
echo "Needs two args" && return 1
elif [[ ! $1 =~ ^-?[0-9]+$ ]]; then
echo "$1 is not an integer" && return 2
elif [[ ! $2 =~ ^-?[0-9]+$ ]]; then
echo "$2 is not an integer" && return 2
elif [ $1 -gt $2 ]; then
echo max number is $1
else
echo max number is $2
fi
}
执行结果如下:
[[email protected] bin]#. F_max.sh
[[email protected] bin]#F_max -6 -15
max number is -6
2、斐波那契数列又称黄金分割数列,因数学家列昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:0、1、1、2、3、5、8、13、21、34、……,斐波纳契数列以如下被以递归的方法定义:F(0)=0,F(1)=1,F(n)=F(n-1)+F(n-2)(n≥2)
利用函数,求n阶斐波那契数列
# 斐波那契数列用函数递归调用构建的话,时间复杂度会达到2^n次方,在此不用递归,改用迭代
#!/bin/bash
#FileName: Fibonacci.sh
while true; do
read -p "Please input a positive integer: " n
[[ $n =~ ^[0-9]+$ ]] && break
done
echo 'F(0),F(1),...,F(n-1),F(n)= '
declare -a Fi
if [ $n -eq 0 ];then
Fi=(0)
elif [ $n -eq 1 ];then
Fi=(0 1)
else
Fi=(0 1)
for i in `seq 2 $n`; do
Fi[$i]=$[(Fi[$i-1])+(Fi[$i-2])]
done
fi
echo ${Fi[*]}
执行结果如下:
[[email protected] bin]#./Fibonacci.sh
Please input a positive integer: 20
F(0),F(1),...,F(n-1),F(n)=
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
3、汉诺塔(又称河内塔)问题是源于印度一个古老传说。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤
#!/bin/bash
#FileName: hanoi.sh
while true; do
read -p "Please input the number of the plates: " n
[[ $n =~ ^[1-9][0-9]?$ ]];break
done
sum=0
move() {
let sum++
echo "$sum: move disk $1 from $2 ---> $3"
}
hanoi() {
[ $1 -eq 1 ] && { move $1 $2 $4;return; }
hanoi $[$1-1] $2 $4 $3
move $1 $2 $4
hanoi $[$1-1] $3 $2 $4
}
hanoi $n A B C
执行结果如下:
[[email protected] bin]#./hanoi.sh
Please input the number of the plates: 4
1: move disk 1 from A ---> B
2: move disk 2 from A ---> C
3: move disk 1 from B ---> C
4: move disk 3 from A ---> B
5: move disk 1 from C ---> A
6: move disk 2 from C ---> B
7: move disk 1 from A ---> B
8: move disk 4 from A ---> C
9: move disk 1 from B ---> C
10: move disk 2 from B ---> A
11: move disk 1 from C ---> A
12: move disk 3 from B ---> C
13: move disk 1 from A ---> B
14: move disk 2 from A ---> C
15: move disk 1 from B ---> C
4、打印杨辉三角
# 杨辉三角用函数递归调用构建的话,时间复杂度会达到2^n次方,在此不用递归,改用迭代
#!/bin/bash
#FileName: yanghui.sh
while true; do
read -p "Please input the height of the triangle: " n
[[ $n =~ ^[1-9][0-9]?$ ]];break
done
n=$[n-1]
declare -A C
C=([00]=1 [10]=1 [11]=1)
if [ $n -ge 2 ];then
for i in `seq 2 $n`; do
C[${i}0]=1
for j in `seq 1 $[i-1]`; do
C[$i$j]=$[(C[$[i-1]$[j-1]])+(C[$[i-1]$j])]
done
C[$i$i]=1
done
fi
for i in `seq 0 $n`;do
for k in `seq 0 $[n-i]`;do echo -e " \c";done
for j in `seq 0 $i`;do
if [ $j -eq $i ];then
echo ${C[$i$j]}
else
echo -e "${C[$i$j]} \c"
fi
done
done
执行结果如下:
数组
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引:编号从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]="VAL1" [3]="VAL2" ...)
(4) 交互式数组值对赋值
read -a ARRAY
显示所有数组:declare -a
例:
只要能生成列表,就能对数组赋值
filename=( /boot/* )
filename=(`ls /boot/`)
filename=( f{1,2,3}.{txt,log} )
例:
[[email protected] bin]#read -a title
boss ceo cto
[[email protected] bin]#echo ${title[*]}
boss ceo cto
关联数组由于下标可以任意指定,因此可以实现一些巧妙的运用
引用数组
引用数组元素:
${ARRAY_NAME[INDEX]}
注意:省略[INDEX]表示引用下标为0的元素
引用数组所有元素:
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
数组的长度(数组中元素的个数):
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
删除数组中的某元素:导致稀疏格式
unset ARRAY[INDEX]
删除整个数组:
unset ARRAY
数组数据处理
引用数组中的元素:
数组切片:${ARRAY[@]:offset:number}
offset: 要跳过的元素个数
number: 要取出的元素个数
取偏移量之后的所有元素 ${ARRAY[@]:offset}
向数组中追加元素:
ARRAY[${#ARRAY[*]}]=value
关联数组:
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用
示例
生成10个随机数保存于数组中,并找出其最大值和最小值
#!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do
nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[$i]} && max=${nums[$i]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]}
done
echo “All numbers are ${nums[*]}”
echo Max is $max
echo Min is $min
示例
编写脚本,定义一个数组,数组中的元素是/var/log目录下所有以.log结尾的文件;统计出其下标为偶数的文件中的行数之和
#!/bin/bash
#
declare -a files
files=(/var/log/*.log)
declare -i lines=0
for i in $(seq 0 $[${#files[*]}-1]); do
if [ $[$i%2] -eq 0 ];then
let lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)
fi
done
echo "Lines: $lines."
练习
1、输入若干个数值存入数组中,采用冒泡算法进行升序或降序排序
#!/bin/bash
#FileName: bubble_sort.sh
while true; do
read -p "Please input the length for the array: " n
[[ $n =~ ^[1-9][0-9]?+$ ]] && break
done
echo "create a random array: "
declare -a arr
for i in `seq 1 $n`;do
arr[$i]=$RANDOM
echo $i: ${arr[$i]}
done
while true; do
read -p "Please input a or d to ascending sort or descending sort: " opt
[ $opt == a -o $opt == d ] && break
done
m=$n
while [ $m -ge 2 ]; do
for j in `seq 2 $m`; do
if [ ${arr[$j]} -lt ${arr[$[j-1]]} ];then
tmp=${arr[$j]}
arr[$j]=${arr[$[j-1]]}
arr[$[j-1]]=$tmp
fi
done
let m--
done
if [ $opt == a ];then
echo ascending sort:
for i in `seq 1 $n`; do
echo $i: ${arr[$i]}
done
elif [ $opt == d ];then
echo descending sort:
declare -a d_arr
for i in `seq 1 $n`; do
d_arr[$i]=${arr[$[n-i+1]]}
echo $i: ${d_arr[$i]}
done
else
:
fi
输出结果如下:
[[email protected] bin]#./bubble_sort.sh
Please input the length for the array: 8
create a random array:
1: 13912
2: 31881
3: 4133
4: 3167
5: 12977
6: 6632
7: 2506
8: 16781
Please input a or d to ascending sort or descending sort: d
descending sort:
1: 31881
2: 16781
3: 13912
4: 12977
5: 6632
6: 4133
7: 3167
8: 2506
2、将下图所示,实现转置矩阵matrix.sh
1 2 3 1 4 7
4 5 6 ===> 2 5 8
7 8 9 3 6 9
#!/bin/bash
#FileName: matrix.sh
# 提示输入矩阵大小n
declare -A matrix
while true; do
read -p "Please input the size of the matrix: " n
[[ $n =~ ^[1-9][0-9]?+$ ]] && break
done
n=$[n-1]
# 生成并打印矩阵
for i in `eval echo {0..$n}`; do
for j in `eval echo {0..$n}`; do
matrix[$i$j]=$[i*(n+1)+j+1]
echo -e "${matrix[$i$j]} \c"
done
echo
done
# 开始转置矩阵
echo -e "\nswitch the matrix: \n"
declare tmp
for i in `eval echo {1..$n}`; do
for j in `eval echo {0..$[i-1]}`; do
tmp=${matrix[$i$j]}
matrix[$i$j]=${matrix[$j$i]}
matrix[$j$i]=$tmp
done
done
for i in `eval echo {0..$n}`; do
for j in `eval echo {0..$n}`; do
echo -e "${matrix[$i$j]} \c"
done
echo
done
输出结果如下:
[[email protected] bin]#./matrix.sh
Please input the size of the matrix: 3
1 2 3
4 5 6
7 8 9
switch the matrix:
1 4 7
2 5 8
3 6 9
字符串切片
${#var}:返回字符串变量var的长度
${var:offset}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset:number}:返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var: -length}:取字符串的最右侧几个字符
注意:冒号后必须有一空白字符
${var:offset:-length}:从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容
${var: -length:-offset}:先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容
注意:-length前空格
注:这段代码有版本要求,7上有效,6上无效
字符串处理
基于模式取子串
${var#*word}:其中word可以是指定的任意字符
功能:自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符之间的所有字符
${var##*word}:同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
示例:
file=“var/log/messages”
${file#*/}: log/messages
${file##*/}: messages
${var%word*}:其中word可以是指定的任意字符
功能:自右而左,查找var变量所存储的字符串中,第一次出现的word, 删除字符串最后一个字符向左至第一次出现word字符之间的所有字符
file="/var/log/messages"
${file%/*}: /var/log
${var%%word*}:同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符;
示例:
url=http://www.51cto.com:80
${url##*:} 80
${url%%:*} http
查找替换
${var/pattern/substr}:查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}: 查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}:查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}:查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
查找并删除
${var/pattern}:删除var所表示的字符串中第一次被pattern所匹配到的字符串
${var//pattern}:删除var所表示的字符串中所有被pattern所匹配到的字符串
${var/#pattern}:删除var所表示的字符串中所有以pattern为行首所匹配到的字符串
${var/%pattern}:删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
字符大小写转换
${var^^}:把var中的所有小写字母转换为大写
${var,,}:把var中的所有大写字母转换为小写
变量赋值
高级变量用法-有类型变量
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母 declare -l var=UPPER
-u 声明变量为大写字母 declare -u var=lower
eval命令
eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量.该命令对变量进行两次扫描
示例:
[[email protected] ~]# CMD=whoami
[[email protected] ~]# echo $CMD
whoami
[[email protected] ~]# eval $CMD
root
[[email protected] ~]# n=10
[[email protected] ~]# echo {0..$n}
{0..10}
[[email protected] ~]# eval echo {0..$n}
0 1 2 3 4 5 6 7 8 9 10
间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为
variable1=variable2
variable2=value
bash Shell提供了两种格式实现间接变量引用
eval tempvar=\$$variable1
tempvar=${!variable1}
示例:
[[email protected] ~]# N=NAME
[[email protected] ~]# NAME=q
[[email protected] ~]# N1=${!N}
[[email protected] ~]# echo $N1
q
[[email protected] ~]# eval N2=\$$N
[[email protected] ~]# echo $N2
q
创建临时文件
mktemp命令:创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filenameXXX
X至少要出现三个
OPTION:
-d: 创建临时目录
-p DIR或--tmpdir=DIR:指明临时文件所存放目录位置
示例:
mktemp /tmp/testXXX
tmpdir=`mktemp -d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX
安装复制文件
install命令:
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录
选项:
-m MODE,默认755
-o OWNER
-g GROUP
示例:
install -m 700 -o wang -g admins srcfile desfile
install -m 770 -d /testdir/installdir
expect介绍
expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助Expect处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
expect命令
expect 语法:
expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
选项
-c:从命令行执行expect脚本,默认expect是交互地执行的
示例:expect -c 'expect "\n" {send "pressed enter\n"}'
-d:可以输出输出调试信息
示例:expect -d ssh.exp
expect中相关命令
spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接收字符串
interact:允许用户交互
exp_continue 匹配多个字符串在执行动作后加此命令
expect
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"}
}
示例
#!/usr/bin/expect
spawn scp /etc/fstab 192.168.8.100:/app
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send “111111\n" }
}
expect eof
示例
#!/usr/bin/expect
spawn ssh 192.168.8.100
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send “111111\n" }
}
interact
#expect eof
示例:变量
#!/usr/bin/expect
set ip 192.168.8.100
set user root
set password 111111
set timeout 10
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
示例:位置参数
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
interact
#./ssh3.exp 192.168.8.100 root 111111
示例:执行多个命令
#!/usr/bin/expect
set ip [lindex $argv 0]
set user [lindex $argv 1]
set password [lindex $argv 2]
set timeout 10
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 111111 |passwd --stdin haha\n" }
send "exit\n"
expect eof
#./ssh4.exp 192.168.8.100 root 111111
示例:shell脚本调用expect
#!/bin/bash
ip=$1
user=$2
password=$3
expect <<EOF
set timeout 10
spawn ssh [email protected]$ip
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd hehe\n" }
expect "]#" { send "echo 111111 |passwd --stdin hehe\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
#./ssh5.sh 192.168.8.100 root 111111
实验:批量自动创建用户
#!/bin/bash
net=192.168.0
user=root
password=111111
for i in {1..100}; do {
expect <<-EOF
spawn ssh [email protected]$net.$i
expect {
"yes/no" { send "yes\n";exp_continue }
"password" { send "$password\n" }
}
expect "]#" { send "useradd haha\n" }
expect "]#" { send "echo 111111 | passwd --stdin haha\n" }
expect "]#" { send "exit\n" }
expect eof
EOF
} &
done
wait
以上是关于Linux-Shell脚本编程进阶的主要内容,如果未能解决你的问题,请参考以下文章