文章转载自友链LeeYD · Blog的文章Shell 编程入门笔记,已经征得授权,特别感谢作者提供了markdown原文.

一. Shell能做什么

  1. 自动化批量系统初始化程序(Update,软件安装,时区设置,安全策略。。。。)
  2. 自动化批量软件部署程序(LAMP,LNMP/,omcat,LVS,Nginx)
  3. 管理应用程序(KVM,集群管理扩容,MySQL)
  4. 日志分析处理程序(PV,UV,状态统计,grep/awk)
  5. 自动化备份/恢复程序(MySQL完全/增量备份+Crond)
  6. 自动化管理程序(批量远程修改密码,软件升级,配置更新)
  7. 配合Zabbix信息采集
  8. 自动化信息采集及监控程序(收集系统/应用的状态信息:CPU/Mem/Disk/Net/TCP Status/Apache/MySQL)
  9. 自动化扩容(增加云主机,业务上线)
  10. 俄罗斯方块,打印图形,算法排序实现

二. Shell规范

1. 脚本命名

脚本通常以.sh结尾

2.执行方式

  1. sh

    sh test.sh
    
  2. bash

    bash test.sh
    
  3. 以路径方式执行(需要脚本有执行权限)

    chmod +x test.sh
    ./test.sh
    

3. 编写规范

第一行声明程序解释器,写明解释器程序所在路径

#!/usr/bin/bash

三. Bash Shell 基础特性

1. 命令和文件自动补齐

Tab键自动补齐

2. 命令历史记忆功能

  1. 上下键切换历史命令
  2. !number :执行第n条命令
  3. !string:执行最近一条以string开头的命令
  4. !$:上一条命令的最后一个参数
  5. !!:上一条命令
  6. Ctrl+R:搜索历史命令

3. 别名功能

  1. alias:查看当前Shell别名
  2. unalias:取消当前Shell别名

4. 快捷键

  • Ctrl+R:搜索历史命令
  • Ctrl+D:等于logout或exit,退出当前Shell
  • Ctrl+A:光标移动到行首
  • Ctrl+E:光标移动到行尾
  • Ctrl+L:清屏,相当于clear命令
  • Ctrl+U:删除或剪切光标之前的命令。
  • Ctrl+K:删除或剪切光标之后的内容。
  • Ctrl+S:暂停屏幕输出
  • Ctrl+Q:恢复屏幕输出
  • Ctl+C:强制中止当前的命令
  • Ctrl+Z:暂停命令,并放入后台

5. 前后台作业控制

  1. &:后台执行命令,终端关闭后任务结束
  2. nohup:后台执行命令,终端关闭后不终止
  3. screen:后台执行命令
  4. Ctrl+C:结束前台进程
  5. kill:结束指定进程
  6. bg/fg:进程转后台/前台

6. 输入输出重定向

  1. > :输出重定向到一个文件或设备 覆盖原来的文件
  2. \>!:输出重定向到一个文件或设备 强制覆盖原来的文件
  3. >>:输出重定向到一个文件或设备 追加原来的文件
  4. <:输入重定向到一个程序
  5. 2>:将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件 b-shell
  6. 2>>:将一个标准错误输出重定向到一个文件或设备 追加到原来的文件
  7. 2>&1:将一个标准错误输出重定向到标准输出
  8. &>:将一个标准错误输出重定向到一个文件或设备 覆盖原来的文件
  9. |&: 将一个标准错误 管道 输送 到另一个命令作为输入
  10. 标准输入;代码为 0 ;或称为 stdin ;使用的方式为 <
  11. 标准输出:代码为 1 ;或称为 stdout;使用的方式为 1>
  12. 错误输出:代码为 2 ;或称为 stderr;使用的方式为 2>

7. 管道

  1. |:上一条命令的输出输入给下一条命令
  2. tee:读取标准输入的数据,并将其内容输出成文件

8. 命令排序

  1. ;:顺序地独立执行各条命令, 彼此之间不关心是否失败, 所有命令都会执行
  2. &&: 顺序执行各条命令, 只有当前一个执行成功时候, 才执行后面的
  3. ||:顺序执行各条命令, 只有当前面一个执行失败的时候, 才执行后面的

9.通配符(元字符)

  1. *:匹配 0 个或匹配任意多个字符
  2. ?:匹配任意一个字符
  3. []:匹配括号中任意一个字符,如[abc] [a-z] [0-9] [a-zA-Z0-9] [^a-zA-Z0-9]
  4. ():在子Shell中执行,不会对当前环境造成影响
  5. {}:集合
  6. \:转义元字符

四. Shell变量

1. 什么是变量?

用一个特定的字符串去表示不固定的内容

2.变量的类型

2.1. 自定义变量

  1. 定义变量:变量名=变量值,变量名必须以字母或下划线开头
  2. 引用变量:$变量名${变量名}
  3. 查看变量:echo $变量名
  4. 取消变量:unset 变量名
  5. 作用范围:仅在当前Shell中有效

2.2. 系统环境变量

  1. 定义环境变量:
    • export 变量名=变量值
    • export 变量名:将自定义变量转换成环境变量
  2. 引用环境变量:$变量名${变量名}
  3. 查看环境变量:
    • echo $变量名
    • env:显示所有环境变量
  4. 取消环境变量:unset 变量名
  5. 作用环境范围:在当前Shell和子Shell中有效

2.3. 位置变量

$number:第n个参数作为变量值赋给变量

2.4. 预定义变量

  1. $0:脚本名
  2. $*:所有的参数
  3. $@:所有的参数
  4. $#:参数的个数
  5. $$:当前进程的PID
  6. $!:上一个后台进程的PID
  7. $?:上一个命令的返回值,0表示成功,非零表示失败

3. 变量赋值方式

  1. 显示赋值:变量名=变量值

    #示例
    ip=192.168.1.100
    school=“Tsinghua University”
    today1=`date +%F`
    today2=$(date +%F)
    
  2. 从键盘读入变量值 readdaima

    #示例:
    read 变量名
    read -p "提示信息:" 变量名
    read -t 5 -p "提示信息:" 变量名
    read -n 2 变量名
    

4. 定义或引用变量时注意事项

  1. "":弱引用

    #示例
    school=“Tsinghua University”
    echo "${school} is good"
    #输出:Tsinghua University is good
    
  2. '':强引用

    #示例
    school=“Tsinghua University”
    echo '${school} is good'
    #输出:${school} is good
    
  3. ``:命令替换,等价于$(),反引号中的Shell命令会被先执行

    #示例
    touch `data +%F`_file1.txt
    touch $(date +%F)_file2.txt
    

5. 变量的运算

5.1. 整数运算

  1. 方法一:expr

    #示例	+ - \* / %
    #注意:星号不进行转义会报错
    #注意:运算符号左右各有一个空格
    expr 1 + 2
    expr $num1 + $num2
    
  2. 方法二:$(())

    #示例 + - * / %
    echo $(($num1+$num2))
    #变量名前的$符号可省略不写
    echo $((num1+num2))
    echo $((5-3*2))
    echo $(((5-3)*2))
    echo $((2**3))
    sum=$((1+2)) ; echo $sum
    
  3. 方法三:$[]

    #示例 + - * / %
    echo $[5+2]
    echo $[5**2]
    
  4. 方法四:let

    #示例
    let sum=2+3 ; echo $sum
    let i++ ; echo $i
    

5.2. 小数运算

#示例
echo "2*4" | bc
echo "2^4" | bc		#^代表取幂
echo "scale=2;6/4" | bc		#小数点后保留两位
awk 'BEGIN{print 1/2}'
echo "print 5.0/2" | python

5.3. 变量运算实例

  1. 计算内存使用量

    #!/usr/bin/bash
    mem_used=`free -m | grep '^Mem:' | awk '{print $3}'`
    mem_total=`free -m | grep '^Mem:' | awk '{print $2}'`
    mem_percent=$((mem_used*100/mem_total))
    echo "当前内存使用百分比为:$mem_percent %"
    
  2. ping主机

    #!/usr/bin/bash
    ip=192.168.0.100
    i=1
    while [ $i -le 5 ]
    do
    	ping -c1 $ip &>/dev/null
    	if [ $? -eq 0];then
    		echo "$ip is up..."
    	fi
    	let i++
    done
    

6. 变量内容的删除和替换

  1. 变量内容删除

    #示例
    url=www.leeyiding.com
    echo ${url}			#打印变量值(标准查看)
    #输出:www.leeyiding.com
    echo ${#url}		#查看变量长度
    #输出:17
    
    ##字符串从前往后删除
    echo ${url#www.}	#删除字符串www.
    #输出:leeyiding.com
    echo ${url#*.}		#删除字符串www.(从前往后,最短匹配)
    #输出:leeyiding.com
    echo ${url##*.}		#删除字符串www.leeyiding.(从前往后,最长匹配/贪婪匹配)
    #输出:com
    
    ##字符串从后往前删除
    echo ${url%.com}	#删除字符串.com
    #输出:www.leeyiding
    echo ${url%.*}		#删除字符串.com(从后往前,最短匹配)
    #输出:www.leeyiding
    echo ${url%%.*}		#删除字符串.leeyiding.com(从后往前,最长匹配/贪婪匹配)
    #输出:www
    
  2. 变量内容的索引及切片

    #示例
    echo ${url:0:13}	#从第1个字符开始,共截取13个字符
    #输出www.leeyiding
    echo ${url:4:9}		#从第4个字符开始,共截取9个字符
    #输出leeyiding
    echo ${url:4}		#从第4个字符开始,一直截取到最后
    #输出leeyiding.com
    
  3. 变量内容的替换

    #示例
    echo ${url/com/cn}		#将com替换成cn
    #输出www.leeyiding.cn
    echo ${url/w/W}			#从前往后将第一个w替换成W
    #输出Www.leeyiding.com
    echo ${url//w/W}		#贪婪匹配,将所有w替换成W
    #输出WWW.leeyiding.com
    
  4. 变量的替代

    #示例1
    unset var1			#取消变量var1
    unset var2
    unset var3
    
    var2=				#赋给变量var2空值
    var3=333			#将值333赋给变量var
    echo ${var1-aaa}	#将值aaa赋给变量var1,输出aaa
    echo ${var2-bbb}	#变量var2为空值,无法替换,故输出空值
    echo ${var3-ccc}	#变量var3已有值,无法替换,故输出333
    
    #总结
    #${value-word}
    #变量没有被赋值:会使用“新的变量值”替代
    #变量有被赋值(包括空值):不会被替代
    
    #示例2
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1:-aaa}	#输出aaa
    echo ${var2:-bbb}	#输出bbb
    echo ${var3:-ccc}	#输出333
    
    #总结
    #${value:-word}
    #变量没有被赋值(包括空值):都会使用“新的变量值”替代
    #变量有被赋值:不会被替代
    
    #示例3
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1+aaa}	#输出空值
    echo ${var2+bbb}	#输出bbb
    echo ${var3+ccc}	#输出ccc
    
    #总结
    #${value:+word}
    #变量被赋值(包括空值):都会使用“新的变量值”替代
    #变量没有被赋值:不会被替代
    
    #示例4
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1:+aaa}	#输出空值
    echo ${var2:+bbb}	#输出空值
    echo ${var3:+ccc}	#输出ccc
    
    #总结
    #${value:+word}
    #变量被赋值:会使用“新的变量值”替代
    #变量没有被赋值(包括空值):不会被替代
    
    #示例5
    unset var1
    unset var2
    unset var3
    var2=
    var3=333
    echo ${var1=aaa}	#输出aaa
    echo ${var2=bbb}	#输出空值
    echo ${var3=ccc}	#输出ccc
    
    #总结
    #${value=word}
    #变量被赋值或没有赋值:都会使用“新的变量值”替代
    #变量为空值:不会被替代
    
    #示例6
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1:=aaa}	#输出aaa
    echo ${var2:=bbb}	#输出bbb
    echo ${var3:=ccc}	#输出333
    
    #总结
    #${value:=word}
    #变量没有被赋值(包括空值):都会使用“新的变量值”替代
    #变量被赋值:不会被替代
    
    #示例7
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1?aaa}	#报错,输出 bash: var1: aaa
    echo ${var2?bbb}	#输出空值
    echo ${var3?ccc}	#输出333
    
    #总结
    #${value?word}
    #变量没有被赋值:报错
    #变量被赋值(包括空值):不会被替代
    
    #示例8
    unset var1
    unset var2
    unset var3
    
    var2=
    var3=333
    echo ${var1:?aaa}	#报错,输出 bash: var1: aaa
    echo ${var2:?bbb}	#报错,输出 bash: var2: bbb
    echo ${var3:?ccc}	#输出333
    
    #总结
    #${value:?word}
    #变量没有被赋值(包括空值):报错
    #变量被赋值:不会被替代
    

7. i++ 和++i

  1. 对变量值的影响

    #示例
    i=1
    let i++
    echo $i		#输出2
    
    j=1
    let ++j
    echo $j		#输出2
    
  2. 对表达式的值的影响

    #示例
    unset i
    unset j
    i=1
    j=1
    let x=i++	#先赋值,再运算
    let y=++j	#先运算,再赋值
    echo $i		#输出2
    echo $j		#输出2
    echo $x		#输出1
    echo $y		#输出2
    

8. 基础总结

8.1. 各种符号

  • ():在子Shell中执行命令
  • (()):数值比较、运算
  • $():命令替换,等用于``
  • $(()):整数运算
  • {}:集合
  • ${}:变量的引用、内容替换或替代
  • []:条件测试(文件测试、数值比较、字符串比较)
  • [[]]:在[]基础上支持正则表达式
  • $[]:整数运算

8.2. 执行脚本

执行方式 权限 执行Shell
./01.sh 需要执行权限 在子Shell中执行
bash 01.sh 不需要执行权限 在子Shell中执行
. 01.sh 不需要执行权限 在当前Shell中执行
source 01.sh 不需要执行权限 在当前Shell中执行

8.3 调试脚本

  • sh -n 02.sh:仅调试syntax error
  • sh -vx 0.2sh:以调试的方式执行,查询整个执行过程

四. Shell 条件测试

1. 表达式

  1. test 条件表达式
  2. [ 条件表达式 ]
  3. [[ 条件表达式 ]]
  4. C语言风格(())
    • ((1>2))
    • ((1==2))
    • ((1>=2))
    • ((1!=2))
    • ((`id -u`>0))
    • (($UID==0))

2. 条件测试参数

表达式 描述 测试类型
(EXPRESSION) 表达式为真 条件判断
! EXPRESSION 表达式为假 条件判断
EXPRESSION1 -a EXPRESSION2 表达式1和表达式2都为真 条件判断
EXPRESSION1 -o EXPRESSION2 表达式1或表达式2其中一个为真 条件判断
-n STRING 字符串长度不为0 字符串判断
-z STRING 字符串长度为0 字符串判断
STRING1 = STRING2 字符串1和字符串2相同 字符串判断
STRING1 != STRING2 字符串1和字符串2不相同 字符串判断
INTEGER1 -eq INTEGER2 整数1与整数2相同 整数判断
INTEGER1 -ge INTEGER2 整数1大于等于整数2 整数判断
INTEGER1 -gt INTEGER2 整数1大于整数2 整数判断
INTEGER1 -le INTEGER2 整数1小于等于整数2 整数判断
INTEGER1 -lt INTEGER2 整数1小于整数2 整数判断
INTEGER1 -ne INTEGER2 整数1不等于整数2 整数判断
-d DIR 检查目录是否存在 文件判断
-e FILE or DIR 检查文件或目录是否存在 文件判断
-f FILE 检查文件是否存在 文件判断
-r FILE 检查文件是否存在并可读 文件判断
-s FILE 检查文件是否存在并非空 文件判断
-w FILE 检查文件是否存在并可写 文件判断
-x FILE 检查文件是否存在并可执行 文件判断
-O FILE 检查文件是否存在并属当前用户所有 文件判断
-G FILE 检查文件是否存在并且默认组与当前用户相同 文件判断
FILE1 -nt FILE2 检查文件1 是否比文件2 新 文件判断
FILE1 -ot FILE2 检查文件1 是否比文件2 旧 文件判断

3. 示例

#判断目录是否存在
test -d /home
echo $?				#目录存在,输出0
test -d /home111	
echo $?				#目录不存在,输出1
#逻辑判断
[ 1 -lt 2 -a 5 -gt 10 ]; echo$?	#输出1
[ 1 -lt 2 -o 5 -gt 10 ]; echo$?	#输出0
[[ 1 -lt && 5 -gt 10 ]]; echo$?	#输出1
[[ 1 -lt || 5 -gt 10 ]]; echo$?	#输出0
#!/usr/bin/bash
#创建用户脚本 creat_user.sh
read -p "Please input a username:" user

if id $user &>/dev/null; then
	echo "user $user already exists"
else
	useradd $user
	if [ $? -eq 0 ]; then
		echo "$user is created."
	fi
fi
#!/usr/bin/bash
#根分区磁盘用量报警 disk_use.sh
disk_use=`df -Th | grep '/$' | awk '{print $(NF-1)}' | awk -F "%" '{print $1}'`
mail_user=root

if [ $disk_use -ge 90 ]; then
	echo "`data +%F-%H` disk:${disk_use}% is used" |mail -s "disk war..." $mail_user
fi
#!/usr/bin/bash
#批量添加用户 useradd1.sh
read -p "Please input number:" num
read -p "Please input prefix:" prefix

for i in `seq $num`		#seq创建从1到num序列
do
	user=$prefix$i
	useradd $user
	echo "123" | passwd --stdin $user &>/dev/null
	if [ $? -eq 0 ];then
		echo "$user is created"
	fi
done
#!/usr/bin/bash
#批量添加用户(严格模式) useradd2.sh
read -p "Please input number:" num
if [[ ! "$num" =~ ^[0-9]+$ || "$num" =~ ^0+$ ]];then	#正则匹配,判断$num是否是数字或为一个或多个0
	echo "Error number"
	exit
fi

read -p "Please input prefix:" prefix
if	[ -z "$prefix" ];then	#判断字符串是否为0
	echo "Error prefix"
	exit
fi

for i in `seq $num`		#seq创建从1到num序列
do
	user=$prefix$i
	useradd $user
	echo "123" | passwd --stdin $user &>/dev/null
	if [ $? -eq 0 ];then
		echo "$user is created"
	fi
done
#/usr/bin/local
#判断是否是命令 if_commanf.sh
command=/bin/date
if command -v $command &>/dev/null;then
	:						#不执行任何命令,等同于true
else
	yum install $command
fi

五. 匹配模式:case

1. case语法结构

case $Var in
Pattern1)
	Command1
	;;
Pattern2)
	Command2
	;;
Patter
	Command3
	;;
*)
	NoPatternCommand
esac

2. 示例

#!/usr/bin/bash
#多系统配置yum源 yum_config_case.sh
yum_server=192.168.0.100
os_version=`cat /etc/redhat-release | awk '{print $4}' | awk -F "." '{print $1"."$2}'`

[ -d /etc/yum.repos.d] || mkdri /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak &>/dev/null

case "$os_version" in
"7.3")
	cat > /etc/yum.repos.d/centos7u3.repo <<-EOF
	[centos7u3]
	name=centos7u3
	baseurl=ftp://$yum_server/centos7u3
	gpgcheck=0
	EOF
	echo "7.3 yum configure..."
	;;
"6.8")
	curl -o /etc/yum.repos.d/centos6u8.repo ftp://$yum_server/centos6u8.repo
	;;
"5.9")
	curl -o /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo
	;;
*)
	echo "error"
esac
echo "Finish"
#!/usr/bin/bash
#删除用户 del_user.sh
read -p "Please input a username:" user

id $user &>/dev/null
if [ $? -ne 0 ];then
	echo "No user named $user"
	exit 1
fi

read -p "Are you sure you want delete $user ?[y/n]" action
#if[ "$action" = "y" -o "$action"= "Y" -o "$action" = "yes" -o "$action" = "YES" ];then
#	userdel -r $user
#	echo "$user is deleted"
#fi
case "$action" in
y|Y|yes|YES)
	userdel -r $user
	echo "$user is deleted"
	;;
*)
	echo "error"
esac
#!/usr/bin/bash
#简易JumpServer跳板机系统 jump_server.sh
#创建管理用户jms
#useradd jms
#创建密钥
#ssh-keygen
#拷贝密钥
#ssh-copy-id 192.168.0.101
#ssh-copy-id 192.168.0.102
#ssh-copy-id 192.168.0.103
#脚本添加进.bash——profile
#/home/jms/jump_server.sh
trap "" HUP INT OUIT TSTP	#捕捉信号,禁止退出
web1=192.168.0.101
web2=192.168.0.102
mysql1=192.168.0.103
clear

while:
do
    cat << -EOF
    +-----------------------------+
    |	Jump_server				  |
    |	1. web1					  |
    |	2. web2					  |
    |	3. mysql1				  |
    +-----------------------------+
    EOF
	echo -en "\e[1;32input number: \e[0m"
    read num
    case "$num" in
    1)
        ssh jms@$web1
        ;;
    2)
        ssh jms@$web2
        ;;
    3)
        ssh jms@$mysql1
        ;;
    "")
        ;;
    *)
        echo "error"
    esac
done
#!/usr/bin/bash
#简易系统工具箱 system_manage.sh
menu() {
    cat <<-EOF
    #################################
    #		h. help					#
    #		f. disk partition		#
    #		d. filesystem mount		#
    #		u. system load			#
    #		q. exit					#
    #################################
    EOF
}
menu

while true
do
    read -p "Please input[h for help]: " action
    case "$action" in
    h)	 clear; menu;;
    f)	 fdisk -l;;
    d)	 df -Th;;
    m)	 free -m;;
    u)	 uptime;;
    q)	 break;;
	"")	;;
    *)	echo "error"
    esac
done

六. 流程控制:if

1. 结构

#单分支结构
if 条件测试
then 命令序列
fi

#双分支结构
if 条件测试
then 命令序列
else 命令序列
fi

#多分支结构
if 条件测试1
then 命令序列1
elif 条件测试2
then 命令序列
elif 条件测试3
then 命令序列
else 命令序列
fi

2. 示例

#!/usr/bin/bash
#安装Apache install_apache01.sh
ping c1 wwww.baidu.com &>/dev/null
if [ $? -ne 0 ];then
	echo "connect unreachable"
	exit
fi

yum -y install httpd
systemstl start httpd
systemstl enable httpd
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload
sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
setenforce 0
#!/usr/bin/bash
#安装Apache install_apache02.sh
gatewau=192.168.0.1

ping c1 wwww.baidu.com &>/dev/null
if [ $? -eq 0 ];then
    yum -y install httpd
    systemstl start httpd
    systemstl enable httpd
    firewall-cmd --permanent --add-service=http
    firewall-cmd --permanent --add-service=https
    firewall-cmd --reload
    sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
    setenforce 0
    #测试
    curl http://127.0.0.1 &>/dev/null
    if [ $? eq 0 ];then
    	echo "Apache is OK!"
    fi
elif ping -c1 $gateway &>/dev/null;then
	echo "Check DNS..."
else
	echo "check ip address!"
fi
#!/usr/bin/bash
#多系统配置yum源 yum_config.sh

os_version=`cat /etc/redhat-release | awk '{print $4}' | awk -F "." '{print $1"."$2}'`
yum_server=192.168.0.100

[ -d /etc/yum.repos.d] || mkdri /etc/yum.repos.d/bak
mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/bak &>/dev/null
if ["$os_version" = "7.3" ];then
	cat > /etc/yum.repos.d/centos7u3.repo <<-EOF
	[centos7u3]
	name=centos7u3
	baseurl=ftp://$yum_server/centos7u3
	gpgcheck=0
	EOF
	echo "7.3 yum configure..."
elif ["$os_version" = "6.8" ];then
	curl -o /etc/yum.repos.d/centos6u8.repo ftp://$yum_server/centos6u8.repo
elif ["$os_version" = "5.9" ];then
	curl -o /etc/yum.repos.d/Centos-Base.repo http://mirrors.aliyun.com/repo/Centos-5.repo
fi

七. Shell 循环:for

1. 语法结构

for 变量名 in 取值列表
do 
	循环体
done

2. 示例

#!/usr/bin/bash
#探测局域网主机
>ip.txt
for i in {2..254}
do
	{
		ip=192.168.0.$i
		ping -c1 -W1 $ip &>/dev/null
		if [ $? -eq 0 ];then
			echo "$ip" | tee -a ip.txt
		fi
	}&
done
wait
echo "finishi...."
#!/usr/bin/bash
#批量创建用户
while true
do
	read -p "Please enter prefix & pass & num: " prefix pass num
	printf"user information:
	----------------------
	user prefix: $prefix
	user password: $pass
	user number: $num
	----------------------
	"
	read -p "Are you sure?[y/n]" action
	if ["$action" = "y" ];then
		break
	fi
done

for i in `seq -w $num`
do 
	user=$prefix$i
	id $user &>/dev/null
	if [ $? -eq 0 ];then
		echo "user $user already exists"
	else
		useradd $user
		echo "pass" | passwd --stdin $user &>/dev/null
		if [ $? -eq 0 ];then
			echo "$user is created."
		fi
	di
done
#!/usr/bin/bash
#批量从文件读入用户名,创建用户
pass=123

if [ $# -eq 0 ];then
	echo "usage: 'basename $0' file"
	exit 1
fi
if [ ! -f $1 ];then
	echo "error file"
	exit 2
fi

for user in `cat $1`
do
	id $user &>/dev/null
	if [ $? -eq 0 ];then
		echo "user $user already exists"
	else
		useradd $user
		echo "$pass" | passwd --stdin $user &>/dev/null
		if [ $? -eq 0 ] ;then
			echo "$user is created."
		fi
	fi
done
#!/usr/bin/bash
#批量从文件读入用户名和密码,创建用户

if [ $# -eq 0 ];then
	echo "usage: 'basename $0' file"
	exit 1
fi
if [ ! -f $1 ];then
	echo "error file"
	exit 2
fi

#重新定义分割符
IFS=$"\n"
for line in `cat $1`
do
	if [ ${#line} -eq 0 ];then
		continue
	fi
	user=`echo "$line" | awk '{print $1}'`
	pass=`echo "$line" | awk '{print $2}'`
	id $user &>/dev/null
	if [ $? -eq 0 ];then
		echo "user $user already exists"
	else
		useradd $user
		echo "$pass" | passwd --stdin $user &>/dev/null
		if [ $? -eq 0 ] ;then
			echo "$user is created."
		fi
	fi
done
#!/usr/bin/bash
#批量修改密码

read -p "Please enter a New Password: " pass

for ip in $(cat ip.txt)
do
	{
		ping -c1 -W1 $ip &>/dev/null
		if [ $? eq 0 ];then
			ssh $ip "echo $pass | passwd --stdin root"
			if [ $? -eq 0 ];then
				echo "$ip" >> ok_`data +%F`.txt
			else
				echo "$ip" >> fail_`data +%F`.txt
			fi
		else
			echo "$ip" >> fail_`data +%F`.txt
		fi
	}&
done
wait
echo "Everything is OK"
#!/usr/bin/bash
# 批量修改远程主机配置文件
for ip 'cat ip.txt'
do
	{
		ping -c1 -W1 $ip &>/dev/null
		if [ $? -eq 0 ];then
			ssh $ip "sed -ri '/^#UseDNS/cUseDNS no' /etc/ssh/sshd_config"
			ssh $ip "systemctl stop firewalld; systemctl disable firewalled"
		fi
	}&
done
wait

八. Shell循环:while until

1. 语法结构

#while
#当条件测试成立(条件测试为真),执行循环体
while 条件测试
do
	循环体
done

#until
#当条件测试成立(条件测试为假),执行循环体
until 条件测试
do
	循环体
done

2. 示例

#!/usr/bin/bash
#while循环读取文件用户名,批量创建用户
while read user
do
	id $user &>/dev/null
	if [ $? -eq 0 ] ;then
		echo "user $user already exista"
	else
		useradd $user
		if [ $? -eq 0 ];then
			echo "$user is created."
		fi
	fi
done < user.txt
#!/usr/bin/bash
#读取文件用户名和密码,批量创建用户
while read line
do
	if [ ${#line} -eq 0];then
		continue
	fi
	user=`echo "$line" | awk '{print $1}'`
	pass=`echo "$line" | awk '{print $2}'`
	id $user &>/dev/null
	if [ $? -eq 0 ] ;then
		echo "user $user already exista"
	else
		useradd $user
		echo "$pass" | passwd --stdin $user &>/dev/null
		if [ $? -eq 0 ];then
			echo "$user is created."
		fi
	fi
done < user.txt
#!/usr/bin/bash
#while测试远程主机连接
ip=192.168.0.100
while ping -c1 -W1 $ip &>/dev/null
do
	sleep 1
done
echo "$ip is down!"
#!/usr/bin/bash
#until测试远程主机连接
ip=192.168.0.100
until ping -c1 -W1 $ip &>/dev/null
do
	sleep 1
done
echo "$ip is up!"

九. Shell 并发控制

1. 传统并发方式

在循环体中套用{}&可以将每个循环放入后台执行,达到处理并发的目的,但只是用于小规模的并发,且该并发是无控制的,无限制蔓延,导致每个并发的完成没有先后。

#!/usr/bin/bash
#测试远程主机连通
for i in {1..254}
do
	{
		ip=192.168.0.$i
		ping c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
         	echo "$ip is up"
         else
         	echo "$ip is down"
         fi
	}&
done
wait

2. fd和命名管道实现并发控制

2.1. 句柄概念

File Descriptors(FD,文件描述符或文件句柄)

ls /proc/$$/fd
#0	1	10	2	29

ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:24 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:24 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:24 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:24 2 -> /dev/pts/3

touch file1
exec 6<> file1		#创建句柄
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:32 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:32 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 2 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:32 6 -> /root/file1ls

echo "111" >> /proc/$$/fd/6
cat file1
#111

rm -rf /file1
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:33 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:33 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 2 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:33 6 -> /root/file1 (deleted)

cp /proc/$$/fd/6 file1
cat file1
#1111

exec 6<&-	#删除句柄
ll /proc/$$/fd
#总用量 0
#lrwx------ 1 root root 64  2月 29 17:34 0 -> /dev/pts/3
#lrwx------ 1 root rootd 64  2月 29 17:34 1 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:34 10 -> /dev/pts/3
#lrwx------ 1 root root 64  2月 29 17:34 2 -> /dev/pts/3

2.2. 管道

  1. 匿名管道

    rpm -qa | grep bash
    
  2. 命名管道

    #使用命名管道,将内容传给另一终端
    
    mkfifo /tmp/fifo1
    file /tmp/fifo1
    #/tmp/fifo1: fifo (named pipe)
    
    #终端1
    tty
    #/dev/pts/0
    ls /dev > /tmp/fifo1
    #终端2
    tty
    #/dev/pts/1
    grep 'sda' /tmp/fifo1
    # sda sda1 sda2 sda3
    

2.3. 举例

#!/usr/bin/bash
#测试远程主机连通
thread=5
tmp_fifofile=/tmp/$$.fifo

mkfifo $tmp_fifofile	#创建管道文件
exec 8<> $tmp_fifofile	#以编号为8的文件句柄打开管道文件
rm  -f $tmp_fifofile	#删除管道文件

for i in `seq $thread`
do
	echo >&8
done

for i in {1..254}
do
	read -u 8	
	#读到编号为8的文件句柄执行本次循环,否则等待之前循环完成后
	#再执行本次循环,最大并发数量为变量thread的值
	{
		ip=192.168.0.$i
		ping c1 -W1 $ip &>/dev/null
        if [ $? -eq 0 ];then
         	echo "$ip is up"
         else
         	echo "$ip is down"
         fi
         echo >&8	#代表本次循环执行完成,释放进程
	}&
done
wait
exec 8>&-		#释放文件句柄

十. Expect 处理交互命令

1. 介绍

借助expect可以处理交互式的命令,使之自动化完成

2. 安装

yum -y install expect

3. 语法结构

expect [option] [args]

参数

  • -c 从命令行执行expect脚本
  • -d 输出调试信息

相关命令

  • spawn:启动新的进程
  • send:用于向进程发送字符串
  • expect:从进程接收字符串
  • interact:允许用户交互
  • exp_contimue:匹配多个字符串在执行动作后的命令

4. 示例

#!/usr/bin/expect
#ssh非交互式连接远程主机

set ip [lindex $argv 0] 	#接收第一个参数 
#set ip 192.168.0.100
set user root
set password centos
set timeout 5

spawn ssh $user@$ip
expect {
	"yes/no" { send "yes\r" ; exp_continue }
	"password:" { send "$password\r"}
}

expect "#"
send "useradd 123\r"
send "pwd\r"
send "exit\r"
expect eof	#结束交互
#!/usr/bin/expect
#scp非交互式传输文件

set ip [lindex $argv 0]
set user root
set password centos
set timeout 5

spawn scp -r /etc/hosts $user@$ip:/tmp

expect {
	"yes/no" { send "yes\r" ; exp_continue }
	"password:" { send "$password\r"}
}
expect eof
#!/usr/bin/bash
#批量推送公钥
>ip.txt
passwd=centos

#判断是否安装expect
rpm -q expect &>/dev/null
if [ &? -ne 0 ];then
	yum -y install expect &>/dev/null
	echo "expect is installed"
fi

#判断是否安装密钥
if [ ! -f ~/.ssh/id_rsa ];then
	ssh-keygen -P "" -f ~/.ssh/id_rsa	#非交互式创建密钥
fi
	
for i in {2..254}
do
	{
		ip=192.168.0.$i
		ping -c1 -W1 $ip &>/dev/null
		if [ $? -eq 0 ];then
			echo "$ip" >> ip.txt
			/usr/bin/expect	 <<-EOF
				set timeout 10
				spawn ssh-copy-id $ip
				expect {
					"yes/no" { send "yes\r" ; exp_continue }
					"password:" { send "$password" }
				}
			expect eof
			EOF
		fi
	}&
done
wait
echo "Finish....."

十一. Shell数组变量

1. 普通数组

1.1. 概念

普通数组:只能使用整数作为数组索引

1.2. 定义数组

#方法一:一次赋一个值
数组名[下标]=变量值
array1[0]=pear
array1[1]=apple
array1[2]=orange
array1[2]=peach

#方法二:一次赋多个值
数组名=(变量1 变量2 变量3)
array2=(tom jack alice)
array3=(`cat /etc/passwd`)
array4=(`ls /var/ftp/Shell/for8`)
array5=(tom jack alice "bash shell")
colors=($red $blue $green $recolor)
array5=(1 2 3 4 5 6 7 "linux shell" [20]=puppet)

1.3. 查看数组

declare -a
#declare -a array1='([0]="pear" [1]="apple" [2]="orange" [3]="peach")'
#declare -a array2='([0]="tom" [1]="jack" [2]="alice")'

1.4. 访问数组元素

echo ${array1[0]}		#访问数组中第一个元素
echo ${array1[@]}		#访问数组中所有元素
echo ${array1[*]}		#访问数组中所有元素
echo ${#array1[@]}		#统计数组元素的个数
echo ${!array1[@]}		#获取数组元素的索引
echo ${array1[@]:1}		#从数组下标1开始
echo ${array1[@]:1:2}	#从数组下标1开始,访问两个元素

1.5. 遍历数组

通过数组元素的个数遍历

通过数组元素的索引进行遍历

1.6. 示例

#!/usr/bin/bash
#使用while遍历hosts文件
while
do
	hosts[++i]=$line
done </etc/hosts

for i in ${!hosts[@]}
do
	echo "$i: ${host[$i]}"
done
#!/usr/bin/bash
#使用for遍历hosts文件
OLD_IFS=$IFS
IFS=$'\n'
for	line in 'cat /etc/hosts'
do
	hosts[++1]=$line
done

for i in ${!hosts[@]}
do
	echo "$i: ${host[$i]}"
done
IFS=$OLD_IFS

2. 关联数组

2.1. 概念

普通数组:可以使用字符串作为数组索引

2.2. 定义关联数组

#声明关联数组变量
declare -A ass_array1
declare -A ass_array2

#方法一:一次赋一个值
数组名[下标]=变量值
ass_array1[index1]=pear
ass_array1[index2]=apple
ass_array1[index3]=orange
ass_array1[index4]=peach

#方法二:一次赋多个值
数组名=([索引1]=变量1 [索引2]=变量2 [索引3]=变量3)
array2=([name]=jack [age]=10 [sex]=male [height]=160)

2.3. 示例

#!/usr/bin/bash
#统计性别
declare -A sex
while
do
	type=`echo $line | awk '{print $2}'`
	let sex[$type]++
done < sec.txt

for i in ${!sex[@]}
do
	echo "$i: $sex[$i]"
done
#!/usr/bin/bash
#统计不同shell数量
declare -A shells
while read line
do
	type=`echo $line | awk -F ":" '{print $NF}'`
	let shells[$type]++
done < /etc/passwd

for i in ${!sheels[@]}
do
	echo "$i: ${shells[$i]}"
done 
#!/usr/bin/bash
#统计TCP连接状态

while :
do
	declare -A status
	unset satus
    type=`ss -an | grep :80 | awk '{print $2}'`
    for i in $type
    do
        let status[$i]++
    done

    for j in ${!status[@]}
    do
        echo "$j: ${status[$j]}"
    done
	sleep 1
	clear
done

十二. function函数的定义及调用

1. 函数的概念

完成特定功能的代码片段

在Shell中定义函数可以使用代码模块化,便于复用代码

函数必须先定义才能使用

  • 传参 $1$2
  • 变量 local
  • 返回值 return $?

2. 定义函数

#方法一
函数名(){
	函数要实现的代码
}
#方法二
function 函数名(){
	函数要实现的代码
}

3. 调用函数

函数名
函数名 参数1 参数2

4. 示例

#!/usr/bin/bash
#阶乘
function factorial() {
	factorial=1
	for((i=1;i<=$1;i++))
	do
		factorial=$[ $factorial * $i ]
	done
	echo "$1的阶乘是: $factorial"
}

factorial $1
#!/usr/bin/bash
#函数使用return输出
fun2()	{
    read -p "enter num: " num
    return $[2*$num]	#最大不超过255
}
fun2
echo "fun 2 return value is : $?"
#!/usr/bin/bash
#函数使用out输出
fun2()	{
    read -p "enter num: " num
    echo $[2*$num]
}
result=`fun2`	#函数执行结果返回给变量

echo "fun 2 return value is : $return"
#!/usr/bin/bash
#函数位置参数
if [ $# -ne 3 ];then
	echo "usage: `basename $0` par1 par2 par3"
fi

fun3() {
	echo "$(($1 * $2 * $3))"	#$1 $2 $3为函数位置参数
}

result=`fun3 $1 $2 $3`			#$1 $2 $3为脚本位置参数

echo "result is: $result"
#!/usr/bin/bash
#函数使用数组传参
num=(1 2 3 4 5)
#echo "${num[@]}"

array() {
	local factorial=1	#局部变量,函数内部生效
	for i in $*			#传入所有位置参数
	do
		factorial=$[$factorial * $i]
	done
	echo "$factorial"
}

array ${num[*]}
#!/usr/bin/bash
#函数输出数组变量
num=(1 2 3)

array() {
	local newarray=($*)
	local i
	for ((i=0;i<$#;i++))
	do
		newarray[$i]=$(( ${newarray[$i]} * 5 ))
	done
	echo "${newarray[*]}"
}
result=`array ${num[*]}`
echo ${result[*]}
#!/usr/bin/bash
#函数输出数组变量2
num=(1 2 3)

array() {
	local i
	local j
	local outarray=()
	for i in $*
	do
		newarray[j++]=$[$i*5]
	done
	echo "${outarray[*]}"
}
result=`array ${num[*]}`
echo ${result[*]}

十三. Shell程序内置命令

1. 概述

  • :
  • true
  • false
  • exit:退出整个程序
  • break:结束当前循环或跳出本层循环
  • continue:忽略本次循环剩余的代码,直接进行下一次循环
  • shift:使位置参数向左移动,默认移动1位,可以使用shift 2

2. 示例

#!/usr/bin/bash
for i in {A..D}
do
	echo -n "$i"
	for j in {1..9}
	do
		if [ $j -eq 5];then
			continue
		fi
		echo -n $j
	done
	echo
done
#!/usr/bin/bash
while [ $# -ne 0]
do
	let sum+=$1
	shift
done
echo "sum: $sum"

我很好奇