shell基本语法


一、文本处理工具

1. grep工具(重点)

grep是**==行==**过滤工具;用于根据关键字进行行过滤

语法和选项

语法:

# grep [选项] '关键字' 文件名

常见选项:

OPTIONS:
    -i: 不区分大小写
    -v: 查找不包含指定内容的行,反向选择
    -w: 按单词搜索
    -o: 打印匹配关键字
    -c: 统计匹配到的行数
    -n: 显示行号
    -r: 逐层遍历目录查找
    -A: 显示匹配行及后面多少行    
    -B: 显示匹配行及前面多少行
    -C: 显示匹配行前后多少行
    -l:只列出匹配的文件名
    -L:列出不匹配的文件名
    -e: 使用正则匹配
    -E:使用扩展正则匹配
    ^key:以关键字开头
    key$:以关键字结尾
    ^$:匹配空行
    --color=auto :可以将找到的关键词部分加上颜色的显示

颜色显示(别名设置):

临时设置:
# alias grep='grep --color=auto'            //只针对当前终端和当前用户生效

永久设置:
1)全局(针对所有用户生效)
vim /etc/bashrc
alias grep='grep --color=auto'
source /etc/bashrc

2)局部(针对具体的某个用户)
vim ~/.bashrc
alias grep='grep --color=auto'
source ~/.bashrc

举例说明:

==说明:不要直接使用/etc/passwd文件,将其拷贝到/tmp下做实验!==

# grep -i root passwd                        忽略大小写匹配包含root的行
# grep -w ftp passwd                         精确匹配ftp单词
# grep -w hello passwd                         精确匹配hello单词;自己添加包含hello的行到文件
# grep -wo ftp passwd                         打印匹配到的关键字ftp
# grep -n root passwd                         打印匹配到root关键字的行好
# grep -ni root passwd                         忽略大小写匹配统计包含关键字root的行
# grep -nic root passwd                        忽略大小写匹配统计包含关键字root的行数
# grep -i ^root passwd                         忽略大小写匹配以root开头的行
# grep bash$ passwd                             匹配以bash结尾的行
# grep -n ^$ passwd                             匹配空行并打印行号
# grep ^# /etc/vsftpd/vsftpd.conf        匹配以#号开头的行
# grep -v ^# /etc/vsftpd/vsftpd.conf    匹配不以#号开头的行
# grep -A 5 mail passwd                      匹配包含mail关键字及其后5行
# grep -B 5 mail passwd                      匹配包含mail关键字及其前5行
# grep -C 5 mail passwd                     匹配包含mail关键字及其前后5行

2. cut工具(重点)

cut是**==列==**截取工具,用于列的截取

语法和选项

语法:

# cut 选项  文件名

常见选项:

-c:    以字符为单位进行分割,截取
-d:    自定义分隔符,默认为制表符\t
-f:    与-d一起使用,指定截取哪个区域

举例说明:

# cut -d: -f1 1.txt             以:冒号分割,截取第1列内容
# cut -d: -f1,6,7 1.txt     以:冒号分割,截取第1,6,7列内容
# cut -c4 1.txt                 截取文件中每行第4个字符
# cut -c1-4 1.txt             截取文件中每行的1-4个字符
# cut -c4-10 1.txt             截取文件中每行的4-10个字符
# cut -c5- 1.txt                 从第5个字符开始截取后面所有字符

课堂练习:
用小工具列出你当系统的运行级别。5/3

  1. 如何查看系统运行级别
    • 命令runlevel
    • 文件/etc/inittab
  2. 如何过滤运行级别
runlevel |cut -c3
runlevel | cut -d ' ' -f2
grep -v '^#' /etc/inittab | cut -d: -f2
grep '^id' /etc/inittab |cut -d: -f2
grep "initdefault:$" /etc/inittab | cut -c4
grep -v ^# /etc/inittab |cut -c4
grep 'id:' /etc/inittab |cut -d: -f2
cut -d':' -f2 /etc/inittab |grep -v ^#
cut -c4 /etc/inittab |tail -1
cut -d: -f2 /etc/inittab |tail -1

3. sort工具

sort工具用于排序;它将文件的每一行作为一个单位,从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出。

语法和选项

-u :去除重复行
-r :降序排列,默认是升序
-o : 将排序结果输出到文件中,类似重定向符号>
-n :以数字排序,默认是按字符排序
-t :分隔符
-k :第N列
-b :忽略前导空格。
-R :随机排序,每次运行的结果均不同

举例说明

# sort -n -t: -k3 1.txt             按照用户的uid进行升序排列
# sort -nr -t: -k3 1.txt             按照用户的uid进行降序排列
# sort -n 2.txt                         按照数字排序
# sort -nu 2.txt                         按照数字排序并且去重
# sort -nr 2.txt 
# sort -nru 2.txt 
# sort -nru 2.txt 
# sort -n 2.txt -o 3.txt             按照数字排序并将结果重定向到文件
# sort -R 2.txt 
# sort -u 2.txt 

##4.uniq工具

uniq用于去除**==连续====重复==**行

常见选项:
-i: 忽略大小写
-c: 统计重复行次数
-d:只显示重复行

举例说明:
# uniq 2.txt 
# uniq -d 2.txt 
# uniq -dc 2.txt 

5.tee工具

tee工具是从标准输入读取并写入到标准输出和文件,即:双向覆盖重定向(屏幕输出|文本输入)

选项:
-a 双向追加重定向

# echo hello world
# echo hello world|tee file1
# cat file1 
# echo 999|tee -a file1
# cat file1 

6.diff工具

diff工具用于逐行比较文件的不同

注意:diff描述两个文件不同的方式是告诉我们==怎样改变第一个==文件之后==与第二个文件匹配==。

语法和选项

语法:

diff [选项] 文件1 文件2

常用选项:

选项 含义 备注
-b 不检查空格
-B 不检查空白行
-i 不检查大小写
-w 忽略所有的空格
–normal 正常格式显示(默认)
-c 上下文格式显示
-u 合并格式显示

举例说明:

  • 比较两个==普通文件==异同,文件准备:
[root@MissHou ~]# cat file1
aaaa
111
hello world
222
333
bbb
[root@MissHou ~]#
[root@MissHou ~]# cat file2
aaa
hello
111
222
bbb
333
world

1)正常显示

diff目的:file1如何改变才能和file2匹配
[root@MissHou ~]# diff file1 file2
1c1,2                    第一个文件的第1行需要改变(c=change)才能和第二个文件的第1到2行匹配            
< aaaa                小于号"<"表示左边文件(file1)文件内容
---                    ---表示分隔符
> aaa                    大于号">"表示右边文件(file2)文件内容
> hello
3d3                    第一个文件的第3行删除(d=delete)后才能和第二个文件的第3行匹配
< hello world
5d4                    第一个文件的第5行删除后才能和第二个文件的第4行匹配
< 333
6a6,7                    第一个文件的第6行增加(a=add)内容后才能和第二个文件的第6到7行匹配
> 333                    需要增加的内容在第二个文件里是333和world
> world

2)上下文格式显示

[root@MissHou ~]# diff -c file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号***表示file1,---表示file2
*** file1       2019-04-16 16:26:05.748650262 +0800
--- file2       2019-04-16 16:26:30.470646030 +0800
***************    我是分隔符
*** 1,6 *******开头表示file1文件,1,6表示1到6行
! aaaa                !表示该行需要修改才与第二个文件匹配
  111
- hello world        -表示需要删除该行才与第二个文件匹配
  222
- 333                    -表示需要删除该行才与第二个文件匹配
  bbb
--- 1,7 -------开头表示file2文件,1,7表示1到7行
! aaa                    表示第一个文件需要修改才与第二个文件匹配
! hello                表示第一个文件需要修改才与第二个文件匹配
  111
  222
  bbb
+ 333                    表示第一个文件需要加上该行才与第二个文件匹配
+ world                表示第一个文件需要加上该行才与第二个文件匹配

3)合并格式显示

[root@MissHou ~]# diff -u file1 file2
前两行主要列出需要比较的文件名和文件的时间戳;文件名前面的符号---表示file1,+++表示file2
--- file1       2019-04-16 16:26:05.748650262 +0800
+++ file2       2019-04-16 16:26:30.470646030 +0800
@@ -1,6 +1,7 @@
-aaaa
+aaa
+hello
 111
-hello world
 222
-333
 bbb
+333
+world
  • 比较两个==目录不同==
默认情况下也会比较两个目录里相同文件的内容
[root@MissHou  tmp]# diff dir1 dir2
diff dir1/file1 dir2/file1
0a1
> hello
Only in dir1: file3
Only in dir2: test1
如果只需要比较两个目录里文件的不同,不需要进一步比较文件内容,需要加-q选项
[root@MissHou  tmp]# diff -q dir1 dir2
Files dir1/file1 and dir2/file1 differ
Only in dir1: file3
Only in dir2: test1

其他小技巧:

有时候我们需要以一个文件为标准,去修改其他文件,并且修改的地方较多时,我们可以通过打补丁的方式完成。

1)先找出文件不同,然后输出到一个文件
[root@MissHou ~]# diff -uN file1 file2 > file.patch
-u:上下文模式
-N:将不存在的文件当作空文件
2)将不同内容打补丁到文件
[root@MissHou ~]# patch file1 file.patch
patching file file1
3)测试验证
[root@MissHou ~]# diff file1 file2
[root@MissHou ~]#

7. paste工具

paste工具用于合并文件行

常用选项:
-d:自定义间隔符,默认是tab
-s:串行处理,非并行

8. tr工具

tr用于字符转换,替换和删除;主要用于==删除文件中控制字符==或进行==字符转换==

语法和选项

语法:

用法1:命令的执行结果交给tr处理,其中string1用于查询,string2用于转换处理

 commands|tr  'string1'  'string2'

用法2:tr处理的内容来自文件,记住要使用"<"标准输入

 tr  'string1'  'string2' < filename

用法3:匹配string1进行相应操作,如删除操作

 tr [options] 'string1' < filename

常用选项:

-d 删除字符串1中所有输入字符。
-s 删除所有重复出现字符序列,只保留第一个;即将重复出现字符串压缩为一个字符串

常匹配字符串:

字符串 含义 备注
==a-z==或[:lower:] 匹配所有小写字母 所有大小写和数字[a-zA-Z0-9]
==A-Z==或[:upper:] 匹配所有大写字母
==0-9==或[:digit:] 匹配所有数字
[:alnum:] 匹配所有字母和数字
[:alpha:] 匹配所有字母
[:blank:] 所有水平空白
[:punct:] 匹配所有标点符号
[:space:] 所有水平或垂直的空格
[:cntrl:] 所有控制字符 \f Ctrl-L 走行换页
\n Ctrl-J 换行
\r Ctrl-M 回车
\t Ctrl-I tab键

举例说明:

[root@MissHou  shell01]# cat 3.txt     自己创建该文件用于测试
ROOT:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
boss02:x:516:511::/home/boss02:/bin/bash
vip:x:517:517::/home/vip:/bin/bash
stu1:x:518:518::/home/stu1:/bin/bash
mailnull:x:47:47::/var/spool/mqueue:/sbin/nologin
smmsp:x:51:51::/var/spool/mqueue:/sbin/nologin
aaaaaaaaaaaaaaaaaaaa
bbbbbb111111122222222222233333333cccccccc
hello world 888
666
777
999


# tr -d '[:/]' < 3.txt                 删除文件中的:和/
# cat 3.txt |tr -d '[:/]'            删除文件中的:和/
# tr '[0-9]' '@' < 3.txt             将文件中的数字替换为@符号
# tr '[a-z]' '[A-Z]' < 3.txt         将文件中的小写字母替换成大写字母
# tr -s '[a-z]' < 3.txt             匹配小写字母并将重复的压缩为一个
# tr -s '[a-z0-9]' < 3.txt         匹配小写字母和数字并将重复的压缩为一个
# tr -d '[:digit:]' < 3.txt         删除文件中的数字
# tr -d '[:blank:]' < 3.txt         删除水平空白
# tr -d '[:space:]' < 3.txt         删除所有水平和垂直空白

小试牛刀

  1. 使用小工具分别截取当前主机IP;截取NETMASK;截取广播地址;截取MAC地址
# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4
10.1.1.1:10.1.1.255:255.255.255.0
# ifconfig eth0|grep 'Bcast'|tr -d '[a-zA-Z ]'|cut -d: -f2,3,4|tr ':' '\n'
10.1.1.1
10.1.1.255
255.255.255.0
# ifconfig eth0|grep 'HWaddr'|cut -d: -f2-|cut -d' ' -f4
00:0C:29:25:AE:54
# ifconfig eth0|grep 'HW'|tr -s ' '|cut -d' ' -f5
00:0C:29:B4:9E:4E

# ifconfig eth1|grep Bcast|cut -d: -f2|cut -d' ' -f1
# ifconfig eth1|grep Bcast|cut -d: -f2|tr -d '[ a-zA-Z]'
# ifconfig eth1|grep Bcast|tr -d '[:a-zA-Z]'|tr ' ' '@'|tr -s '@'|tr '@' '\n'|grep -v ^$
# ifconfig eth0|grep 'Bcast'|tr -d [:alpha:]|tr '[ :]' '\n'|grep -v ^$
# ifconfig eth1|grep HWaddr|cut -d ' ' -f11
# ifconfig eth0|grep HWaddr|tr -s ' '|cut -d' ' -f5
# ifconfig eth1|grep HWaddr|tr -s ' '|cut -d' ' -f5
# ifconfig eth0|grep 'Bcast'|tr -d 'a-zA-Z:'|tr ' ' '\n'|grep -v '^$'
  1. 将系统中所有普通用户的用户名、密码和默认shell保存到一个文件中,要求用户名密码和默认shell之间用tab键分割
# grep 'bash$' passwd |grep -v 'root'|cut -d: -f1,2,7|tr ':' '\t' |tee abc.txt

二、bash的特性

1、命令和文件自动补全

Tab只能补全==命令和文件== (RHEL6/Centos6)

2、常见的快捷键

^c               终止前台运行的程序
^z                  将前台运行的程序挂起到后台
^d               退出 等价exit
^l               清屏 
^a |home      光标移到命令行的最前端
^e |end      光标移到命令行的后端
^u               删除光标前所有字符
^k               删除光标后所有字符
^r                 搜索历史命令

3 、常用的通配符(重点)

*:    匹配0或多个任意字符
?:    匹配任意单个字符
[list]:    匹配[list]中的任意单个字符,或者一组单个字符   [a-z]
[!list]: 匹配除list中的任意单个字符
{string1,string2,...}:匹配string1,string2或更多字符串


# rm -f file*
# cp *.conf  /dir1
# touch file{1..5}

4、bash的引号(重点)

  • 双引号”” :会把引号的内容当成整体来看待,允许通过$符号引用其他变量值
  • 单引号’ ‘ :会把引号的内容当成整体来看待,禁止引用其他变量值,shell中特殊符号都被视为普通字符
  • 反撇号`` :反撇号和$()一样,引号或括号里的命令会优先执行,如果存在嵌套,反撇号不能用
[root@MissHou  dir1]# echo "$(hostname)"
server
[root@MissHou  dir1]# echo '$(hostname)'
$(hostname)
[root@MissHou  dir1]# echo "hello world"
hello world
[root@MissHou  dir1]# echo 'hello world'
hello world

[root@MissHou  dir1]# echo $(date +%F)
2018-11-22
[root@MissHou  dir1]# echo `echo $(date +%F)`
2018-11-22
[root@MissHou  dir1]# echo `date +%F`
2018-11-22
[root@MissHou  dir1]# echo `echo `date +%F``
date +%F
[root@MissHou  dir1]# echo $(echo `date +%F`)
2018-11-22

三、SHELL介绍

1. 编程语言分类

  • 编译型语言:

​ ==程序在执行之前需要一个专门的编译过程==,把程序编译成为机器语言文件,运行时不需要重新翻译,直接使用编译的结果就行了。程序执行效率高,依赖编译器,跨平台性差些。如C、C++

  • 解释型语言:

​ 程序不需要编译,程序在运行时由**==解释器==**翻译成机器语言,每执行一次都要翻译一次。因此效率比较低。比如Python/JavaScript/ Perl /ruby/==Shell==等都是解释型语言。

  • 总结

编译型语言比解释型语言==速度较快==,但是不如解释型语言==跨平台性好==。如果做底层开发或者大型应用程序或者操作系开发一==般都用编译型语言==;如果是一些服务器脚本及一些辅助的接口,对速度要求不高、对各个平台的==兼容性有要求==的话则一般都用==解释型语言==。

2. shell简介

总结:

  • ==shell就是人机交互的一个桥梁==
  • shell的种类
[root@MissHou ~]# cat /etc/shells 
/bin/sh                #是bash的一个快捷方式
/bin/bash            #bash是大多数Linux默认的shell,包含的功能几乎可以涵盖shell所有的功能
/sbin/nologin        #表示非交互,不能登录操作系统
/bin/dash            #小巧,高效,功能相比少一些

/bin/csh                #具有C语言风格的一种shell,具有许多特性,但也有一些缺陷
/bin/tcsh            #是csh的增强版,完全兼容csh

3. shell脚本

㈠ 什么是shell脚本?

  • 一句话概括

简单来说就是将==需要执行的命令==保存到文本中,==按照顺序执行==。它是解释型的,意味着不需要编译。

  • 准确叙述

若干命令 + 脚本的基本格式 + 脚本特定语法 + 思想= shell脚本

㈡ 什么时候用到脚本?

重复化、复杂化的工作,通过把工作的命令写成脚本,以后仅仅需要执行脚本就能完成这些工作。

㈢ shell脚本能干啥?

①自动化软件部署 LAMP/LNMP/Tomcat…

②自动化管理 系统初始化脚本、批量更改主机密码、推送公钥…

③==自动化分析处理== 统计网站访问量

④==自动化备份== 数据库备份、日志转储…

⑤自动化监控脚本

㈣ shell脚本的基本写法

1)脚本第一行,魔法字符==#!==指定解释器【==必写==】

#!/bin/bash 表示以下内容使用bash解释器解析

==注意:==
如果直接将解释器路径写死在脚本里,可能在某些系统就会存在找不到解释器的兼容性问题,所以可以使用:==#!/bin/env 解释器== ==#!/bin/env bash==

2)脚本第二部分,注释(#号)说明,对脚本的基本信息进行描述【可选】

#!/bin/env bash

# 以下内容是对脚本的基本信息的描述
# Name: 名字
# Desc:描述describe
# Path:存放路径
# Usage:用法
# Update:更新时间

#下面就是脚本的具体内容
commands
...

3)脚本第三部分,脚本要实现的具体代码内容

㈤ shell脚本的执行方法

  • 标准脚本执行方法(建议)
1) 编写人生第一个shell脚本
[root@MissHou shell01]# cat first_shell.sh
#!/bin/env bash

# 以下内容是对脚本的基本信息的描述
# Name: first_shell.sh
# Desc: num1
# Path: /shell01/first_shell.sh
# Usage:/shell01/first_shell.sh
# Update:2019-05-05

echo "hello world"
echo "hello world"
echo "hello world"

2) 脚本增加可执行权限
[root@MissHou shell01]# chmod +x first_shell.sh

3) 标准方式执行脚本
[root@MissHou shell01]# pwd
/shell01
[root@MissHou shell01]# /shell01/first_shell.sh
或者
[root@MissHou shell01]# ./first_shell.sh

注意:标准执行方式脚本必须要有可执行权限。
  • 非标准的执行方法(不建议)
  1. 直接在命令行指定解释器执行
[root@MissHou shell01]# bash first_shell.sh
[root@MissHou shell01]# sh first_shell.sh
[root@MissHou shell01]# bash -x first_shell.sh
+ echo 'hello world'
hello world
+ echo 'hello world'
hello world
+ echo 'hello world'
hello world
----------------
-x:一般用于排错,查看脚本的执行过程
-n:用来查看脚本的语法是否有问题
------------
  1. 使用source命令读取脚本文件,执行文件里的代码
[root@MissHou shell01]# source first_shell.sh
hello world
hello world
hello world

四、变量的定义

1.变量如何定义?

变量名=====变量值

变量名:用来临时保存数据的

变量值:就是临时的可变化的数据

[root@MissHou ~]# A=hello            定义变量A
[root@MissHou ~]# echo $A            调用变量A,要给钱的,不是人民币是美元"$"
hello
[root@MissHou ~]# echo ${A}        还可以这样调用,不管你的姿势多优雅,总之要给钱
hello
[root@MissHou ~]# A=world            因为是变量所以可以变,移情别恋是常事
[root@MissHou ~]# echo $A            不管你是谁,只要调用就要给钱
world
[root@MissHou ~]# unset A            不跟你玩了,取消变量
[root@MissHou ~]# echo $A            从此,我单身了,你可以给我介绍任何人

2. 变量的定义规则

虽然可以给变量(变量名)赋予任何值;但是,对于==变量名==也是要求的!:unamused:

㈠ 变量名区分大小写

[root@MissHou ~]# A=hello
[root@MissHou ~]# a=world
[root@MissHou ~]# echo $A
hello
[root@MissHou ~]# echo $a
world

㈡ 变量名不能有特殊符号

[root@MissHou ~]# *A=hello
-bash: *A=hello: command not found
[root@MissHou ~]# ?A=hello
-bash: ?A=hello: command not found
[root@MissHou ~]# @A=hello
-bash: @A=hello: command not found

特别说明:对于有空格的字符串给变量赋值时,要用引号引起来
[root@MissHou ~]# A=hello world
-bash: world: command not found
[root@MissHou ~]# A="hello world"
[root@MissHou ~]# A='hello world'

㈢ 变量名不能以数字开头

[root@MissHou ~]# 1A=hello
-bash: 1A=hello: command not found
[root@MissHou ~]# A1=hello
注意:不能以数字开头并不代表变量名中不能包含数字呦。

㈣ 等号两边不能有任何空格

[root@MissHou ~]# A =123
-bash: A: command not found
[root@MissHou ~]# A= 123
-bash: 123: command not found
[root@MissHou ~]# A = 123
-bash: A: command not found
[root@MissHou ~]# A=123
[root@MissHou ~]# echo $A
123

3. 变量的定义方式

㈠ 基本方式

直接赋值给一个变量

[root@MissHou ~]# A=1234567
[root@MissHou ~]# echo $A
1234567
[root@MissHou ~]# echo ${A:2:4}        表示从A变量中第3个字符开始截取,截取4个字符
3456

说明:
$变量名 和 ${变量名}的异同
相同点:都可以调用变量
不同点:${变量名}可以只截取变量的一部分,而$变量名不可以

㈡ 命令执行结果赋值给变量

[root@MissHou ~]# B=`date +%F`
[root@MissHou ~]# echo $B
2019-04-16
[root@MissHou ~]# C=$(uname -r)
[root@MissHou ~]# echo $C
2.6.32-696.el6.x86_64

㈢ 交互式定义变量(read)

目的:让==用户自己==给变量赋值,比较灵活。

语法:read [选项] 变量名

常见选项:

选项 释义
-p 定义提示用户的信息
-n 定义字符数(限制变量值的长度)
-s 不显示(不显示用户输入的内容)
-t 定义超时时间,默认单位为秒(限制用户输入变量值的超时时间)

举例说明:

用法1:用户自己定义变量值
[root@MissHou ~]# read name
harry
[root@MissHou ~]# echo $name
harry
[root@MissHou ~]# read -p "Input your name:" name
Input your name:tom
[root@MissHou ~]# echo $name
tom

用法2:变量值来自文件

[root@MissHou ~]# cat 1.txt 
10.1.1.1 255.255.255.0

[root@MissHou ~]# read ip mask < 1.txt 
[root@MissHou ~]# echo $ip
10.1.1.1
[root@MissHou ~]# echo $mask
255.255.255.0

㈣ 定义有类型的变量(declare)

目的: 给变量做一些限制,固定变量的类型,比如:整型、只读

用法:declare 选项 变量名=变量值

常用选项:

选项 释义 举例
-i 将变量看成整数 declare -i A=123
-r 定义只读变量 declare -r B=hello
-a 定义普通数组;查看普通数组
-A 定义关联数组;查看关联数组
-x 将变量通过环境导出 declare -x AAA=123456 等于 export AAA=123456

举例说明:

[root@MissHou ~]# declare -i A=123
[root@MissHou ~]# echo $A
123
[root@MissHou ~]# A=hello
[root@MissHou ~]# echo $A
0

[root@MissHou ~]# declare -r B=hello
[root@MissHou ~]# echo $B
hello
[root@MissHou ~]# B=world
-bash: B: readonly variable
[root@MissHou ~]# unset B
-bash: unset: B: cannot unset: readonly variable

㈤变量的拼接

不需要加 + 号

a=rewind
echo $a  "hello"
rewind hello

4. 变量的分类

㈠ 本地变量

  • **==本地变量==**:当前用户自定义的变量。当前进程中有效,其他进程及当前进程的子进程无效。

㈡ 环境变量

  • 环境变量:当前进程有效,并且能够被子进程调用。
    • env查看当前用户的环境变量
    • set==查询当前用户的所有变量(临时变量与环境变量)==
    • export 变量名=变量值 或者 变量名=变量值;export 变量名
[root@MissHou ~]# export A=hello        临时将一个本地变量(临时变量)变成环境变量
[root@MissHou ~]# env|grep ^A
A=hello

永久生效:
vim /etc/profile 或者 ~/.bashrc
export A=hello
或者
A=hello
export A

说明:系统中有一个变量PATH,环境变量
export PATH=/usr/local/mysql/bin:$PATH

㈢ 全局变量

  • 全局变量:全局所有的用户和程序都能调用,且继承,新建的用户也默认能调用.

  • 解读相关配置文件

文件名 说明 备注
$HOME/.bashrc 当前用户的bash信息,用户==登录==时读取 定义别名、umask、函数等
$HOME/.bash_profile 当前用户的环境变量,用户==登录==时读取
$HOME/.bash_logout 当前用户==退出==当前shell时最后读取 定义用户退出时执行的程序等
/etc/bashrc 全局的bash信息,所有用户都生效
/etc/profile 全局环境变量信息 系统和所有用户都生效
$HOME/.bash_history 用户的历史命令 history -w 保存历史记录 history -c 清空历史记录

说明:以上文件修改后,都需要重新==source==让其生效或者退出重新登录。

  • 用户登录系统读取相关==文件的顺序==
    1. /etc/profile
    2. $HOME/.bash_profile
    3. $HOME/.bashrc
    4. /etc/bashrc
    5. $HOME/.bash_logout

㈣ ==系统变量==

  • 系统变量(内置bash中变量) : shell本身已经固定好了它的名字和作用.
$? 上一条命令执行后返回的状态;状态值为0表示执行正常,==非0==表示执行异常或错误
$0 当前执行的程序或脚本名
$# 脚本后面接的参数的==个数==
$* 脚本后面==所有参数==,参数当成一个整体输出,每一个变量参数之间以空格隔开
$@ 脚本后面==所有参数==,参数是独立的,也是全部输出
$1~$9 脚本后面的==位置参数==,$1表示第1个位置参数,依次类推
${10}~${n} 扩展位置参数,第10个位置变量必须用{}大括号括起来(2位数字以上扩起来)
$$ 当前所在进程的进程号,如echo $$
$! 后台运行的最后一个进程号 (当前终端)
!$ 调用最后一条命令历史中的==参数==
  • 进一步了解位置参数$1~${n}
#!/bin/bash
#了解shell内置变量中的位置参数含义
echo "\$0 = $0"
echo "\$# = $#"
echo "\$* = $*"
echo "\$@ = $@"
echo "\$1 = $1" 
echo "\$2 = $2" 
echo "\$3 = $3" 
echo "\$11 = ${11}" 
echo "\$12 = ${12}" 
  • 进一步了解$*和​$@的区别

$*:表示将变量看成一个整体
$@:表示变量是独立的

#!/bin/bash
for i in "$@"
do
echo $i
done

echo "======我是分割线======="

for i in "$*"
do
echo $i
done

[root@MissHou ~]# bash 3.sh a b c
a
b
c
======我是分割线=======
a b c

五、简单四则运算

算术运算:默认情况下,shell就只能支持简单的==整数==运算

运算内容:加(+)、减(-)、乘(*)、除(/)、求余数(%)

1. 四则运算符号

表达式 举例
$(( )) echo $((1+1))
$[ ] echo $[10-5]
expr expr 10 / 5
let n=1;let n+=1 等价于 let n=n+1

2.了解i++和++i

  • 对变量的值的影响
[root@MissHou ~]# i=1
[root@MissHou ~]# let i++
[root@MissHou ~]# echo $i
2
[root@MissHou ~]# j=1
[root@MissHou ~]# let ++j
[root@MissHou ~]# echo $j
2
  • 对==表达式==的值的影响
[root@MissHou ~]# unset i j
[root@MissHou ~]# i=1;j=1
[root@MissHou ~]# let x=i++         先赋值,再运算
[root@MissHou ~]# let y=++j         先运算,再赋值
[root@MissHou ~]# echo $i
2
[root@MissHou ~]# echo $j
2
[root@MissHou ~]# echo $x
1
[root@MissHou ~]# echo $y
2

六、数组、其他类型

1. 数组定义

㈠ 数组分类

  • 普通数组:只能使用整数作为数组索引(元素的下标)
  • 关联数组:可以使用字符串作为数组索引(元素的下标)

㈡ 普通数组定义

  • 一次赋予一个值
数组名[索引下标]=值
array[0]=v1
array[1]=v2
array[2]=v3
array[3]=v4
  • 一次赋予多个值
数组名=(值1 值2 值3 ...)
array=(var1 var2 var3 var4)

array1=(`cat /etc/passwd`)            将文件中每一行赋值给array1数组
array2=(`ls /root`)
array3=(harry amy jack "Miss Hou")
array4=(1 2 3 4 "hello world" [10]=linux)

㈢ 数组的读取

${数组名[元素下标]}

echo ${array[0]}            获取数组里第一个元素
echo ${array[*]}            获取数组里的所有元素
echo ${#array[*]}            获取数组里所有元素个数
echo ${!array[@]}        获取数组元素的索引下标
echo ${array[@]:1:2}    访问指定的元素;1代表从下标为1的元素开始获取;2代表获取后面几个元素

查看普通数组信息:
[root@MissHou ~]# declare -a

㈣ 关联数组定义

①首先声明关联数组

==declare -A asso_array1==

declare -A asso_array1
declare -A asso_array2
declare -A asso_array3

② 数组赋值

  • 一次赋一个值
数组名[索引or下标]=变量值
# asso_array1[linux]=one
# asso_array1[java]=two
# asso_array1[php]=three
  • 一次赋多个值
# asso_array2=([name1]=harry [name2]=jack [name3]=amy [name4]="Miss Hou")
  • 查看关联数组
# declare -A
declare -A asso_array1='([php]="three" [java]="two" [linux]="one" )'
declare -A asso_array2='([name3]="amy" [name2]="jack" [name1]="harry" [name4]="Miss Hou" )'
  • 获取关联数组值
# echo ${asso_array1[linux]}
one
# echo ${asso_array1[php]}
three
# echo ${asso_array1[*]}
three two one
# echo ${!asso_array1[*]}
php java linux
# echo ${#asso_array1[*]}
3
# echo ${#asso_array2[*]}
4
# echo ${!asso_array2[*]}
name3 name2 name1 name4
  • 其他定义方式
[root@MissHou shell05]# declare -A books
[root@MissHou shell05]# let books[linux]++
[root@MissHou shell05]# declare -A|grep books
declare -A books='([linux]="1" )'
[root@MissHou shell05]# let books[linux]++
[root@MissHou shell05]# declare -A|grep books
declare -A books='([linux]="2" )'

2. 其他变量定义

  • 取出一个目录下的目录和文件:dirnamebasename
# A=/root/Desktop/shell/mem.txt 
# echo $A
/root/Desktop/shell/mem.txt
# dirname $A   取出目录
/root/Desktop/shell
# basename $A  取出文件
mem.txt
  • -==变量”内容”的删除和替换==
一个“%”代表从右往左删除
两个“%%”代表从右往左去掉最多
一个“#”代表从左往右去掉删除
两个“##”代表从左往右去掉最多

举例说明:
# url=www.taobao.com
# echo ${#url}             获取变量的长度
# echo ${url#*.}
# echo ${url##*.}
# echo ${url%.*}
# echo ${url%%.*}
  • 以下了解,自己完成
替换:///
 1015  echo ${url/ao/AO}  用AO代替ao(从左往右第一个)
 1017  echo ${url//ao/AO}   贪婪替换(替代所有)

替代: - 和 :-  +和:+
 1019  echo ${abc-123}
 1020  abc=hello
 1021  echo ${abc-444}
 1022  echo $abc
 1024  abc=
 1025  echo ${abc-222}

${变量名-新的变量值} 或者 ${变量名=新的变量值}
变量没有被赋值:会使用“新的变量值“ 替代
变量有被赋值(包括空值): 不会被替代

 1062  echo ${ABC:-123}
 1063  ABC=HELLO
 1064  echo ${ABC:-123}
 1065  ABC=
 1066  echo ${ABC:-123}

${变量名:-新的变量值} 或者 ${变量名:=新的变量值}
变量没有被赋值或者赋空值:会使用“新的变量值“ 替代
变量有被赋值: 不会被替代

 1116  echo ${abc=123}
 1118  echo ${abc:=123}

[root@MissHou ~]# unset abc
[root@MissHou ~]# echo ${abc:+123}

[root@MissHou ~]# abc=hello
[root@MissHou ~]# echo ${abc:+123}
123
[root@MissHou ~]# abc=
[root@MissHou ~]# echo ${abc:+123}

${变量名+新的变量值}
变量没有被赋值或者赋空值:不会使用“新的变量值“ 替代
变量有被赋值: 会被替代
[root@MissHou ~]# unset abc
[root@MissHou ~]# echo ${abc+123}

[root@MissHou ~]# abc=hello
[root@MissHou ~]# echo ${abc+123}
123
[root@MissHou ~]# abc=
[root@MissHou ~]# echo ${abc+123}
123
${变量名:+新的变量值}
变量没有被赋值:不会使用“新的变量值“ 替代
变量有被赋值(包括空值): 会被替代

[root@MissHou ~]# unset abc
[root@MissHou ~]# echo ${abc?123}
-bash: abc: 123

[root@MissHou ~]# abc=hello
[root@MissHou ~]# echo ${abc?123}
hello
[root@MissHou ~]# abc=
[root@MissHou ~]# echo ${abc?123}

${变量名?新的变量值}
变量没有被赋值:提示错误信息
变量被赋值(包括空值):不会使用“新的变量值“ 替代

[root@MissHou ~]# unset abc
[root@MissHou ~]# echo ${abc:?123}
-bash: abc: 123
[root@MissHou ~]# abc=hello
[root@MissHou ~]# echo ${abc:?123}
hello
[root@MissHou ~]# abc=
[root@MissHou ~]# echo ${abc:?123}
-bash: abc: 123

${变量名:?新的变量值}
变量没有被赋值或者赋空值时:提示错误信息
变量被赋值:不会使用“新的变量值“ 替代

说明:?主要是当变量没有赋值提示错误信息的,没有赋值功能

七、条件判断语法结构

思考:何为真(==true==)?何为假(==false==)?

1. 条件判断语法格式

  • 格式1: test 条件表达式
  • 格式2: [ 条件表达式 ]
  • 格式3: [[ 条件表达式 ]] 支持正则 =~

特别说明:

1)[ 亲亲,我两边都有空格,不空打死你呦 ]

2)[[ 亲亲,我两边都有空格,不空打死你呦 ]]

  1. 更多判断,man test去查看,很多的参数都用来进行条件判断

2. 条件判断相关参数

问:你要判断什么?

答:我要判断文件类型,判断文件新旧,判断字符串是否相等,判断权限等等…

㈠ 判断文件类型

判断参数 含义
-e 判断文件是否存在(任何类型文件)
-f 判断文件是否存在==并且==是一个普通文件
-d 判断文件是否存在并且是一个目录
-L 判断文件是否存在并且是一个软连接文件
-b 判断文件是否存在并且是一个块设备文件
-S 判断文件是否存在并且是一个套接字文件
-c 判断文件是否存在并且是一个字符设备文件
-p 判断文件是否存在并且是一个命名管道文件
-s 判断文件是否存在并且是一个非空文件(有内容)

举例说明:

test -e file                    只要文件存在条件为真
[ -d /shell01/dir1 ]             判断目录是否存在,存在条件为真
[ ! -d /shell01/dir1 ]        判断目录是否存在,不存在条件为真
[[ -f /shell01/1.sh ]]        判断文件是否存在,并且是一个普通的文件

㈡ 判断文件权限

判断参数 含义
-r 当前用户对其是否可读
-w 当前用户对其是否可写
-x 当前用户对其是否可执行
-u 是否有suid,高级权限冒险位
-g 是否sgid,高级权限强制位
-k 是否有t位,高级权限粘滞位

㈢ 判断文件新旧

说明:这里的新旧指的是==文件的修改时间==。

判断参数 含义
file1 -nt file2 比较file1是否比file2新
file1 -ot file2 比较file1是否比file2旧
file1 -ef file2 比较是否为同一个文件,或者用于判断硬连接,是否指向同一个inode

㈣ 判断整数

判断参数 含义
-eq 相等
-ne 不等
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于

㈤ 判断字符串

判断参数 含义
-z 判断是否为==空==字符串,字符串长度为0则成立
-n 判断是否为==非空==字符串,字符串长度不为0则成立
string1 = string2 判断字符串是否相等
string1 != string2 判断字符串是否相不等

㈥ 多重条件判断

判断符号 含义 举例
-a 和 && 逻辑与 [ 1 -eq 1 -a 1 -ne 0 ] [ 1 -eq 1 ] && [ 1 -ne 0 ]
-o 和 || 逻辑或 [ 1 -eq 1 -o 1 -ne 1 ]

==特别说明:==

&& 前面的表达式==为真==,才会执行后面的代码

|| 前面的表达式==为假==,才会执行后面的代码

; ==只==用于==分割==命令或表达式

① 举例说明

  • 数值比较
[root@server ~]# [ $(id -u) -eq 0 ] && echo "the user is admin"
[root@server ~]$ [ $(id -u) -ne 0 ] && echo "the user is not admin"
[root@server ~]$ [ $(id -u) -eq 0 ] && echo "the user is admin" || echo "the user is not admin"

[root@server ~]# uid=`id -u`
[root@server ~]# test $uid -eq 0 && echo this is admin
this is admin
[root@server ~]# [ $(id -u) -ne 0 ]  || echo this is admin
this is admin
[root@server ~]# [ $(id -u) -eq 0 ]  && echo this is admin || echo this is not admin
this is admin
[root@server ~]# su - stu1
[stu1@server ~]$ [ $(id -u) -eq 0 ]  && echo this is admin || echo this is not admin
this is not admin
  • 类C风格的数值比较
注意:在(( ))中,=表示赋值;==表示判断
[root@server ~]# ((1==2));echo $?
[root@server ~]# ((1<2));echo $?
[root@server ~]# ((2>=1));echo $?
[root@server ~]# ((2!=1));echo $?
[root@server ~]# ((`id -u`==0));echo $?

[root@server ~]# ((a=123));echo $a
[root@server ~]# unset a
[root@server ~]# ((a==123));echo $?
  • 字符串比较
注意:双引号引起来,看作一个整体;= 和 == 在 [ 字符串 ] 比较中都表示判断
[root@server ~]# a='hello world';b=world
[root@server ~]# [ $a = $b ];echo $?
[root@server ~]# [ "$a" = "$b" ];echo $?
[root@server ~]# [ "$a" != "$b" ];echo $?
[root@server ~]# [ "$a" !== "$b" ];echo $?        错误
[root@server ~]# [ "$a" == "$b" ];echo $?
[root@server ~]# test "$a" != "$b";echo $?


test  表达式
[ 表达式 ]
[[ 表达式 ]]

思考:[ ][[ ]] 有什么区别?

[root@server ~]# a=
[root@server ~]# test -z $a;echo $?
[root@server ~]# a=hello
[root@server ~]# test -z $a;echo $?
[root@server ~]# test -n $a;echo $?
[root@server ~]# test -n "$a";echo $?

# [ '' = $a ];echo $?
-bash: [: : unary operator expected
2
# [[ '' = $a ]];echo $?
0


[root@server ~]# [ 1 -eq 0 -a 1 -ne 0 ];echo $?
[root@server ~]# [ 1 -eq 0 && 1 -ne 0 ];echo $?
[root@server ~]# [[ 1 -eq 0 && 1 -ne 0 ]];echo $?

② 逻辑运算符总结

  1. 符号;和&&和||都可以用来分割命令或者表达式
  2. 分号(;)完全不考虑前面的语句是否正确执行,都会执行;号后面的内容
  3. &&符号,需要考虑&&前面的语句的正确性,前面语句正确执行才会执行&&后的内容;反之亦然
  4. ||符号,需要考虑||前面的语句的非正确性,前面语句执行错误才会执行||后内容;反之亦然
  5. 如果&&和||一起出现,从左往右依次看,按照以上原则

八、流程控制语句

1. 基本语法结构

㈠ if结构

F:表示false,为假

T:表示true,为真

if [ condition ];then
        command
        command
fi

if test 条件;then
    命令
fi

if [[ 条件 ]];then
    命令
fi

[ 条件 ] && command

㈡ if…else结构

if [ condition ];then
        command1
    else
        command2
fi

[ 条件 ] && command1 || command2

小试牛刀:

==让用户自己输入==字符串,==如果==用户输入的是hello,请打印world,==否则==打印“请输入hello”

  1. read定义变量
  2. if….else…
#!/bin/env bash

read -p '请输入一个字符串:' str
if [ "$str" = 'hello' ];then
    echo 'world'
 else
    echo '请输入hello!'
fi

  1 #!/bin/env bash
  2
  3 read -p "请输入一个字符串:" str
  4 if [ "$str" = "hello" ]
  5 then
  6     echo world
  7 else
  8     echo "请输入hello!"
  9 fi

  echo "该脚本需要传递参数"
  1 if [ $1 = hello ];then
  2         echo "hello"
  3 else
  4         echo "请输入hello"
  5 fi

#!/bin/env bash

A=hello
B=world
C=hello

if [ "$1" = "$A" ];then
        echo "$B"
    else
        echo "$C"
fi


read -p '请输入一个字符串:' str;
 [ "$str" = 'hello' ] && echo 'world' ||  echo '请输入hello!'

㈢ if…elif…else结构

箴言3:选择很多,能走的只有一条

if [ condition1 ];then
        command1      结束
    elif [ condition2 ];then
        command2       结束
    else
        command3
fi
注释:
如果条件1满足,执行命令1后结束;如果条件1不满足,再看条件2,如果条件2满足执行命令2后结束;如果条件1和条件2都不满足执行命令3结束.

㈣ 层层嵌套结构

if [ condition1 ];then
        command1        
        if [ condition2 ];then
            command2
        fi
 else
        if [ condition3 ];then
            command3
        elif [ condition4 ];then
            command4
        else
            command5
        fi
fi
注释:
如果条件1满足,执行命令1;如果条件2也满足执行命令2,如果不满足就只执行命令1结束;
如果条件1不满足,不看条件2;直接看条件3,如果条件3满足执行命令3;如果不满足则看条件4,如果条件4满足执行命令4;否则执行命令5

2. 应用案例

㈠ 判断两台主机是否ping通

需求:判断==当前主机==是否和==远程主机==是否ping通

① 思路

  1. 使用哪个命令实现 ping -c次数
  2. 根据命令的==执行结果状态==来判断是否通$?
  3. 根据逻辑和语法结构来编写脚本(条件判断或者流程控制)

② 落地实现

#!/bin/env bash
# 该脚本用于判断当前主机是否和远程指定主机互通

# 交互式定义变量,让用户自己决定ping哪个主机
read -p "请输入你要ping的主机的IP:" ip

# 使用ping程序判断主机是否互通
ping -c1 $ip &>/dev/null

if [ $? -eq 0 ];then
    echo "当前主机和远程主机$ip是互通的"
 else
     echo "当前主机和远程主机$ip不通的"
fi

逻辑运算符
test $? -eq 0 &&  echo "当前主机和远程主机$ip是互通的" || echo "当前主机和远程主机$ip不通的"

㈡ 判断一个进程是否存在

需求:判断web服务器中httpd进程是否存在

① 思路

  1. 查看进程的相关命令 ps pgrep
  2. 根据命令的返回状态值来判断进程是否存在
  3. 根据逻辑用脚本语言实现

② 落地实现

#!/bin/env bash
# 判断一个程序(httpd)的进程是否存在
pgrep httpd &>/dev/null
if [ $? -ne 0 ];then
    echo "当前httpd进程不存在"
else
    echo "当前httpd进程存在"
fi

或者
test $? -eq 0 && echo "当前httpd进程存在" || echo "当前httpd进程不存在"

③ 补充命令

pgrep命令:以名称为依据从运行进程队列中查找进程,并显示查找到的进程id
选项
-o:仅显示找到的最小(起始)进程号;
-n:仅显示找到的最大(结束)进程号;
-l:显示进程名称;
-P:指定父进程号;pgrep -p 4764  查看父进程下的子进程id
-g:指定进程组;
-t:指定开启进程的终端;
-u:指定进程的有效用户ID。

㈢ 判断一个服务是否正常

需求:判断门户网站是否能够正常访问

① 思路

  1. 可以判断进程是否存在,用/etc/init.d/httpd status判断状态等方法
  2. 最好的方法是==直接去访问==一下,通过访问成功和失败的返回值来判断
    • Linux环境,==wget== curl elinks -dump

② 落地实现

#!/bin/env bash
# 判断门户网站是否能够正常提供服务

#定义变量
web_server=www.itcast.cn
#访问网站
wget -P /shell/ $web_server &>/dev/null
[ $? -eq 0 ] && echo "当前网站服务是ok" && rm -f /shell/index.* || echo "当前网站服务不ok,请立刻处理"

3. 应用案例2

㈠ 判断用户是否存在

需求1:输入一个用户,用脚本判断该用户是否存在

 #!/bin/env bash
  2 read -p "请输入一个用户名:" user_name
  3 id $user_name &>/dev/null
  4 if [ $? -eq 0 ];then
  6     echo "该用户存在!"
  7 else
  8     echo "用户不存在!"
  9 fi


#!/bin/bash
# 判断 用户(id) 是否存在
read -p "输入壹个用户:" id
id $id &> /dev/null
if [ $? -eq 0 ];then
        echo "该用户存在"
else
        echo "该用户不存在"
fi

#!/bin/env bash
read -p "请输入你要查询的用户名:" username
grep -w $username /etc/passwd &>/dev/null
if [ $? -eq 0 ]
then
    echo "该用户已存在"
else
    echo "该用户不存在"
fi

#!/bin/bash
read -p "请输入你要检查的用户名:" name
id $name &>/dev/null
if [ $? -eq 0 ]
then
echo 用户"$name"已经存在
else
echo 用户"$name"不存在
fi

#!/bin/env bash
#判断用户是否存在
read -p "请写出用户名" id
id $id
if [ $? -eq 0 ];then
        echo "用户存在"
else
        echo "用户不存在"
fi

#!/bin/env bash
read -p '请输入用户名:' username
id $username &>/dev/null
[ $? -eq 0 ] && echo '用户存在' || echo '不存在'


㈡ 判断软件包是否安装

需求2:用脚本判断一个软件包是否安装,如果没安装则安装它(假设本地yum已配合)

㈢ 判断当前主机的内核版本

需求3:判断当前内核主版本是否为2,且次版本是否大于等于6;如果都满足则输出当前内核版本

思路:
1. 先查看内核的版本号    uname -r
2. 先将内核的版本号保存到一个变量里,然后再根据需求截取出该变量的一部分:主版本和次版本
3. 根据需求进步判断


#!/bin/bash
kernel=`uname -r`
var1=`echo $kernel|cut -d. -f1`
var2=`echo $kernel|cut -d. -f2`
test $var1 -eq 2 -a $var2 -ge 6 && echo $kernel || echo "当前内核版本不符合要求"
或者
[ $var1 -eq 2 -a $var2 -ge 6 ] && echo $kernel || echo "当前内核版本不符合要求"
或者
[[ $var1 -eq 2 && $var2 -ge 6 ]] && echo $kernel || echo "当前内核版本不符合要求"

或者
#!/bin/bash
kernel=`uname -r`
test ${kernel:0:1} -eq 2 -a ${kernel:2:1} -ge 6 && echo $kernel || echo '不符合要求'

其他命令参考:
uname -r|grep ^2.[6-9] || echo '不符合要求'

九、for循环语句

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

1. for循环语法结构

㈠ 列表循环

列表for循环:用于将一组命令执行**==已知的次数==**

  • 基本语法格式
for variable in {list}
     do
          command 
          command
          …
     done
或者
for variable in a b c
     do
         command
         command
     done
  • 举例说明
# for var in {1..10};do echo $var;done
# for var in 1 2 3 4 5;do echo $var;done
# for var in `seq 10`;do echo $var;done
# for var in $(seq 10);do echo $var;done
# for var in {0..10..2};do echo $var;done
# for var in {2..10..2};do echo $var;done
# for var in {10..1};do echo $var;done
# for var in {10..1..-2};do echo $var;done
# for var in `seq 10 -2 1`;do echo $var;done

㈡ 不带列表循环

不带列表的for循环执行时由用户指定参数和参数的个数

  • 基本语法格式
for variable
    do
        command 
        command
        …
   done
  • 举例说明
#!/bin/bash
for var
do
echo $var
done

echo "脚本后面有$#个参数"

㈢ 类C风格的for循环

  • 基本语法结构
for(( expr1;expr2;expr3 ))
    do
        command
        command
        …
    done
for (( i=1;i<=5;i++))
    do
        echo $i
    done


expr1:定义变量并赋初值
expr2:决定是否进行循环(条件)
expr3:决定循环变量如何改变,决定循环什么时候退出
  • 举例说明
 # for ((i=1;i<=5;i++));do echo $i;done
 # for ((i=1;i<=10;i+=2));do echo $i;done
 # for ((i=2;i<=10;i+=2));do echo $i;done

2. 应用案例

㈠ 脚本==计算==1-100奇数和

① 思路

  1. 定义一个变量来保存奇数的和 ==sum===0
  2. 找出1-100的奇数,保存到另一个变量里 ==i===遍历出来的奇数
  3. 从1-100中找出奇数后,再相加,然后将和赋值给变量 循环变量 for
  4. 遍历完毕后,将sum的值打印出来

② 落地实现(条条大路通罗马)

#!/bin/env bash
# 计算1-100的奇数和
# 定义变量来保存奇数和
sum=0

#for循环遍历1-100的奇数,并且相加,把结果重新赋值给sum

for i in {1..100..2}
do
    let sum=$sum+$i
done
#打印所有奇数的和
echo "1-100的奇数和是:$sum"


方法1:
#!/bin/bash
sum=0
for i in {1..100..2}
do
    sum=$[$i+$sum]
done
echo "1-100的奇数和为:$sum"

方法2:
#!/bin/bash
sum=0
for ((i=1;i<=100;i+=2))
do
    let sum=$i+$sum
done
echo "1-100的奇数和为:$sum"

方法3:
#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
    if [ $[$i%2] -ne 0 ];then
    let sum=$sum+$i
    fi
或者
test $[$i%2] -ne 0 && let sum=$sum+$i

done
echo "1-100的奇数和为:$sum"

方法4:
sum=0
for ((i=1;i<=100;i++))
do
    if [ $[$i%2] -eq 0 ];then
    continue
    else
    let sum=$sum+$i
    fi
done
echo "1-100的奇数和为:$sum"

#!/bin/bash
sum=0
for ((i=1;i<=100;i++))
do
    test $[$i%2] -eq 0 && continue || let sum=sum+$i
done
echo "1-100的奇数和是:$sum"

③ 循环控制语句

循环体: ==do….done==之间的内容

  • continue:继续;表示==循环体==内下面的代码不执行,重新开始下一次循环
  • break:打断;马上停止执行本次循环,执行==循环体==后面的代码
  • exit:表示直接跳出程序
[root@server ~]# cat for5.sh 
#!/bin/bash
for i in {1..5}
do
    test $i -eq 2 && break || touch /tmp/file$i
done
echo hello hahahah

㈡ 判断所输整数是否为质数

质数(素数):==只能==被1和它本身==整除==的数叫质数。
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

① 思路

  1. 让用户输入一个数,保存到一个变量里 read -p "请输入一个正整数:" num
  2. 如果能被其他数整除就不是质数——>$num%$i 是否等于0 $i=2到$num-1
  3. 如果输入的数是1或者2取模根据上面判断又不符合,所以先排除1和2
  4. 测试序列从2开始,输入的数是4——>得出结果$num不能和$i相等,并且$num不能小于$i

② 落地实现

#!/bin/env bash
#定义变量来保存用户所输入数字
read -p "请输入一个正整数字:" number

#先排除用户输入的数字1和2
[ $number -eq 1 ] && echo "$number不是质数" && exit
[ $number -eq 2 ] && echo "$number是质数" && exit

#循环判断用户所输入的数字是否质数

for i in `seq 2 $[$number-1]`
    do
     [ $[$number%$i] -eq 0 ] && echo "$number不是质数" && exit
    done
echo "$number是质数"

优化思路:没有必要全部产生2~$[$number-1]序列,只需要产生一半即可。

更好解决办法:类C风格完美避开了生成序列的坑
for (( i=2;i<=$[$number-1];i++))
do
        [ $[$number%$i] -eq 0 ] && echo "$number不是质数" && exit

done
echo "$number是质数"

㈢ 批量创建用户

需求:批量加5个新用户,以u1到u5命名,并统一加一个新组,组名为class,统一改密码为123

① 思路

  1. 添加用户的命令 useradd -G class
  2. 判断class组是否存在 grep -w ^class /etc/group 或者groupadd class
  3. 根据题意,判断该脚本循环5次来添加用户 for
  4. 给用户设置密码,应该放到循环体里面

② 落地实现

#!/bin/env bash
#判断class组是否存在
grep -w ^class /etc/group &>/dev/null
test $? -ne 0 && groupadd class

#循环创建用户
for ((i=1;i<=5;i++))
do
    useradd -G class u$i
    echo 123|passwd --stdin u$i
done
#用户创建信息保存日志文件



方法一:
#!/bin/bash
#判断class组是否存在
grep -w class /etc/group &>/dev/null
[ $? -ne 0 ] && groupadd class
#批量创建5个用户
for i in {1..5}
do
    useradd -G class u$i
    echo 123|passwd --stdin u$i
done

方法二:
#!/bin/bash
#判断class组是否存在
cut -d: -f1 /etc/group|grep -w class &>/dev/null
[ $? -ne 0 ] && groupadd class

#循环增加用户,循环次数5次,for循环,给用户设定密码
for ((i=1;i<=5;i++))
do
    useradd u$i -G class
    echo 123|passwd --stdin u$i
done


方法三:
#!/bin/bash
grep -w class /etc/group &>/dev/null
test $? -ne 0 && groupadd class
或者
groupadd class &>/dev/null

for ((i=1;i<=5;i++))
do
useradd -G class u$i && echo 123|passwd --stdin u$i
done

3. 案例二

㈠ 批量创建用户

**需求1:**批量新建5个用户stu1~stu5,要求这几个用户的家目录都在/rhome.

#!/bin/bash
#判断/rhome是否存在
[ -f /rhome ] && mv /rhome /rhome.bak
test ! -d /rhome && mkdir /rhome
或者
[ -f /rhome ] && mv /rhome /rhome.bak || [ ! -d /rhome ] && mkdir /rhome 

#创建用户,循环5次
for ((i=1;i<=5;i++))
do
    useradd -d /rhome/stu$i stu$i
    echo 123|passwd --stdin stu$i
done

㈡ 局域网内脚本检查主机网络通讯

需求2:

写一个脚本,局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

以10.1.1.1~10.1.1.10为例

10.1.1.1~10.1.1.254

#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
    ping -c1 $ip.$i &>/dev/null
    if [ $? -eq 0 ];then
        echo "$ip.$i is ok" >> /tmp/ip_up.txt
    else
        echo "$ip.$i is down" >> /tmp/ip_down.txt
    fi
    或者
    [ $? -eq 0 ] && echo "$ip.$i is ok" >> /tmp/ip_up.txt || echo "$ip.$i is down" >> /tmp/ip_down.txt
done

[root@server shell03]# time ./ping.sh         

real    0m24.129s
user    0m0.006s
sys     0m0.005s

延伸扩展:shell脚本并发

并行执行:
{程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加wait

#!/bin/bash
#定义变量
ip=10.1.1
#循环去ping主机的IP
for ((i=1;i<=10;i++))
do
{

        ping -c1 $ip.$i &>/dev/null
        if [ $? -eq 0 ];then
                echo "$ip.$i is ok" >> /tmp/ip_up.txt
        else
                echo "$ip.$i is down" >> /tmp/ip_down.txt
        fi
}&
done
wait
echo "ip is ok...."

[root@server ~]# time ./ping.sh 
ip is ok...

real    0m3.091s
user    0m0.001s
sys     0m0.008s

㈢ 判断闰年

需求3:

输入一个年份,判断是否是润年(能被4整除但不能被100整除,或能被400整除的年份即为闰年)

#!/bin/bash
read -p "Please input year:(2017)" year
if [ $[$year%4] -eq 0 -a $[$year%100] -ne 0 ];then
    echo "$year is leap year"
elif [ $[$year%400] -eq 0 ];then
    echo "$year is leap year"
else
    echo "$year is not leap year"
fi

4. 总结

  • FOR循环语法结构
  • FOR循环可以结合==条件判断和流程控制语句==
    • do ……done 循环体
    • 循环体里可以是命令集合,再加上条件判断以及流程控制
  • 控制循环语句
    • continue 继续,跳过本次循环,继续下一次循环
    • break 打断,跳出循环,==执行==循环体外的代码
    • exit 退出,直接退出程序

十、while循环语句

特点:==条件为真就进入循环;条件为假就退出循环==

1. while循环语法结构

while 表达式
    do
        command...
    done

while  [ 1 -eq 1 ] 或者 (( 1 > 2 ))
  do
     command
     command
     ...
 done

循环打印1-5数字

FOR循环打印:
for ((i=1;i<=5;i++))
do
    echo $i
done

while循环打印:
i=1
while [ $i -le 5 ]
do
    echo $i
    let i++
done

2. 应用案例

㈠ 脚本计算1-50偶数和

#!/bin/env bash
sum=0
for ((i=0;i<=50;i+=2))
do
    let sum=$sum+$i  (let sum=sum+i)
done
echo "1-50的偶数和为:$sum"


#!/bin/bash
#定义变量
sum=0
i=2
#循环打印1-50的偶数和并且计算后重新赋值给sum
while [ $i -le 50 ]
do
    let sum=$sum+$i
    let i+=2  或者 $[$i+2]
done
#打印sum的值
echo "1-50的偶数和为:$sum"

㈡ 脚本同步系统时间

① 具体需求

  1. 写一个脚本,==30秒==同步一次系统时间,时间同步服务器10.1.1.1
  2. 如果同步失败,则进行邮件报警,每次失败都报警
  3. 如果同步成功,也进行邮件通知,但是==成功100次==才通知一次

② 思路

  1. 每隔30s同步一次时间,该脚本是一个死循环 while 循环

  2. 同步失败发送邮件 1) ntpdate 10.1.1.1 2) rdate -s 10.1.1.1

  3. 同步成功100次发送邮件 定义变量保存成功次数

③ 落地实现

#!/bin/env bash
# 该脚本用于时间同步
NTP=10.1.1.1
count=0
while true
do
    ntpdate $NTP &>/dev/null
    if [ $? -ne 0 ];then
        echo "system date failed" |mail -s "check system date"  root@localhost
    else
        let count++
        if [ $count -eq 100 ];then
        echo "systemc date success" |mail -s "check system date"  root@localhost && count=0
        fi
    fi
sleep 30
done


#!/bin/bash
#定义变量
count=0
ntp_server=10.1.1.1
while true
do
    rdate -s $ntp-server &>/dev/null
    if [ $? -ne 0 ];then
        echo "system date failed" |mail -s 'check system date'  root@localhost    
    else
        let count++
        if [ $[$count%100] -eq 0 ];then
        echo "system date successfull" |mail -s 'check system date'  root@localhost && count=0
        fi
    fi
sleep 3
done

以上脚本还有更多的写法,课后自己完成

十一、until循环

特点:条件为假就进入循环;条件为真就退出循环

1. until语法结构

until expression   [ 1 -eq 1 ]  (( 1 >= 1 ))
    do
        command
        command
        ...
    done

打印1-5数字

i=1
while [ $i -le 5 ]
do
    echo $i
    let i++
done

i=1
until [ $i -gt 5 ]
do
    echo $i
    let i++
done

2. 应用案例

㈠ 具体需求

  1. 使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;
  2. stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10

㈡ 思路

  1. 创建用户语句 useradd -u|useradd -d
  2. 使用循环语句(until)批量创建用户 until循环语句结构
  3. 判断用户前5个和后5个 条件判断语句

㈢ 落地实现

#!/bin/env bash
if [ -d /rhome ];then
    echo "/rhome目录已存在"
else
    mkdir /rhome
    echo "/rhome不存在,已完成创建"
fi

i=1
until [ $i -gt 10 ]
do
        if [ $i -le 5 ];then
                useradd -u $[1000+$i] stu$i
                echo 123|passwd --stdin stu$i

        else
                useradd -d /rhome/stu$i stu$i
                echo 123|passwd --stdin stu$i
        fi
let i++
done

==================================================

#!/bin/bash
i=1
until [ $i -gt 10 ]
do
    if [ $i -le 5 ];then
        useradd -u $[1000+$i] stu$i && echo 123|passwd --stdin stu$i
    else
        [ ! -d /rhome ] && mkdir /rhome
        useradd -d /rhome/stu$i stu$i && echo 123|passwd --stdin stu$i        
    fi
let i++
done

十二、随机数

1. 如何生成随机数?

系统变量:**==RANDOM==**,默认会产生0~32767的随机整数

打印一个随机数
echo $RANDOM
查看系统上一次生成的随机数
# set|grep RANDOM
RANDOM=28325

产生0~1之间的随机数
echo $[$RANDOM%2]

产生0~2之间的随机数
echo $[$RANDOM%3]

产生0~3之间的随机数
echo $[$RANDOM%4]

产生0~9内的随机数
echo $[$RANDOM%10]

产生0~100内的随机数
echo $[$RANDOM%101]


产生50-100之内的随机数
echo $[$RANDOM%51+50]

产生三位数的随机数
echo $[$RANDOM%900+100]

2. 实战案例

㈠ 随机产生以139开头的电话号码

具体需求1:

写一个脚本,产生一个phonenum.txt文件,随机产生以139开头的手机号1000个,每个一行。

① 思路

  1. 产生1000个电话号码,脚本需要循环1000次 FOR WHILE UNTIL
  2. 139+8位,后8位随机产生,可以让每一位数字都随机产生 echo $[$RANDOM%10]
  3. 将随机产生的数字分别保存到变量里,然后加上139保存到文件里

② 落地实现

#!/bin/env bash
#产生1000个以139开头的电话号码并保存文件phonenum.txt
file=/shell03/phonenum.txt
for ((i=1;i<=1000;i++))
do
    n1=$[$RANDOM%10]
    n2=$[$RANDOM%10]
    n3=$[$RANDOM%10]
    n4=$[$RANDOM%10]
    n5=$[$RANDOM%10]
    n6=$[$RANDOM%10]
    n7=$[$RANDOM%10]
    n8=$[$RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> $file
done


#!/bin/bash
# random phonenum
# 循环1000次产生电话号码并保存到文件
for i in {1..1000}
do
    n1=$[RANDOM%10]
    n2=$[RANDOM%10]
    n3=$[RANDOM%10]
    n4=$[RANDOM%10]
    n5=$[RANDOM%10]
    n6=$[RANDOM%10]
    n7=$[RANDOM%10]
    n8=$[RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done

#!/bin/bash
i=1
while [ $i -le 1000 ]
do
    n1=$[$RANDOM%10]
    n2=$[$RANDOM%10]
    n3=$[$RANDOM%10]
    n4=$[$RANDOM%10]
    n5=$[$RANDOM%10]
    n6=$[$RANDOM%10]
    n7=$[$RANDOM%10]
    n8=$[$RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
    let i++
done

continue:继续,跳过本次循环,执行下一次循环
break:打断,执行循环体外的代码do..done外
exit:退出程序


#!/bin/bash
for i in {1..1000}
do
    n1=$[$RANDOM%10]
    n2=$[$RANDOM%10]
    n3=$[$RANDOM%10]
    n4=$[$RANDOM%10]
    n5=$[$RANDOM%10]
    n6=$[$RANDOM%10]
    n7=$[$RANDOM%10]
    n8=$[$RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" >> phonenum.txt
done

#!/bin/bash
#create phone num file
for ((i=1;i<=1000;i++))
do
    n1=$[$RANDOM%10]
    n2=$[$RANDOM%10]
    n3=$[$RANDOM%10]
    n4=$[$RANDOM%10]
    n5=$[$RANDOM%10]
    n6=$[$RANDOM%10]
    n7=$[$RANDOM%10]
    n8=$[$RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt
done

#!/bin/bash
count=0
while true
do
    n1=$[$RANDOM%10]
    n2=$[$RANDOM%10]
    n3=$[$RANDOM%10]
    n4=$[$RANDOM%10]
    n5=$[$RANDOM%10]
    n6=$[$RANDOM%10]
    n7=$[$RANDOM%10]
    n8=$[$RANDOM%10]
    echo "139$n1$n2$n3$n4$n5$n6$n7$n8" |tee -a phonenum.txt && let count++
    if [ $count -eq 1000 ];then
        break
    fi
done

㈡ 随机抽出5位幸运观众

具体需求:

  1. 在上面的1000个手机号里抽奖==5个==幸运观众,显示出这5个幸运观众。
  2. 但只显示头3个数和尾号的4个数,中间的都用*代替

① 思路

  1. 确定幸运观众所在的行 0-1000 随机找出一个数字 $[$RANDOM%1000+1]
  2. 将电话号码提取出来 head -随机产生行号 phonenum.txt |tail -1
  3. ==显示==前3个和后4个数到屏幕 echo 139****

② 落地实现

#!/bin/bash
#定义变量
phone=/shell03/phonenum.txt
#循环抽出5位幸运观众
for ((i=1;i<=5;i++))
do
    #定位幸运观众所在行号
    line=`wc -l $phone |cut -d' ' -f1`
    luck_line=$[RANDOM%$line+1]
    #取出幸运观众所在行的电话号码
    luck_num=`head -$luck_line $phone|tail -1`
    #显示到屏幕
    echo "139****${luck_num:7:4}"
    echo $luck_num >> luck.txt
    #删除已经被抽取的幸运观众号码
    #sed -i "/$luck_num/d" $phone
done


#!/bin/bash
file=/shell04/phonenum.txt
for i in {1..5}
do
    file_num=`wc -l $file |cut -d' ' -f1`
    line=`echo $[$RANDOM%$file_num+1]`
    luck=`head -n $line  $file|tail -1`
    echo "139****${luck:7:4}" && echo $luck >> /shell04/luck_num.txt
done


#!/bin/bash
for ((i=1;i<=5;i++))
do
file=phonenum.txt
line=`cat phonenum.txt |wc -l`    1000
luckline=$[$RANDOM%$line+1]
phone=`cat $file|head -$luckline|tail -1`
echo "幸运观众为:139****${phone:7:4}"
done


或者
#!/bin/bash
# choujiang
phone=phonenum.txt
for ((i=1;i<=5;i++))
do
    num=`wc -l phonenum.txt |cut -d' ' -f1`
    line=`echo $[$RANDOM%$num+1]`
    luck=`head -$line $phone |tail -1`
    sed -i "/$luck/d" $phone
    echo "幸运观众是:139****${luck:7:4}"
done

㈢ 批量创建用户(密码随机产生)

需求:批量创建5个用户,每个用户的密码为一个随机数

① 思路

  1. 循环5次创建用户
  2. 产生一个密码文件来保存用户的随机密码
  3. 从密码文件中取出随机密码赋值给用户

② 落地实现

#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..5}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'>> user_pass.file

#循环创建5个用户
for ((i=1;i<=5;i++))
do
    user=`head -$i user_pass.file|tail -1|cut -d: -f1`
    pass=`head -$i user_pass.file|tail -1|cut -d: -f2`
    useradd $user
    echo $pass|passwd --stdin $user
done

或者
for i in `cat user_pass.file`
do
    user=`echo $i|cut -d: -f1`
    pass=`echo $i|cut -d: -f2`
    useradd $user
    echo $pass|passwd --stdin $user
done

#!/bin/bash
#crate user and set passwd
#产生一个保存用户名和密码的文件
echo user0{1..3}:itcast$[$RANDOM%9000+1000]#@~|tr ' ' '\n'|tr ':' ' ' >> user_pass.file
#循环创建5个用户
while read user pass
do
useradd $user
echo $pass|passwd --stdin $user
done < user_pass.file


pwgen工具产生随机密码:
[root@server shell04]# pwgen -cn1 12
Meep5ob1aesa
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)
user01:Bahqu9haipho user02:Feiphoh7moo4 user03:eilahj5eth2R
[root@server shell04]# echo user0{1..3}:$(pwgen -cn1 12)|tr ' ' '\n'
user01:eiwaShuZo5hi
user02:eiDeih7aim9k
user03:aeBahwien8co

十三、expect

expect 自动应答 tcl语言

需求1:A远程登录到server上什么都不做

#!/usr/bin/expect
# 开启一个程序
spawn ssh root@10.1.1.1
# 捕获相关内容
expect {
        "(yes/no)?" { send "yes\r";exp_continue }
        "password:" { send "123456\r" }
}
interact   //交互

脚本执行方式:
# ./expect1.sh
# /shell04/expect1.sh
# expect -f expect1.sh

1)定义变量
#!/usr/bin/expect
set ip 10.1.1.1
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}
interact


2)使用位置参数
#!/usr/bin/expect
set ip [ lindex $argv 0 ]
set pass [ lindex $argv 1 ]
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}
interact

需求2:A远程登录到server上操作

#!/usr/bin/expect
set ip 10.1.1.1
set pass 123456
set timeout 5
spawn ssh root@$ip
expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
}

expect "#"
send "rm -rf /tmp/*\r"
send "touch /tmp/file{1..3}\r"
send "date\r"
send "exit\r"
expect eof

需求3:shell脚本和expect结合使用,在==多台服务器==上创建1个用户

[root@server shell04]# cat ip.txt 
10.1.1.1 123456
10.1.1.2 123456


1. 循环  useradd username
2. 登录远程主机——>ssh——>从ip.txt文件里获取IP和密码分别赋值给两个变量
3. 使用expect程序来解决交互问题


#!/bin/bash
# 循环在指定的服务器上创建用户和文件
while read ip pass
do
    /usr/bin/expect <<-END &>/dev/null
    spawn ssh root@$ip
    expect {
    "yes/no" { send "yes\r";exp_continue }
    "password:" { send "$pass\r" }
    }
    expect "#" { send "useradd yy1;rm -rf /tmp/*;exit\r" }
    expect eof
    END
echo "$ip服务器用户创建完毕"
done < ip.txt



#!/bin/bash
cat ip.txt|while read ip pass
do
        {

        /usr/bin/expect <<-HOU
        spawn ssh root@$ip
        expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
        }
        expect "#"
        send "hostname\r"
        send "exit\r"
        expect eof
        HOU

        }&
done
wait
echo "user is ok...."


或者
#!/bin/bash
while read ip pass
do
        {

        /usr/bin/expect <<-HOU
        spawn ssh root@$ip
        expect {
                "yes/no" { send "yes\r";exp_continue }
                "password:" { send "$pass\r" }
        }
        expect "#"
        send "hostname\r"
        send "exit\r"
        expect eof
        HOU

        }&
done<ip.txt
wait
echo "user is ok...."

十四、case语句

  1. case语句为多重匹配语句
  2. 如果匹配成功,执行相匹配的命令

1. 语法结构

说明:pattern表示需要匹配的模式


case var in             定义变量;var代表是变量名
pattern 1)              模式1;| 分割多个模式,相当于or
    command1            需要执行的语句
    ;;                  两个分号代表命令结束
pattern 2)
    command2
    ;;
pattern 3)
    command3
    ;;
          *)              default,不满足以上模式,默认执行*)下面的语句
    command4
    ;;
esac                            esac表示case语句结束

2. 应用案例

㈠ 脚本传不同值做不同事

具体需求:当给程序传入start、stop、restart三个不同参数时分别执行相应命令

#!/bin/env bash
case $1 in
        start|S)
        service apache start &>/dev/null && echo "apache 启动成功"
        ;;
        stop|T)
        service apache stop &>/dev/null && echo "apache 停止成功"
        ;;
        restart|R)
        service apache restart &>/dev/null && echo "apache 重启完毕"
        ;;
        *)
        echo "请输入要做的事情..."
        ;;
esac

㈡ 根据用户需求选择做事

具体需求:

脚本提示让用户输入需要管理的服务名,然后提示用户需要对服务做什么操作,如启动,关闭等操作

#!/bin/env bash
read -p "请输入你要管理的服务名称(vsftpd):" service
case $service in
        vsftpd|ftp)
        read -p "请选择你需要做的事情(restart|stop):" action
        case $action in
                stop|S)
                service vsftpd stop &>/dev/null && echo "该$serivce服务已经停止成功"
                ;;
                start)
                service vsftpd start &>/dev/null && echo "该$serivce服务已经成功启动"
                ;;
        esac
        ;;
        httpd|apache)
        echo "apache hello world"
        ;;
        *)
        echo "请输入你要管理的服务名称(vsftpd)"
        ;;
esac

㈢ 菜单提示让用户选择需要做的事

具体需求:

模拟一个多任务维护界面;当执行程序时先显示总菜单,然后进行选择后做相应维护监控操作

**********请选择*********
h    显示命令帮助
f    显示磁盘分区
d    显示磁盘挂载
m    查看内存使用
u    查看系统负载
q    退出程序
*************************

思路:

  1. 菜单打印出来
  2. 交互式让用户输入操作编号,然后做出相应处理

落地实现:

  1. 菜单打印(分解动作)
#!/bin/env bash
cat <<-EOF
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    EOF
  1. 最终实现
#!/bin/bash
#打印菜单
cat <<-EOF
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    EOF

#让用户输入需要的操作
while true
do
read -p "请输入需要操作的选项[f|d]:" var1
case $var1 in
    h)
    cat <<-EOF
        h       显示命令帮助
        f       显示磁盘分区
        d       显示磁盘挂载
        m       查看内存使用
        u       查看系统负载
        q       退出程序
    EOF
    ;;
    f)
    fdisk -l
    ;;
    d)
    df -h
    ;;
    m)
    free -m
    ;;
    u)
    uptime
    ;;
    q)
    exit
    ;;
esac
done



#!/bin/bash
#打印菜单
menu(){
cat <<-END
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    END
}
menu
while true
do
read -p "请输入你的操作[h for help]:" var1
case $var1 in
    h)
    menu
    ;;
    f)
    read -p "请输入你要查看的设备名字[/dev/sdb]:" var2
    case $var2 in
        /dev/sda)
        fdisk -l /dev/sda
        ;;
        /dev/sdb)
        fdisk -l /dev/sdb
        ;;
    esac
    ;;
    d)
    lsblk
    ;;
    m)
    free -m
    ;;
    u)
    uptime
    ;;
    q)
    exit
    ;;
esac
done

十五、函数

1. 如何定义函数?

方法1:

函数名()
{
  函数体(一堆命令的集合,来实现某个功能)   
}

方法2:

function 函数名()
{
   函数体(一堆命令的集合,来实现某个功能)
   echo hello
   echo world
}

函数中==return==说明:

  1. return可以==结束一个函数==。类似于循环控制语句break(结束当前循环,执行循环体后面的代码)。
  2. return默认返回函数中最后一个命令状态值,也可以给定参数值,范围是0-256之间。
  3. 如果没有return命令,函数将返回最后一个指令的退出状态值。

3. 函数如何调用?

㈠ 当前命令行调用

[root@MissHou shell04]# cat fun1.sh 
#!/bin/bash
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

[root@MissHou shell04]# source fun1.sh 
[root@MissHou shell04]# . fun1.sh 

[root@MissHou shell04]# hello 888
hello lilei 888
MissHou.itcast.cc
[root@MissHou shell04]# menu
1. mysql
2. web
3. app
4. exit

㈡ 定义到用户的环境变量中

[root@MissHou shell05]# vim ~/.bashrc 
文件中增加如下内容:
hello(){
echo "hello lilei $1"
hostname
}
menu(){
cat <<-EOF
1. mysql
2. web
3. app
4. exit
EOF
}

注意:
当用户打开bash的时候会读取该文件

㈢ 脚本中调用

#!/bin/bash
#打印菜单
source ./fun1.sh
menu(){
cat <<-END
    h    显示命令帮助
    f    显示磁盘分区
    d    显示磁盘挂载
    m    查看内存使用
    u    查看系统负载
    q    退出程序
    END
}
menu        //调用函数

4. 应用案例

具体需求:

  1. 写一个脚本==收集用户输入==的基本信息(姓名,性别,年龄),如不输入==一直提示输入==
  2. 最后根据用户的信息输出相对应的内容

思路:

  1. ==交互式==定义多个变量来保存用户信息 姓名、性别、年龄

  2. 如果不输一直提示输入

    • ==循环==直到输入字符串不为空 while 判断输入字符串是否为空
    • 每个信息都必须不能为空,该功能可以定义为一个函数,方便下面脚本调用
  3. 根据用户输入信息做出匹配判断

代码实现:

#!/bin/bash
#该函数实现用户如果不输入内容则一直循环直到用户输入为止,并且将用户输入的内容打印出来
input_fun()
{
  input_var=""
  output_var=$1
  while [ -z $input_var ]
    do
    read -p "$output_var" input_var
    done
    echo $input_var
}

input_fun 请输入你的姓名:

或者
#!/bin/bash
fun()
{
    read -p "$1" var
    if [ -z $var ];then
        fun $1
    else
        echo $var
    fi
}


#调用函数并且获取用户的姓名、性别、年龄分别赋值给name、sex、age变量
name=$(input_fun 请输入你的姓名:)
sex=$(input_fun 请输入你的性别:)
age=$(input_fun 请输入你的年龄:)

#根据用户输入的性别进行匹配判断
case $sex in
            man)
            if [ $age -gt 18 -a $age -le 35 ];then
                echo "中年大叔你油腻了吗?加油"
            elif [ $age -gt 35 ];then
                echo "保温杯里泡枸杞"
            else
                echo "年轻有为。。。"
            fi
            ;;
            woman)
            xxx
            ;;
            *)
            xxx
            ;;
esac

  目录