一、shell脚本介绍
1.1 开头(环境使用shebang机制)
#!/bin/bash 必须写在文件首行
符号#!用来告诉系统它后面的参数是用来执行该文件的程序。
当编辑好脚本时,如果要执行该脚本,还必须使其可执行。
要使脚本可执行:
编译 chmod +x filename 这样才能用./filename或source filename 来运行。
1.2 注释
在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我建议在程序中使用注释。如果使用了注释,那么即使相当长的时间内没有使用该脚本,我们也能在很短的时间内明白该脚本的作用及工作原理。
1.3格式
脚本中如果有语法错误可以用来: bash -n /path/to/some_script
调试跟踪: bash -x /path/to/some_script
第一行的#!/bin/bash是声明这个脚本使用的shell格式,因为我们使用的是bash,所以必须要以“#!/bin/bash”来告诉别人这个脚本里的语法使用的是bash语法,这样它在执行的时候,就能加载bash相关配置文件,使我们的命令能够更好的执行下去。(如果不加,有可能脚本无法执行,无法判断脚本是使用的什么shell。当然如果默认选择bash格式,可以不写,但最好还是脚本第一行写上shebang机制,还有就是创建文本的时候,后缀加上.sh为结尾。
二、变量
2.1 变量定义
所有的变量都由字符串组成,用一个字符串,替代更多更复杂的内容 ,并且您不需要对变量进行声明。在变量使用命令时需要加` ` 或者$( ).
赋值给一个变量,并打印出内容如下:
有时候变量名很容易与其他文字混淆,比如:
num=2
echo “this is the $numnd”
这并不会打印出“this is the 2nd”,而仅仅打印”this is the “,因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量:echo “this is the ${num}nd” 这将打印:this is the 2nd。
注意:变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界
2.2 变量格式
1、必须以字母开头后面可以使用下划线,数字
2、中间不能有空格,不能使用标点符号,不能使用程序中的保留字如:if,for。
3、驼峰语法:首个字母小写,其余开头字母大写如 mageJiaoYu
2.3变量分类
本地变量,环境变量,位置变量,特殊变量
本地变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效。
变量赋值:name=‘value’
可以使用引用value:
(1)可以是直接字串; name=“root”
(2)变量引用:name=”$USER”
(3)命令引用:name=`COMMAND`, name=$(COMMAND)
变量引用:${name}, $name
" ":弱引用,其中的变量引用会被替换为变量值”;
例:[root@localhost ~]# var1=1
[root@localhost ~]# echo "$var1 * '' \$a\\
1 * $a\
(双引号比起单引号比较有人情味,也比较聪明,它能识别里面的变量,不会屏蔽\和$这两个字符的含义,如果需要屏蔽这些字符含义,除了用单引号外,还可以在前面加个\符号。)
' ':强引用,其中的变量引用不会被替换为变量值,而保持原字符串;
例:[root@localhost ~]# var1=2
[root@localhost ~]# echo '$var1 * \$a\\'
$var1 * \$a\\
(单引号可以屏蔽全部字符的特殊意义,但是不能表示一个字符:(单引号')。由于她没有转义字符,所以不能表示她本身。强引用)
显示已定义的所有变量:set
删除变量:unset name 脚本运行完毕要释放变量。
环境变量:生效范围为当前shell进程及其子进程
变量声明、赋值:
export name=VALUE
declare -x name=VALUE
变量引用:$name, ${name}
删除变量:unset name
显示所有环境变量(环境变量的查询):
env
printenv
export
declare -x
#env列出环境下所有环境变量与其内容
#set可查看所有的变量(含环境变量与本地变量)
bash内建的环境变量:PATH SHELL UID HOME PWD LANG MAIL HOSTNAME HISTSIZE PS1等
例:[root@centos7 ~ ]# echo $HOME
[root@centos7 ~]# /root
局部变量:生效范围为当前shell进程中某代码片断(通常指函数)
位置变量:$1, $2, …来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数。
$1, $2, …:对应第1、第2等参数,shift [n]换位置。当位置参数遇到10及10以上的要用{ };例:echo 10st 结果为arg is {10}。
$0:命令本身,是用于取脚本本身的名字
[root@centos7 tmp]# vim file1
#!/bin/bash
dirname $0
basename $0
[root@centos7 tmp]# bash ./file1
.
file1
[root@centos7 tmp]# bash /tmp/file1
/tmp
file1
$#:传递给脚本的参数的个数,一般用于控制参数个数。
[root@centos7 tmp]#vim file2
#!/bin/bash
echo $#
[root@centos7 tmp]# bash file2 123
3
[root@centos7 tmp]# bash file2 123456
6
$?:判断上个命令脚本,函数是否执行成功,命令执行的返回值,0表示没有错误,其他表示有错误,代表上个命令执行失败
[root@centos7 tmp] # ls
file1 file2
[root@centos7 tmp] # echo $?
0
[root@centos7 ~] # lls
bash:lls:command not found...
[root@centos7 ~] # echo $?
1
$*:传递给脚本的所有参数,全部参数合为一个字符串
$@:传递给脚本的所有参数,每个参数为独立字符串
$@ $*只在被双引号包起来的时候才会有差异
set —清空所有位置变量
小总结:变量内容若有空格符可使用双引号" "或者单引号' '将变量内容结合起来:
a.双引号内的特殊字符如$等,可以保持原本的特性,如:
b.单引号内的特殊字符则仅为一般字符(纯文本),如:
c.可用转义字符“\”将特殊符号(如$、\、!)变成一般字符。如
d.在一串命令中,还需要通过其他的命令提供的信息,可以使用反单引号“`命令`”或者“$(命令)”,如:
e.若该变量为了增加变量内容时,则可用“$变量名称”或${变量}累加内容,如:给变量PATH增加内容,不能直接用”PATH=内容“,这样会覆盖掉原本的变量值,应该用“PATH=$PATH:内容”.
f.若该变量需要在其他子进程(子进程:在目前这个shell的情况下,去打开另一个新的shell,新的shell就是子进程)执行,则需要以export来使变量变成环境变量。如:
g.取消变量的方法为使用“unset变量名称”,如:
三、bash的配置文件
3.1按生效范围划分,存在两类:
全局配置:/etc/profile /etc/profile.d/*.sh /etc/bashrc
个人配置:~/.bash_profile ~/.bashrc
3.2按功能划分,存在两类:
profile类和bashrc类
profile类:为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:(1)用于定义环境变量 (2)运行命令或脚本
bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc
功用:(1)定义命令别名和函数 (2)定义本地变量
3.3shell登录两种方式
交互式登录:(不能继承上一个shell)
(1)直接通过终端输入账号和密码登录
(2)使用“su -l UserName 或su - UserName”切换的用户
执行顺序:/etc/profile –> /etc/profile.d/*.sh –> ~/.bash_profile–> ~/.bashrc–> /etc/bashrc
非交互式登录:(继承上一个shell)
(1)su UserName
(2)图形界面下打开的终端,执行脚本
执行顺序:~/.bashrc–> /etc/bashrc–> /etc/profile.d/*.sh
例子:在这几个文件分别设置环境变量A,B,C,D,E
没有重新登陆时,这几个变量就不会生效,echo $A $B $C $D $E,什么都不显示。重新登陆后,这几个变量就会生效。
用非交互式登陆guanyu用户,就会继承上一个shell,所以echo 5个变量都会显示。而用交互式登陆guanyu用户,不能继承上一个shell,就只能读取/etc/profile,/etc/profile.d/*.sh,~/.bash_profile,~/.bashrc,/etc/bashrc这几个文件,而变量C,D都是在用户root的家目录设置的,只能读取到变量A,B,E,所以echo这几个变量只显示变量A,B,E的值。
在root用户下,将变量A的值改为A6,变量B的值改为B7,变量D的值改为D9,变量E的值改为E10
然后不退出重新登陆,直接echo这几个变量,当然不变,因为这几个变量没有生效,此时用非交互式登陆切到guanyu用户下,变量A的值不变,尽管,变量A的值变为A6,但是用非交互式登陆,继承了上一个shell变量A的值A1,但是不读取/etc/profile这个文件,所以不更新变量A的值,变量B的值变为B7,是因为继承了上一个shell变量B的值然后读取/etc/profile.d/mage.sh这个文件,更新变量B的值。变量C3的值不变,变量D的值为D4,是因为继承了上一个shell的变量D的值D4,又因为变量D在root用户的家目录里,所以不读取,不更新变量D的值。变量E5的值为E10,是因为继承了上一个shell的变量D的值,又读取/etc/bashrc所以更新变量E的值,所以变量E的值为E10.
在用交互式登陆guanyu用户,echo这几个变量,不继承上一个shell,只读取文件,因为是交互式登陆,所以/etc/profile,/etc/profile.d/mage.sh,/etc/bashrc这几个文件都能读取,所以变量A,B,E的值为A6,B7,E10,又因为变量C,D都在root用户的家目录里,所以不能读取,变量C,D的值为空。
3.4 read
变量的定义—read读入 默认存储位置$REPLY
如果传的参数多于赋值,最后一个值会全部接收
–p增加提示
[root@centos7 ~]# read -p "输入密码" e (必须有空格)
输入密码123456
[root@centos7 ~]# echo $e
123456
–s 隐藏输入
[root@centos7 ~]# read -s -p "输入密码" e (将密码隐藏)
输入密码
[root@centos7 ~]# echo $e
123456
-t 设置超时时间
[root@centos7 ~]# read -t5 -p "输入密码" e (表示5秒后直接退出当前操作)
输入密码[root@centos7~]#
四、逻辑判断语句
4.1条件判断
[root@centos7 ~]# [ -f /etc/hosts ] 判断是否是文件
[root@centos7~]# echo $?
0
4.2 && ||
&&表示前面执行成功,执行后面.
||表示前面执行成功,不执行后面,前面执行失败,执行后面
[root@centos7 ~]# [ -f /etc/hosts ]&& echo "文件存在" || echo "文件不存在" 相当于返回值0和1
文件存在
[root@centos7 ~]# [ -f /etc/host ]&& echo "文件存在" || echo "文件不存在"
文件不存在
4.3 测试文件符号(test)
4.4 测试字符串
特别注意:字符串必须要用双引号引起来。字符串比较,比较符号两端必须有空格。
4.5测试大小
[root@centos7 ~]# [ 1 -eq 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 -eq 2 ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ 1 -ne 2 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 -gt 2 ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ 1 -gt 0 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 -gt 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ 1 -ge 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 -le 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 -lt 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ 1 -lt 2 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 = 1 ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ 1 = 2 ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ "1" = "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ "1" != "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ "1" > "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ "1" \> "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ "1" \< "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ "1" \<= "2" ] && echo "表达式成立" || echo "表达式不成立"
-bash: [: <=: binary operator expected
表达式不成立
[root@centos7 ~]# [ "1" \<\= "2" ] && echo "表达式成立" || echo "表达式不成立"
-bash: [: <=: binary operator expected
表达式不成立
4.6 与或非 -a -o !
-a 前一个条件成立,后一个条件成立,整个表达式才成立,有一个是错的都不会成立
-o 只要任意一个条件成立,表达式就成立,只有俩个都不成立,才不成立,则表达式不成立
[root@centos7 ~]# [ "1" = "1" -a "2" = "2" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ "1" = "1" -a "2" = "3" ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ "1" = "1" -o "2" = "3" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
[root@centos7 ~]# [ "1" = "2" -o "2" = "3" ] && echo "表达式成立" || echo "表达式不成立"
表达式不成立
[root@centos7 ~]# [ "1" = "1" -a !"2" = "3" ] && echo "表达式成立" || echo "表达式不成立"
-bash: !"2": event not found
[root@centos7 ~]# [ "1" = "2" -o ! "2" = "3" ] && echo "表达式成立" || echo "表达式不成立"
表达式成立
五、循环语句
if 语句 单分支语句
语法格式:
如果if [条件语句]
然后 then
执行命令
fi
实例:编写一个脚本/root/bin/createuser.sh,脚本的执行语法必须是:createuser.sh -u username -m password,选项与参数间可支持多空格,但不能顺序颠倒。
当未指定正确的选项或参数时,以错误输出方式提示“createuser.sh -u username -m password ”后退出脚本。
用户名必须以字母开头,可包括数字和_。否则不合法。以错误输出提示用户"用户名仅包含字母数据和下划线"
当用户名检测合法后,判断用户名是否已存在,若存在,再判断用户是否已设置过密码,若设置过密码,直接退出,未设置,则将密码设置为所指定的密码后以正确输出方式显示“username 密码已更新后退出”
当用户名不存在,则创建用户,并为该用户设置所指定的密码后以正确输出方式显示“用户username已创建并更新密码”
要求脚本执行过程中不能有非要求的其他输出结果出现。脚本在非正确方式退出时应反回给?参数非0值。
#判断用户名是否规范,并且给出返回值
decide=`(echo $2|grep "^[[:alpha:]]\([[:alpha:]]\|[[:digit:]]\|_\)*$" &>/dev/null;echo $?)`
#判断用户是否存在,并给出返回值
id=`id $2 &>/dev/null;echo $?`
#截取用户密码位
mima=`getent shadow $2 |cut -d: -f 2`
#判断第一个参数
if [ "$1" != "-u" ];then
echo "createuser.sh -u username -m password"
exit 1
fi
#判断第三个参数
if [ "$3" != "-m" ];then
echo "createuser.sh -u username -m password"
exit 1
fi
#判断参数个数
if [ $# -ne 4 ];then
echo " createuser.sh -u username -m password"
exit 1
fi
#判断第二个参数
if [ $decide -ne 0 ];then
echo "用户仅包含字母数字和下划线"
exit 1
else
if [ $id -eq 0 ];then
if [ "$mima" == "!!" -o "$mima" == "" ];then
`echo "$4" |passwd --stdin $2 &>/dev/null`
echo "$2密码已更新"
exit 1
else
exit 1
fi
else
`useradd $2`
`echo $4 |passwd --stdin $2 &>/dev/null`
echo "用户$2已创建并更新密码"
fi
fi
#删除变量
unset decide
unset id
unset mima
解析:先定义变量decide,id,mima,再对变量进行测试。
练习:
1.写一个脚本名为jiaozuoyexx.sh 当执行该脚本时如jiaozuoyeXX.sh testXX.sh,就会自动将该testXX.sh传给教师机,
路径是scp testXX.sh mage26@172.17.252.213:~/scripts 密码为mage26
1、vim jiaozuoye.sh
#!/bin/bash
--------------------------
# Filename:
# Revision:
# Date:
# Author:
# Email:
# Website:
# Description:
# ------------------------------------------
scp $1 mage26@172.17.252.213:~/scripts
2、chmod +x createshXX.sh 添加权限
3、./jiaozuoyeXX.sh jiaozuoyeXX.sh 查看结果
思路:这里的$1实际是个参数,代表脚本名称。
2.写一个能够创建新脚本的Shell script,如名为createshXX.sh 当执行时createsh /root/bin/test1.sh
则会自动创建并打开/root/bin/test1.sh,且其中包含以下内容。
1、vim createshXX.sh
2、#!/bin/bash
# ------------------------------------------
# Filename:
# Revision:
# Date:
# Author:
# Email:
# Website:
# Description:
# ------------------------------------------
echo "#!/bin/bash
# ------------------------------------------
# Filename:
# Revision:
# Date:
# Author:
# Email:
# Website:
# Description:
# ------------------------------------------
" >>$1
chmod +x createshXX.sh
vim $1
3、修改脚本要进入vim createshXX.sh
4、./jiaozuoyeXX.sh createshXX.sh
此处>>表示追加
3、编写脚本/root/bin/sumid.sh,计算/etc/passwd文件中的第10个用户和第20用户的ID之和
1、./createsh36.sh(相当于一个命令) /root/bin/sumid.sh 进入编辑界面
2、#!/bin/bash
#--------------------------------------------------
# Filename:/root/bin/sumid.sh
# Version:1.0
# Date:'date "+%F %T"'
# Author:Lemon.
# Description:ID之和
#--------------------------------------------------
echo "$[$(cat /etc/passwd|head -10|tail -1|cut -d: -f3)+$(cat /etc/passwd|head -20|tail -1|cut -d: -f3)]"
chmod +x /root/bin/sumid.sh
4、cd /root/bin/ 需要进入脚本目录才能查看结果
5、./sumid.sh 查看文件结果
或uid=10 uid=20
#!/bin/bash
Uid1=`head -$1 /etc/passwd|tail -1|cut -d: -f3`
Uid2=`head -$2 /etc/passwd|tail -1|cut -d: -f3`
Sumid=$[Uid1+Uid2]
echo "Sumid is $Sumid"
unset Uid1 Uid2 Sumid
分析:分别表示出第10个用户和第20个用户
4、编写脚本/root/bin/sumfile.sh,统计/etc, /var, /usr目录中共有多少个一级子目录和文件
1、./createsh36.sh sumfile.sh
2、#!/bin/bash
file1=`ls -A /etc|wc -l`
file2=`ls -A /var|wc -l`
file3=`ls -A /usr|wc -l`
let sum=$file1+$file2+$file3
echo "$sum"
unset file1 file2 file3 sum
chmod +x sumfile.sh
3、 ./sumspace.sh /etc /var /usr
注意:要想引用一串命令需要用` `和$( )引用。
5、编写脚本/root/bin/checkdisk.sh,检查磁盘分区空间和inode使用率,如果超过80%,就发广播警告空间将满
diskused_max=`df | grep "/dev/sd"|sort -nr -k5|head -1|tr -s ' ' %|cut -d% -f5`
inodeused_max=`df -i| grep "/dev/sd"|sort -nr -k5|head -1|tr -s ' ' %|cut -d% -f5`
[ "$diskused_max" -gt "80" ] && wall "空间即将满"||echo "空间使用率不超过80%"
[ "$inodeused_max" -gt "80" ] && wall "inode即将满"||echo "inode使用率不超过80%"
unset diskused_max inodeused_max
解释:搜索到磁盘分区中的分区/dev/sd,sort -nr -k5表示取第5列然后按数字从大到小排列,tr -s ' ' %将空格替换成%,再剪切,随后和80比较。
-----------------------答案2---------------------------------------------------
dev=`df|grep "/dev/sd"|egrep -o "[0-9]{1,3}%"|sort -n|tail -n 1|cut -d% -f1`
ino=`df -i|egrep -o "[0-9]{1,3}%" |sort -n|tail -n 1|cut -d% -f1`
[[ "$dev" -gt 80 ]] || [[ "$ino" -gt 80 ]] && echo $(wall 磁盘已满)||echo 还有很多利用空
间呦
unset dev
unset ino
解释:egrep -o 只显示被模式匹配的字串,[0-9]{1,3}%表示在空间使用率这里前面的字符匹配至少1次,最多3次,随后剪切,排序。这里的比较用或表示,只要有一个满了,另一个不用比较,就能发出警报。