1. Shell概述
Shell是一个命令解释器,它接收应用程序/用户命令,然后调用操作系统内核。
Shell是一个功能强大的编程语言,易编写、易调试、灵活性强。
- Linux提供的Shelll解析器有
cat /etc/shells
- bash和sh的关系
cd /bin
ll | grep bash
[root@lys bin]# echo $SHELL
/bin/bash
2. Shell 脚本入门
(1) 脚本格式
脚本以#!/bin/bash开头 (指定解析器)
(2)第一个Shell脚本 helloworld.sh
touch helloworld.sh
vim helloworld.sh
# 内容
# !/bin/bash
echo "hellow world"
(3) 脚本的常用执行方式
- bash或sh + 脚本的相对路径或绝对路径 (不用赋予脚本+x权限)(重新开了一个进程执行bash命令)
sh ./helloworld.sh
bash ./helloworld.sh
- 采用输入脚本的绝对路径或相对路径执行脚本(必须具有可执行权限+x)(本质是使用当前的bash进程执行命令)
./helloworld.sh
- 【了解】在脚本的路径前加“.”或者source
source helloworld.sh
. helloworld.sh
原因:
前两种方式都是在当前shell找那个打开一个子shell来执行脚本内容,当脚本内容结束,则子shell关系,回到父shell中。
第三种,也就是使用在脚本路径前加”." 或者source的方式,可以使脚本内容在当前shell里执行,而无需打开子shell。这就是为什么我们每次要修改完/etc/profile文件以后,需要source一下的原因。
开子shell与不开子shell的却别就在于,环境变量的集成关系,如在子shell中设置的当前变量,父shell是不可见的。
子Shell
[root@lys shell]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 1907 10679 0 12:46 pts/1 00:00:00 ps -f
root 10679 10672 0 09:47 pts/1 00:00:00 -bash
[root@lys shell]# bash
[root@lys shell]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 1970 10679 0 12:46 pts/1 00:00:00 bash
root 2006 1970 0 12:46 pts/1 00:00:00 ps -f
root 10679 10672 0 09:47 pts/1 00:00:00 -bash
[root@lys shell]# exit
exit
[root@lys shell]# ps -f
UID PID PPID C STIME TTY TIME CMD
root 2034 10679 0 12:46 pts/1 00:00:00 ps -f
root 10679 10672 0 09:47 pts/1 00:00:00 -bash
3. 变量
3.1 系统预定义变量
常用系统变量
PWD、
USER
(1)查看系统变量的值
echo $HOME
(2)显示当前Shell中所有变量:
env
set # 包含所有系统自定义和用户自定义的变量
printenv $USER
printenv USER
3.2 自定义变量
(1)基本语法
- 定义变量:变量名=变量,注意,= 前后不能有空格
- 撤销变量:usset变量名
- 声明静态变量:readonly变量,注意:不能unset
(2)变量定义规则
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。
- 等号两侧不能有空格
- 在bash中,变量默认类型都是字符串类型,无法进行数值运算。
- 变量的值如果有空格,需要使用双引号或单引号括起来。
[root@lys shell]# a=2
[root@lys shell]# echo $a
2
# 提升为全部变量
[root@lys shell]# export a
new_var='hello linux'
vim helloworld.sh
# 追加
echo $new_var
sh helloworld.sh
#发现没有值
# 使用一下命令就有值
. hellworld.sh
sourcce hellowrld.sh
计算
a=$((1+5))
a=$[1+5]
定义只读变量
readonly=5f
撤销变量
unset a
3.3 特殊变量
3.3.1 $n
$n n为数字,¥0代表该脚本名称,$1-
{10}
vim helloworld.sh
echo "hello,$1"
# 执行
sh helloworld.sh a
# 输出
hello,a
示例2
#!/bin/bash
echo '======$n====='
echo script name: $0
echo 1st paramater: $1
echo 2nd paramater: $2
[root@lys shell]# sh parameter.sh 0 1 2
======$n=====
script name: parameter.sh
1st paramater: 0
2nd paramater: 1
# 获取调用文件名称
basename
3.3.2 $#
$# 获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性
vim parameter.sh
#!/bin/bash
echo '======$n====='
echo script name: $0
echo 1st paramater: $1
echo 2nd paramater: $2
echo '======$#====='
echo paramter numbers: $#
sh parameter.sh ab cd
# 结果
======$n=====
script name: parameter.sh
1st paramater: ab
2nd paramater: cd
======$#=====
paramter numbers: 2
3.3.3
*把所有的参数看成一个整体
@把每个参数区分对待
vim paramter.sh
#!/bin/bash
echo '======$n====='
echo script name: $0
echo 1st paramater: $1
echo 2nd paramater: $2
echo '======$#====='
echo paramter numbers: $#
echo $*
echo $@
[root@lys shell]# sh parameter.sh ab cd
======$n=====
script name: parameter.sh
1st paramater: ab
2nd paramater: cd
======$#=====
paramter numbers: 2
ab cd
ab cd
3.3.4 $?
$? 最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量非0(具体是哪个数,由命令自己来决定)则证明上一命令执行不正确了)
[root@lys shell]# ./helloworld.sh
helloworld
hello,
[root@lys shell]# echo $?
0
# 如果错误执行,就非0
[root@lys shell]# $?
-bash: 0: command not found
[root@lys shell]# echo $?
127
4 运算符
$((运算式)) 或 $[运算式]
计算 (2+3)*4
[root@lys shell]# echo $[(2+3)*4]
20
expr使用
expr 1 + 2
3
# 乘法需转义
expr 5 \* 2
10
命令替换
[root@lys shell]# a=$(expr 2 + 2)
[root@lys shell]# echo $a
4
[root@lys shell]# a=`expr 5 + 2`
[root@lys shell]# echo $a
7
加法脚本
#!/bin/bash
sum=$[$1 + $2]
echo $sum
5 条件判断
基本语法
test condition
[ condition] (注意condition前后要有空格)
注意: 条件非空即为true, [lys] 返回true, [ ]返回false
常用判断条件
(1) 两个整数之间比较
-eq 等于(equal)
-ne 不等于 (not equal)
-lt 小于 (less than)
-le 小于等于 (less equal)
-gt 大于 (greate than)
-ge 大于等于 (greater equal)
注:如果是字符串之间的比较,用等号”=“判断相等;用”!=“判断不等。
(2)按照文件权限进行判断
-r 有读的权限(read)
-w 有写的权限 (write)
-x 有执行的权限 (execute)
# 判断文件是否有可执行权限
[root@lys shell]# [ -x helloworld.sh ]
[root@lys shell]# echo $?
0
# 结果0代表有
(3) 按照文件类型进行判断
-e 文件存在(existence)
-f 文件存在并且是一个常规的文件(file)
-d 文件存在并且是一个目录(directory)
案例实操
(1)23是否大于等于22
$ [ 23 -ge 22 ]
(2)判断文件是否有可执行权限
$ [ -x helloworld.sh ]
(3) 文件是否存在
$ [ -e helloworld.sh]
6 流程控制
6.1 if判断
(1)单分支
if [条件判断]; then
程序
fi
或者
if [ 条件判断式 ]
then
程序
fi
if [ "$1"x = "lys"x ]
then
echo 'welcome, lys'
fi
if [ $a -gt 18 -a $a -lt 35]; then echo OK; fi
-a and
-o or
多分支
if [ 条件判断式 ]
then
程序
if [ $2 -lt 18 ]
then
echo "未成年人"
elif [ $2 -lt 35 ]
then
echo "中年人"
else
echo "成年人"
fi
6.2 case语句
基本语法
case $变量名 in
"值1")
;;
"值2")
;;
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac
注意事项:
(1) case行尾必须为单词 "in", 每个模式匹配必须以有括号")" 结束
(2)双分号“;;"表示命令序列结束,相当于java的breakl
(3)最后的“*)"表示默认模式,相当于java中的default
case $1 in
1)
echo "one"
;;
2)
echo "two"
;;
3)
echo "three"
;;
*)
echo "number else"
;;
esac
sh case_test.sh 1
6.3 for 循环
基本语法
for ((初始值; 循环控制条件; 变量变化 ))
do
程序
done
#!/bin/bash
for (( i=1; i <= $1 ; i++ ))
do
sum=$[ $sum + $i ]
done;
echo $sum
for os in linux windows macos; do echo $os; done
for i in {1..100}; do sum=$[$sum+$i]; done; echo $sum
for 与
@
echo '$#'
for para in $*
do
echo $para
done
echo '$@'
for para in $@
do
echo $para
done
[root@lys shell]# sh for_test2.sh 1 2 3
$#
1
2
3
$@
1
2
3
如果使用引号包围起来
echo '$#'
for para in "$*"
do
echo $para
done
echo '$@'
for para in "$@"
do
echo $para
done
[root@lys shell]# sh for_test2.sh 1 2 3
$#
1 2 3
$@
1
2
3
6.4 while循环
while [ 条件判断式 ]
do
程序
done
while循环实现 1+ 100
i=1
sum=0
while [ $i -le 100 ]
do
sum=$[ $sum + $i ]
i=$[$i + 1]
# let sum+=i
# let i++ let 实现方式
done;
echo $sum
7 read读取控制台输入
基本语法
read (选项) (参数)
-p:指定读取值时的提示符:
-t:指定读取值时等待的时间(秒)如果-t不加表示一直等待
参数:
变量:指定读取值的变量名
案例实操
提示7秒内,读取控制台输入的名称
read -t 10 -p "请输入您的名称:" name
echo "welcome $name"
8 函数
8.1 系统函数
8.1.1 basename
basename [string/pathname] [suffix] basename命令会删除所有的前缀包括最后一个(“/")字符,然后再字符串显示出来
basename 可以理解为取路径里的文件名称
选项:
suffix为后缀,如果suffix被指定了,basename会讲pathname或string中的suffix去掉。
filename="$1"_log_$(date +%s)
echo $filename
basename 基础用法
[root@lys shell]# basename helloworld.sh
helloworld.sh
[root@lys shell]# basename helloworld.sh .sh
helloworld
8.1.2 dirname
dirname 文件绝对路径 从给定的包含绝对路径的文件名中取出文件名(非目录的部分),然后返回剩下的路径(目录的部分))
dirname 可以理解为取文件路径的绝对路径名称
获取hellowolrd.sh的目录名称
[root@lys shell]# dirname /java-project/shell/helloworld.sh
/java-project/shell
8.2 自定义函数
[function] funname[()]
{
Action;
[return int;]
}
经验技巧
(1)必须在调用函数地方之前,先声明函数,shell脚本是逐行运行。不会像其他语言一样先编译。
(2)函数返回值,只能通过$?系统变量获得,可以显示加:return返回,如果不加,将以最后一条命令运行结果作为返回值。return后跟述职n(0--255)
计算两数的和
function add(){
s=$[$1+$2]
echo "sum=$s"
}
read -p "请输入第一个整数" a
read -p "请输入第二个整数" b
add $a $b
使用#?最大只能返回255
function add(){
s=$[$1+$2]
return $s
}
read -p "请输入第一个整数" a
read -p "请输入第二个整数" b
add $a $b
echo "sum="$?
使用$()获取值
function add(){
s=$[$1+$2]
echo $s
}
read -p "请输入第一个整数" a
read -p "请输入第二个整数" b
sum=$(add $a $b)
echo "sum=$sum"
11 综合案例
11.1 归档文件
实际生产中,往往需要对重要数据进行归档备份。
需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将目录下所有文件按天归档保存,并将归档日期附加在归档文件名上,放在/root/archive下。
这里使用到了归档命令:tar
后面可以加上-c表示归档。加上-z选项表示同时进行压缩,得到的文件后缀名为tar.gz
脚本实现:
#!/bin/bash
#首先判断输入参数个数是否为1
if [ $# -ne 1 ]
then
echo "参数个数错误!应该输入一个参数,作为归档目录"
exit
fi
# 从参数中获取目录名称
if [ -d $1 ]
then
echo
else
echo
echo "目录不存在!"
exit
fi
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
# 获取当前日期
DATE=$(date +%y%m%d)
# 生成的归档文件名称
FILE=archive_${DIR_NAME}_$DATE.tar.gz
DEST=/root/archive/$FILE
# 开始归档
echo "开始归档..."
echo
tar -zcf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]
then
echo
echo "归档成功"
echo "归档文件为:$DEST"
else
echo "归档出现问题"
echo
fi
exit
执行
mkdir /root/archive
sh tar.sh ../shell/
定时执行
crontab -e
0 2 * * * /java-project/shell/tar.sh /java-project/shell
9 正则表达式入门
正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在Linux中,grep,sed,awk等文本处理工具都支持通过正则表达式进行模式匹配。
9.1 常规匹配
一串不包含规则字符的正则表达式匹配它自己,例如:
cat /etc/passwd | grep lys
就会匹配所有包含lys的行
9.2 常用特殊字符
- 特殊字符: ^
^匹配一行的开头,例如:
cat /etc/passwd | grep ^a
会匹配出所有的a开头的行
- 特殊字符:$
$ 匹配一行的结束,例如:
cat /etc/passwd | grep t$
查找空行
cat helloworld.sh | grep ^$
- 特殊字符:.
cat /etc/passwd | grep r..t
会匹配包含rabt,rbbt,rxdt,root等的所有行
- 特殊字符:*
*不单独使用,他上一个字符连用,表示上一个字符0次或多次,例如
cat /etc/passwd | grep ro*t
会匹配rt,rot,root,roooor等
.*会匹配全部
- 字符区间(中括号):[]
[] 表示匹配某个范围内的一个字符,例如
[6,8]-------匹配6或者8,
[0-9]------匹配一个0-9的数字
[0-9]*-----匹配任意长度的数字字符串
[a-z] --- 匹配一个a-z之间的字符串
[a-z]* ------ 匹配任意长度的字母字符串
[a-z,e-f]-- 匹配a-c,或者e-f之间的任意字符
cat /etc/passwd | grep r[a,b,c]*t
会匹配rt,rat,raat等等行
6 特殊字符:\
\标识转义,并不会单独使用。由于所有特殊字符都有其特定匹配模式,当我们想匹配某一特殊字符本身时(例如,我想找出所有包含’$'的行),就需要将转义符合特殊字符连用,来表示特殊字符本身,例如
cat /etc/passwd | grep 'a\$b'
就会匹配所有包含a$b的行。主要需要使用单引号将表达式引起来、
10 文本处理工具
10.1 cut
cut的工作就是“剪”,具体的说 就是在文件中负责剪切数据用的。cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。
基本用法
cut [选项参数] filename
说明:默认分隔符是制表符
选项参数 | 功能 |
-f | 列号,提前第几列 |
-d | 分隔符,按照指定分隔符分割列,默认是制表符“\t" |
-c | 按字符进行切割 后加加n 表示取第几列 比如 -c 1 |
数据准备
dong shen
guan zhen
wo wo
lai lai
le le
切割 cut.txt 第一列
cut -d " " -f 1 cut.sh
截取第二列
cut -d " " -f 2 cut.sh
截取区间
cut -d " " -f 1-4 cut.sh
cut -d " " -f -4 cut.sh
cut -d " " -f 4- cut.sh
在cut切割guan
cat cut.sh | grep guan | cut -d " " -f 1
选取系统 PATH 变量值,第 2 个“:”开始后的所有路径:
echo $PATH
/java-project/jdk1.8.0_321/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bi
echo $PATH | cut -d ":" -f 3-
/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
切割 ifconfig 后打印的 IP 地址
ifconfig ens33 | grep netmask | cut -d " " -f 10
10.2 awk
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。
基本用法
awk [选项参数] '/pattern1/{action}' '/patten2/{action2}' filename
pattern:表示awk在数据中查找的内容,就是匹配模式
action:在匹配内容时所执行的一系列命令
选项参数 | 功能 |
-F | 指定输入文件分隔符 |
-v | 赋值一个用户定义变量 |
案例实操
(1)数据准备
搜索passwd文件以root关键字开头的所有行,并输出该行的第7列
#cut 实现
cat /etc/passwd | grep ^root | cut -d ";" -f 7
# awk实现
cat /etc/passwd | awk -F ":" '/^root/ {print $7}'
ps -ef | awk '{print $}'
# 打印文件大小
ll -l | awk '{print $5}'
搜索passwd文件以root关键字开头的所有行,并输出该行的第1列和第7列,中间以“,”号分割。
cat /etc/passwd | awk -F ":" '/^root/ {print $1","$7}'
(4)只显示/etc/passwd的第一列和第七列,以逗号分割,且在所有行前面添加列名"begin"在最后一行添加"end"
cat /etc/passwd | awk -F ":" 'BEGIN{print "begin"}{print $1","$7}END{print "end"}'
(5)将passwd文件中的用户id增加1并输出
cat /etc/passwd | awk -F ":" '{print $3+1}'
# 使用变量
cat /etc/passwd | awk -v i=1 -F ":" '{print $3+i}'
awk的内置变量
变量 | 说明 |
FILENAME | 文件名 |
NR | 已读的记录数(行号) |
DF | 浏览记录的域的个数(切割后,列的个数) |
案例实操
(1)统计passwd文件名,每行的行号,每行的列数
cat /etc/passwd | awk -F ":" '{print "文件名 :"FILENAME "行号:" NR "列数: "NF }'
(2) 查询ifconfig命令和输出结果中的空行所在的行号
ifconfig | awk '/^$/ {print "空行: " NR}'
11 综合应用
我们可以利用 Linux 自带的 mesg 和 write 工具,向其它用户发送消息。
需求:实现一个向某个用户快速发送消息的脚本,输入用户名作为第一个参数,后面直 接跟要发送的消息。脚本需要检测用户是否登录在系统中、是否打开消息功能,以及当前发送消息是否为空。
前置知识
who 查看有多少控制台
[root@bogon logs]# who
root pts/0 2022-05-26 17:34 (124.64.252.49)
root pts/1 2022-05-27 10:12 (120.244.202.117)
root pts/2 2022-05-27 11:37 (58.34.52.34)
root pts/5 2022-05-27 10:16 (120.244.202.117)
root pts/6 2022-05-27 19:10 (120.245.102.201)
root pts/8 2022-05-25 10:18 (124.64.252.49)
# 向某个控制台发出消息
write root pts/1
hi
脚本实现如下:
#!/bin/bash/
# 查看用户是否登录
login_user=$(who | grep -i -m 1 $1 | awk '{print $1}' )
if [ -z $login_user ]
then
echo "$1 不在线!"
echo "脚本退出"
exit
fi
# 查看用户是否开启消息功能
is_allowed=$(who -T | grep -i -m 1 $1 | awk '{print $2}' )
if [ $is_allowed != "+" ]
then
echo "$1 没有开启消息功能"
echo "脚本退出..."
exit
fi
# 确认是否有消息发送
if [ -z $2 ]
then
echo "没有消息发送"
echo "脚本退出"
exit
fi
# 从参数中获取要发送的消息
whole_msg=$(echo $* | cut -d " " -f 2-)
# 获取用户登录的终端
user_terminal=$(who | grep -i -m 1 $1 | awk '{print $2}' )
# 写入要发送的消息
echo $whole_msg | write $login_user $user_terminal
if [ $? != 0 ]
then
echo "发送失败!"
else
echo "发送成功!"
fi
exit
超级实用的shell脚本
1. 编写 helloworld 脚本
#!/bin/bash
echo "hello world"
2. 通过位置变量创建 Linux 系统账户及密码
#!/bin/bash
#$1 是执行脚本的第一个参数,$2 是执行脚本的第二个参数
useradd "$1"
echo "$2" | passwd ‐‐stdin "$1"
3. 每周 5 使用 tar 命令备份/var/log 下的所有日志文件
#vim /root/logbak.sh
#编写备份脚本,备份后的文件名包含日期标签,防止后面的备份将前面的备份数据覆盖
#注意 date 命令需要使用反引号括起来,反引号在键盘<tab>键上面
tar -czf log-`date +%Y%m%d`.tar.gz /var/log
# crontab ‐e #编写计划任务,执行备份脚本
00 03 * * 5 /root/logbak.sh
4. 一键部署 LNMP(RPM 包版本)
#!/bin/bash
#使用 yum 安装部署 LNMP,需要提前配置好 yum 源,否则该脚本会失败
#本脚本使用于 centos7.2 或 RHEL7.2
yum ‐y install httpd
yum ‐y install mariadb mariadb‐devel mariadb‐server
yum ‐y install php php‐mysql
systemctl start httpd mariadb
systemctl enable httpd mariadb
5. 实时监控本机内存和硬盘剩余空间,剩余内存小于 500M、根分区剩余空间小于 1000M
时,发送报警邮件给 root 管理员
#!/bin/bash
#提取根分区剩余空间
useradd "$1"
3 / 38
#提取内存剩余空间
mem_size=$(free |awk '/Mem/{print $4}')
while :
do
#注意内存和磁盘提取的空间大小都是以 Kb 为单位
if [ $disk_size ‐le 512000 ‐a $mem_size ‐le 1024000 ];then
mail ‐s Warning root <<EOF
Insufficient resources,资源不足
EOF
fi
done
6. 脚本生成一个 100 以内的随机数,提示用户猜数字,根据用户的输入,提示用户猜对了,
猜小了或猜大了,直至用户猜对脚本结束。
#!/bin/bash
#RANDOM 为系统自带的系统变量,值为 0‐32767 的随机数
#使用取余算法将随机数变为 1‐100 的随机数
num=$[RANDOM%100+1]
#使用 read 提示用户猜数字
#使用 if 判断用户猜数字的大小关系:‐eq(等于),‐ne(不等于),‐gt(大于),‐ge(大于等于),‐lt(小于),‐le(小
于等于)
while :
do
read ‐p "计算机生成了一个 1‐100 的随机数,你猜: " cai
if [ $cai ‐eq $num ];then
echo "恭喜,猜对了"
exit
elif [ $cai ‐gt $num ];then
echo "Oops,猜大了"
else
echo "Oops,猜小了"
fi
done
7. 检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不
是,则提示您非管理员(使用字串对比版本)
#!/bin/bash
if [ $USER == "root" ];then
yum ‐y install vsftpd
else
echo "您不是管理员,没有权限安装软件"
fi
8. 检测本机当前用户是否为超级管理员,如果是管理员,则使用 yum 安装 vsftpd,如果不
是,则提示您非管理员(使用 UID 数字对比版本)
#!/bin/bash
if [ $UID ‐eq 0 ];then
yum ‐y install vsftpd
else
echo "您不是管理员,没有权限安装软件"
fi
9. 编写脚本:提示用户输入用户名和密码,脚本自动创建相应的账户及配置密码。如果用户
不输入账户名,则提示必须输入账户名并退出脚本;如果用户不输入密码,则统一使用默
认的 123456 作为默认密码。
#!/bin/bash
read ‐p "请输入用户名: " user
#使用‐z 可以判断一个变量是否为空,如果为空,提示用户必须输入账户名,并退出脚本,退出码为 2 #没有输入用户名脚本退出后,使用$?查看的返回码为 2
if [ ‐z $user ];then
echo "您不需输入账户名"
exit 2
fi
#使用 stty ‐echo 关闭 shell 的回显功能
#使用 stty echo 打开 shell 的回显功能
stty ‐echo
read ‐p "请输入密码: " pass
stty echo
pass=${pass:‐123456}
useradd "$user"
echo "$pass" | passwd ‐‐stdin "$user"
10. 依次提示用户输入 3 个整数,脚本根据数字大小依次排序输出 3 个数字
#!/bin/bash
read ‐p "请输入一个整数:" num1
read ‐p "请输入一个整数:" num2
read ‐p "请输入一个整数:" num3
#不管谁大谁小,最后都打印 echo "$num1,$num2,$num3"
#num1 中永远存最小的值,num2 中永远存中间值,num3 永远存最大值
#如果输入的不是这样的顺序,则改变数的存储顺序,如:可以将 num1 和 num2 的值对调
tmp=0
#如果 num1 大于 num2,就把 num1 和和 num2 的值对调,确保 num1 变量中存的是最小值
if [ $num1 ‐gt $num2 ];then
tmp=$num1
num1=$num2
num2=$tmp
fi
#如果 num1 大于 num3,就把 num1 和 num3 对调,确保 num1 变量中存的是最小值
if [ $num1 ‐gt $num3 ];then
tmp=$num1
num1=$num3
num3=$tmp
fi
#如果 num2 大于 num3,就把 num2 和 num3 对标,确保 num2 变量中存的是小一点的值
if [ $num2 ‐gt $num3 ];then
tmp=$num2
num2=$num3
num3=$tmp
fi
echo "排序后数据为:$num1,$num2,$num3"
11. 编写脚本,实现人机<石头,剪刀,布>游戏
#!/bin/bash
game=(石头 剪刀 布)
num=$[RANDOM%3]
computer=${game[$num]}
#通过随机数获取计算机的出拳
#出拳的可能性保存在一个数组中,game[0],game[1],game[2]分别是 3 中不同的可能
echo "请根据下列提示选择您的出拳手势"
echo "1.石头"
echo "2.剪刀"
echo "3.布"
read ‐p "请选择 1‐3:" person
case $person in
1)
if [ $num ‐eq 0 ];then
echo "平局"
elif [ $num ‐eq 1 ];then
echo "你赢"
else
echo "计算机赢"
fi;;
2)
if [ $num ‐eq 0 ];then
echo "计算机赢"
elif [ $num ‐eq 1 ];then
echo "平局"
else
echo "你赢"
fi;;
3)
if [ $num ‐eq 0 ];then
echo "你赢"
elif [ $num ‐eq 1 ];then
echo "计算机赢"
else
echo "平局"
fi;;
*)
echo "必须输入 1‐3 的数字"
esac
12. 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
状态(for 版本)
#!/bin/bash
for i in {1..254}
do
ping ‐c2 ‐i0.3 ‐W1 192.168.4.$i &>/dev/null
if [ $? –eq 0 ];then
echo "192.168.4.$i is up"
else
echo "192.168.4.$i is down"
fi
done
13. 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
状态(while 版本)
#!/bin/bash
i=1
while [ $i ‐le 254 ]
do
ping ‐c2 ‐i0.3 ‐W1 192.168.4.$i &>/dev/null
if [ $? –eq 0 ];then
echo "192.168.4.$i is up"
else
echo "192.168.4.$i is down"
fi
let i++
done
14. 编写脚本测试 192.168.4.0/24 整个网段中哪些主机处于开机状态,哪些主机处于关机
状态(多进程版)
#!/bin/bash
#定义一个函数,ping 某一台主机,并检测主机的存活状态
myping(){
ping ‐c2 ‐i0.3 ‐W1 $1 &>/dev/null
if [ $? ‐eq 0 ];then
echo "$1 is up"
else
echo "$1 is down"
fi
}
for i in {1..254}
do
myping 192.168.4.$i &
done
#使用&符号,将执行的函数放入后台执行
#这样做的好处是不需要等待 ping 第一台主机的回应,就可以继续并发 ping 第二台主机,依次类推。
15. 编写脚本,显示进度条
#!/bin/bash
jindu(){
while :
do
echo ‐n '#'
sleep 0.2
done
}
jindu &
cp ‐a $1 $2
killall $!
echo "拷贝完成''
16. 进度条,动态时针版本
#!/bin/bash
#定义一个显示进度的函数,屏幕快速显示| / ‐ \
rotate_line(){
INTERVAL=0.1 #设置间隔时间
COUNT="0" #设置 4 个形状的编号,默认编号为 0(不代表任何图像)
while :
do
COUNT=`expr $COUNT + 1` #执行循环,COUNT 每次循环加 1,(分别代表 4 中不同的形状)
case $COUNT in #判断 COUNT 的值,值不一样显示的形状就不一样
"1") #值为 1 显示‐
echo ‐e '‐'"\b\c"
sleep $INTERVAL
;;
"2") #值为 2 显示\\,第一个\是转义
echo ‐e '\\'"\b\c"
sleep $INTERVAL
;;
"3") #值为 3 显示|
echo ‐e "|\b\c"
sleep $INTERVAL
;;
"4") #值为 4 显示/
echo ‐e "/\b\c"
sleep $INTERVAL
;;
*) #值为其他时,将 COUNT 重置为 0
COUNT="0";;
esac
done
}
rotate_line
17. 9*9 乘法表(编写 shell 脚本,打印 9*9 乘法表)
#!/bin/bash
for i in `seq 9`
do
for j in `seq $i`
do
echo ‐n "$i*$j=$[i*j] "
done
echo
done
18. 使用死循环实时显示 eth0 网卡发送的数据包流量
#!/bin/bash
while :
do
echo '本地网卡 eth0 流量信息如下: '
ifconfig eth0 | grep "RX pack" | awk '{print $5}'
ifconfig eth0 | grep "TX pack" | awk '{print $5}'
sleep 1
done
19. 使用 user.txt 文件中的人员名单,在计算机中自动创建对应的账户并配置初始密码
#!/bin/bash
#本脚本执行,需要提前准备一个 user.txt 文件,该文件中包含有若干用户名信息
for i in `cat user.txt`
do
useradd $i
echo "123456" | passwd ‐‐stdin $i
done
20. 编写批量修改扩展名脚本,如批量将 txt 文件修改为 doc 文件
#!/bin/bash
#执行脚本时,需要给脚本添加位置参数
#脚本名 txt doc(可以将 txt 的扩展名修改为 doc) #脚本名 doc jpg(可以将 doc 的扩展名修改为 jpg)
for i in "ls *.$1"
do
mv $i ${i%.*}.$2
done
21. 使用 expect 工具自动交互密码远程其他主机安装 httpd 软件
#!/bin/bash
#删除~/.ssh/known_hosts 后,ssh 远程任何主机都会询问是否确认要连接该主机
rm ‐rf ~/.ssh/known_hosts
expect <<EOF
spawn ssh 192.168.4.254
expect "yes/no" {send "yes\r"}
#根据自己的实际情况将密码修改为真实的密码字串
expect "password" {send "密码\r"}
expect "#" {send "yum ‐y install httpd\r"}
expect "#" {send "exit\r"}
EOF
22.一键部署 LNMP(源码安装版本)
#!/bin/bash
menu(){
clear
echo " ##############‐‐‐‐Menu‐‐‐‐##############"
echo "# 1. Install Nginx"
echo "# 2. Install MySQL"
echo "# 3. Install PHP"
echo "# 4. Exit Program"
echo " ########################################"
}
choice(){
read ‐p "Please choice a menu[1‐9]:" select
}
install_nginx(){
id nginx &>/dev/null
if [ $? ‐ne 0 ];then
useradd ‐s /sbin/nologin nginx
fi
if [ ‐f nginx‐1.8.0.tar.gz ];then
tar ‐xf nginx‐1.8.0.tar.gz
cd nginx‐1.8.0
yum ‐y install gcc pcre‐devel openssl‐devel zlib‐devel make
./configure ‐‐prefix=/usr/local/nginx ‐‐with‐http_ssl_module
make
make install
ln ‐s /usr/local/nginx/sbin/nginx /usr/sbin/
cd ..
else
echo "没有 Nginx 源码包"
fi
}
install_mysql(){
yum ‐y install gcc gcc‐c++ cmake ncurses‐devel perl
id mysql &>/dev/null
if [ $? ‐ne 0 ];then
useradd ‐s /sbin/nologin mysql
fi
if [ ‐f mysql‐5.6.25.tar.gz ];then
tar ‐xf mysql‐5.6.25.tar.gz
cd mysql‐5.6.25
cmake .
make
make install
/usr/local/mysql/scripts/mysql_install_db ‐‐user=mysql ‐‐ datadir=/usr/local/mysql/data/‐‐basedir=/usr/local/mysql/
chown ‐R root.mysql /usr/local/mysql
chown ‐R mysql /usr/local/mysql/data
/bin/cp ‐f /usr/local/mysql/support‐files/mysql.server /etc/init.d/mysqld
chmod +x /etc/init.d/mysqld
/bin/cp ‐f /usr/local/mysql/support‐files/my‐default.cnf /etc/my.cnf
echo "/usr/local/mysql/lib/" >> /etc/ld.so.conf
ldconfig
echo 'PATH=\$PATH:/usr/local/mysql/bin/' >> /etc/profile
export PATH
else
echo "没有 mysql 源码包"
exit
fi
}
install_php(){
#安装 php 时没有指定启动哪些模块功能,如果的用户可以根据实际情况自行添加额外功能如‐‐with‐gd 等
yum ‐y install gcc libxml2‐devel
if [ ‐f mhash‐0.9.9.9.tar.gz ];then
tar ‐xf mhash‐0.9.9.9.tar.gz
cd mhash‐0.9.9.9
./configure
make
make install
cd ..
if [ ! ‐f /usr/lib/libmhash.so ];then
ln ‐s /usr/local/lib/libmhash.so /usr/lib/
fi
ldconfig
else
echo "没有 mhash 源码包文件"
exit
fi
if [ ‐f libmcrypt‐2.5.8.tar.gz ];then
tar ‐xf libmcrypt‐2.5.8.tar.gz
cd libmcrypt‐2.5.8
./configure
make
make install
cd ..
if [ ! ‐f /usr/lib/libmcrypt.so ];then
ln ‐s /usr/local/lib/libmcrypt.so /usr/lib/
fi
ldconfig
else
echo "没有 libmcrypt 源码包文件"
exit
fi
if [ ‐f php‐5.4.24.tar.gz ];then
tar ‐xf php‐5.4.24.tar.gz
cd php‐5.4.24
./configure ‐‐prefix=/usr/local/php5 ‐‐with‐mysql=/usr/local/mysql ‐‐enable‐fpm ‐‐enable‐mbstring ‐‐with‐mcrypt ‐‐with‐mhash ‐‐with‐config‐file‐path=/usr/local/php5/etc ‐‐with‐mysqli=/usr/local/mysql/bin/mysql_config
make && make install
/bin/cp ‐f php.ini‐production /usr/local/php5/etc/php.ini
/bin/cp ‐f /usr/local/php5/etc/php‐fpm.conf.default /usr/local/php5/etc/php‐fpm.conf
cd ..
else
echo "没有 php 源码包文件"
exit
fi
}
while :
do
menu
choice
case $select i
)
install_nginx
;;
2)
install_mysql
;;
3)
install_php
;;
4)
exit
;;
*)
echo Sorry!
esac
done
23. 编写脚本快速克隆 KVM 虚拟机
#!/bin/bash
#本脚本针对 RHEL7.2 或 Centos7.2
#本脚本需要提前准备一个 qcow2 格式的虚拟机模板,名称为/var/lib/libvirt/images /.rh7_template 的虚
拟机模板
#该脚本使用 qemu‐img 命令快速创建快照虚拟机
#脚本使用 sed 修改模板虚拟机的配置文件,将虚拟机名称、UUID、磁盘文件名、MAC 地址
# exit code:
# 65 ‐> user input nothing
# 66 ‐> user input is not a number
# 67 ‐> user input out of range
# 68 ‐> vm disk image exists
IMG_DIR=/var/lib/libvirt/images
BASEVM=rh7_template
read ‐p "Enter VM number: " VMNUM
if [ $VMNUM ‐le 9 ];then
VMNUM=0$VMNUM
fi
if [ ‐z "${VMNUM}" ]; then
echo "You must input a number."
exit 65
elif [[ ${VMNUM} =~ [a‐z] ]; then
echo "You must input a number."
exit 66
elif [ ${VMNUM} ‐lt 1 ‐o ${VMNUM} ‐gt 99 ]; then
echo "Input out of range"
exit 67
fi
NEWVM=rh7_node${VMNUM}
if [ ‐e $IMG_DIR/${NEWVM}.img ]; then
echo "File exists."
exit 68
fi
echo ‐en "Creating Virtual Machine disk image......\t"
qemu‐img create ‐f qcow2 ‐b $IMG_DIR/.${BASEVM}.img $IMG_DIR/${NEWVM}.img &> /dev/null
echo ‐e "\e[32;1m[OK]\e[0m"
#virsh dumpxml ${BASEVM} > /tmp/myvm.xml
cat /var/lib/libvirt/images/.rhel7.xml > /tmp/myvm.xml
sed ‐i "/<name>${BASEVM}/s/${BASEVM}/${NEWVM}/" /tmp/myvm.xml
sed ‐i "/uuid/s/<uuid>.*<\/uuid>/<uuid>$(uuidgen)<\/uuid>/" /tmp/myvm.xml
sed ‐i "/${BASEVM}\.img/s/${BASEVM}/${NEWVM}/" /tmp/myvm.xml
#修改 MAC 地址,本例使用的是常量,每位使用该脚本的用户需要根据实际情况修改这些值
#最好这里可以使用便利,这样更适合于批量操作,可以克隆更多虚拟机
sed ‐i "/mac /s/a1/0c/" /tmp/myvm.xml
echo ‐en "Defining new virtual machine......\t\t"
virsh define /tmp/myvm.xml &> /dev/null
echo ‐e "\e[32;1m[OK]\e[0m"
24. 编写一个点名器脚本
#!/bin/bash
#该脚本,需要提前准备一个 user.txt 文件
#该文件中需要包含所有姓名的信息,一行一个姓名,脚本每次随机显示一个姓名
while :
do
#统计 user 文件中有多少用户
line=`cat user.txt |wc ‐l`
num=$[RANDOM%line+1]
sed ‐n "${num}p" user.txt
sleep 0.2
clear
done
25. 查看有多少远程的 IP 在连接本机(不管是通过 ssh 还是 web 还是 ftp 都统计)
#!/bin/bash
#使用 netstat ‐atn 可以查看本机所有连接的状态,‐a 查看所有,‐t 仅显示 tcp 连接的信息,‐n 数字格式显示
# Local Address(第四列是本机的 IP 和端口信息)
#Foreign Address(第五列是远程主机的 IP 和端口信息)
#使用 awk 命令仅显示第 5 列数据,再显示第 1 列 IP 地址的信息
#sort 可以按数字大小排序,最后使用 uniq 将多余重复的删除,并统计重复的次数
netstat ‐atn | awk '{print $5}' | awk '{print $1}' | sort ‐nr | uniq ‐c
26. 对 100 以内的所有正整数相加求和(1+2+3+4…+100)
#!/bin/bash
#seq 100 可以快速自动生成 100 个整数
sum=0
for i in `seq 100`
do
sum=$[sum+i]
done
echo "总和是:$sum"
27. 统计 13:30 到 14:30 所有访问 apache 服务器的请求有多少个
#!/bin/bash
#awk 使用‐F 选项指定文件内容的分隔符是/或者:
#条件判断$7:$8 大于等于 13:30,并且要求,$7:$8 小于等于 14:30
#最后使用 wc ‐l 统计这样的数据有多少行,即多少个
awk ‐F "[ /:]" '$7":"$8>="13:30" && $7":"$8<="14:30"' /var/log/httpd/access_log |wc ‐l
28. 统计 13:30 到 14:30 所有访问本机 Aapche 服务器的远程 IP 地址是什么
#!/bin/bash
#awk 使用‐F 选项指定文件内容的分隔符是/或者:
#条件判断$7:$8 大于等于 13:30,并且要求,$7:$8 小于等于 14:30
#日志文档内容里面,第 1 列是远程主机的 IP 地址,使用 awk 单独显示第 1 列即可
awk ‐F "[ /:]" '$7":"$8>="13:30" && $7":"$8<="14:30"{print $1}' /var/log/httpd/access_log
29. 打印国际象棋棋盘,效果如下图:
#!/bin/bash
#设置两个变量,i 和 j,一个代表行,一个代表列,国际象棋为 8*8 棋盘
#i=1 是代表准备打印第一行棋盘,第 1 行棋盘有灰色和蓝色间隔输出,总共为 8 列
#i=1,j=1 代表第 1 行的第 1 列;i=2,j=3 代表第 2 行的第 3 列
#棋盘的规律是 i+j 如果是偶数,就打印蓝色色块,如果是奇数就打印灰色色块
#使用 echo ‐ne 打印色块,并且打印完成色块后不自动换行,在同一行继续输出其他色块
for i in {1..8}
do
for j in {1..8}
do
sum=$[i+j]
if [ $[sum%2] ‐eq 0 ];then
echo ‐ne "\033[46m \033[0m"
else
echo ‐ne "\033[47m \033[0m"
fi
done
echo
done
30. 统计每个远程 IP 访问了本机 apache 几次?
#!/bin/bash
awk '{ip[$1]++}END{for(i in ip){print ip[i],i}}' /var/log/httpd/access_log
31. 统计当前 Linux 系统中可以登录计算机的账户有多少个
#!/bin/bash
#方法 1:
grep "bash$" /etc/passwd | wc ‐l
#方法 2:
awk ‐f: '/bash$/{x++}end{print x}' /etc/passwd
32. 统计/var/log 有多少个文件,并显示这些文件名
#!/bin/bash
#使用 ls 递归显示所有,再判断是否为文件,如果是文件则计数器加 1
cd /var/log
sum=0
for i in `ls ‐r *`
do
if [ ‐f $i ];then
let sum++
echo "文件名:$i"
fi
done
echo "总文件数量为:$sum"
33. 自动为其他脚本添加解释器信息#!/bin/bash,如脚本名为 test.sh 则效果如下:
#!/bin/bash
#./test.sh abc.sh
自动为 abc.sh 添加解释器信息
#./test.sh user.sh
自动为 user.sh 添加解释器信息
#先使用 grep 判断对象脚本是否已经有解释器信息,如果没有则使用 sed 添加解释器以及描述信息
if ! grep ‐q "^#!" $1; then
sed '1i #!/bin/bash' $1
sed '2i #Description: '
fi
#因为每个脚本的功能不同,作用不同,所以在给对象脚本添加完解释器信息,以及 Description 后还希望
#继续编辑具体的脚本功能的描述信息,这里直接使用 vim 把对象脚本打开,并且光标跳转到该文件的第 2 行
vim +2 $1
34. 自动化部署 varnish 源码包软件
#!/bin/bash
#本脚本需要提前下载 varnish‐3.0.6.tar.gz 这样一个源码包软件,该脚本即可用自动源码安装部署软件
yum ‐y install gcc readline‐devel pcre‐devel
useradd ‐s /sbin/nologin varnish
tar ‐xf varnish‐3.0.6.tar.gz
cd varnish‐3.0.6
#使用 configure,make,make install 源码安装软件包
./configure ‐‐prefix=/usr/local/varnish
make && make install
#在源码包目录下,将相应的配置文件拷贝到 Linux 系统文件系统中
#默认安装完成后,不会自动拷贝或安装配置文件到 Linux 系统,所以需要手动 cp 复制配置文件
#并使用 uuidgen 生成一个随机密钥的配置文件
cp redhat/varnish.initrc /etc/init.d/varnish
cp redhat/varnish.sysconfig /etc/sysconfig/varnish
cp redhat/varnish_reload_vcl /usr/bin/
ln ‐s /usr/local/varnish/sbin/varnishd /usr/sbin/
ln ‐s /usr/local/varnish/bin/* /usr/bin
mkdir /etc/varnish
cp /usr/local/varnish/etc/varnish/default.vcl /etc/varnish/
uuidgen > /etc/varnish/secret
35. 编写 nginx 启动脚本
#!/bin/bash
#本脚本编写完成后,放置在/etc/init.d/目录下,就可以被 Linux 系统自动识别到该脚本
#如果是centos7以上的版本就是systemctl stop nginx,这种类型
#如果本脚本名为/etc/init.d/nginx,则 service nginx start 就可以启动该服务
#service nginx stop 就可以关闭服务
#service nginx restart 可以重启服务
#service nginx status 可以查看服务状态
program=/usr/local/nginx/sbin/nginx
pid=/usr/local/nginx/logs/nginx.pid
start(){
if [ ‐f $pid ];then
echo "nginx 服务已经处于开启状态"
else
$program
fi
stop(){
if [ ‐! ‐f $pid ];then
echo "nginx 服务已经关闭"
else
$program ‐s stop
echo "关闭服务 ok"
fi
}
status(){
if [ ‐f $pid ];then
echo "服务正在运行…"
else
echo "服务已经关闭"
fi
}
case $1 in
start)
start;;
stop)
stop;;
restart)
stop
sleep 1
start;;
status)
status;;
*)
echo "你输入的语法格式错误"
esac
36. 自动对磁盘分区、格式化、挂载
#!/bin/bash
#对虚拟机的 vdb 磁盘进行分区格式化,使用<<将需要的分区指令导入给程序 fdisk
#n(新建分区),p(创建主分区),1(分区编号为 1),两个空白行(两个回车,相当于将整个磁盘分一个区)
#注意:1 后面的两个回车(空白行)是必须的!
fdisk /dev/vdb << EOF
n
p
1
wq
EOF
#格式化刚刚创建好的分区
mkfs.xfs /dev/vdb1
#创建挂载点目录
if [ ‐e /data ]; then
exit
fi
mkdir /data
#自动挂载刚刚创建的分区,并设置开机自动挂载该分区
echo '/dev/vdb1 /data xfs defaults 1 2' >> /etc/fstab
mount ‐a
37. 自动优化 Linux 内核参数
#!/bin/bash
#脚本针对 RHEL7
cat >> /usr/lib/sysctl.d/00‐system.conf <<EOF
fs.file‐max=65535
net.ipv4.tcp_timestamps = 0
net.ipv4.tcp_synack_retries = 5
net.ipv4.tcp_syn_retries = 5
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
#net.ipv4.tcp_keepalive_time = 120
net.ipv4.ip_local_port_range = 1024 65535
kernel.shmall = 2097152
kernel.shmmax = 2147483648
kernel.shmmni = 4096
kernel.sem = 5010 641280 5010 128
net.core.wmem_default=262144
net.core.wmem_max=262144
net.core.rmem_default=4194304
net.core.rmem_max=4194304
net.ipv4.tcp_fin_timeout = 10
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_window_scaling = 0
net.ipv4.tcp_sack = 0
EOF
sysctl –p
38. 切割 Nginx 日志文件(防止单个文件过大,后期处理很困难)
#!/bin/bash
#mkdir /data/scripts
#vim /data/scripts/nginx_log.sh
#!/bin/bash
logs_path="/usr/local/nginx/logs/"
mv ${logs_path}access.log ${logs_path}access_$(date ‐d "yesterday" +"%Y%m%d").log
kill ‐USR1 `cat /usr/local/nginx/logs/nginx.pid`
# chmod +x /data/scripts/nginx_log.sh
#crontab ‐e #脚本写完后,将脚本放入计划任务每天执行一次脚本
0 1 * * * /data/scripts/nginx_log.sh
39. 检测 MySQL 数据库连接数量
#!/bin/bash
#本脚本每 2 秒检测一次 MySQL 并发连接数,可以将本脚本设置为开机启动脚本,或在特定时间段执行
#以满足对 MySQL 数据库的监控需求,查看 MySQL 连接是否正常
#本案例中的用户名和密码需要根据实际情况修改后方可使用
log_file=/var/log/mysql_count.log
user=root
passwd=123456
while :
do
sleep 2
count=`mysqladmin ‐u "$user" ‐p "$passwd" status | awk '{print $4}'`
echo "`date +%Y‐%m‐%d` 并发连接数为:$count" >> $log_file
done
40. 根据 md5 校验码,检测文件是否被修改
#!/bin/bash
#本示例脚本检测的是/etc 目录下所有的 conf 结尾的文件,根据实际情况,您可以修改为其他目录或文件
#本脚本在目标数据没有被修改时执行一次,当怀疑数据被人篡改,再执行一次
#将两次执行的结果做对比,MD5 码发生改变的文件,就是被人篡改的文件
for i in $(ls /etc/*.conf)
do
md5sum "$i" >> /var/log/conf_file.log
done