一、shell脚本介绍

1.1 开头(环境使用shebang机制)

#!/bin/bash 必须写在文件首行

符号#!用来告诉系统它后面的参数是用来执行该文件的程序。

当编辑好脚本时,如果要执行该脚本,还必须使其可执行。

要使脚本可执行:

编译 chmod +x filename 这样才能用./filename或source filename 来运行。

1.2 注释

在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我建议在程序中使用注释。如果使用了注释,那么即使相当长的时间内没有使用该脚本,我们也能在很短的时间内明白该脚本的作用及工作原理。

1.3格式


Shell脚本基础入门_shell脚本

脚本中如果有语法错误可以用来:  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 变量定义

所有的变量都由字符串组成,用一个字符串,替代更多更复杂的内容 ,并且您不需要对变量进行声明。在变量使用命令时需要加` ` 或者$( ).

赋值给一个变量,并打印出内容如下:


Shell脚本基础入门_shell脚本_02

有时候变量名很容易与其他文字混淆,比如:

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 —清空所有位置变量



Shell脚本基础入门_shell脚本_03

小总结:变量内容若有空格符可使用双引号"  "或者单引号'  '将变量内容结合起来:

a.双引号内的特殊字符如$等,可以保持原本的特性,如:


Shell脚本基础入门_shell脚本_04

b.单引号内的特殊字符则仅为一般字符(纯文本),如:


Shell脚本基础入门_shell脚本_05

c.可用转义字符“\”将特殊符号(如$、\、!)变成一般字符。如


Shell脚本基础入门_shell脚本_06

d.在一串命令中,还需要通过其他的命令提供的信息,可以使用反单引号“`命令`”或者“$(命令)”,如:


Shell脚本基础入门_shell脚本_07

e.若该变量为了增加变量内容时,则可用“$变量名称”或${变量}累加内容,如:给变量PATH增加内容,不能直接用”PATH=内容“,这样会覆盖掉原本的变量值,应该用“PATH=$PATH:内容”.


Shell脚本基础入门_shell脚本_08

f.若该变量需要在其他子进程(子进程:在目前这个shell的情况下,去打开另一个新的shell,新的shell就是子进程)执行,则需要以export来使变量变成环境变量。如:


Shell脚本基础入门_shell脚本_09

g.取消变量的方法为使用“unset变量名称”,如:


Shell脚本基础入门_shell脚本_10

三、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


Shell脚本基础入门_shell脚本_11

没有重新登陆时,这几个变量就不会生效,echo $A   $B $C $D $E,什么都不显示。重新登陆后,这几个变量就会生效。


Shell脚本基础入门_shell脚本_12

用非交互式登陆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的值。


Shell脚本基础入门_shell脚本_13

在root用户下,将变量A的值改为A6,变量B的值改为B7,变量D的值改为D9,变量E的值改为E10


Shell脚本基础入门_shell脚本_14

然后不退出重新登陆,直接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.


Shell脚本基础入门_shell脚本_15


Shell脚本基础入门_shell脚本_16

在用交互式登陆guanyu用户,echo这几个变量,不继承上一个shell,只读取文件,因为是交互式登陆,所以/etc/profile,/etc/profile.d/mage.sh,/etc/bashrc这几个文件都能读取,所以变量A,B,E的值为A6,B7,E10,又因为变量C,D都在root用户的家目录里,所以不能读取,变量C,D的值为空。


Shell脚本基础入门_shell脚本_17

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)


Shell脚本基础入门_shell脚本_18

4.4  测试字符串


Shell脚本基础入门_shell脚本_19

特别注意:字符串必须要用双引号引起来。字符串比较,比较符号两端必须有空格。

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次,随后剪切,排序。这里的比较用或表示,只要有一个满了,另一个不用比较,就能发出警报。