shell脚本

概述

shell是一个命令行解释器,它接收应用程序\用户命令,然后调用操作系统内核

解释器负责将用户的指令翻译为内核可以识别的指令

shell还是一个功能相当强大的编程语言,易编写,易调试,灵活性强

shell脚本

提前将可执行的命令语句写入一个文件

快捷键

常用快捷键:

  • Tab键补齐命令
  • ctrl+A光标到命令的最前面
  • ctrl+E光标到命令的最后面
  • hostory命令历史
  • alias命令别名
  • 标准输入与输出重定向(>、>>、2>、2>>、&>)
  • 管道(|)

编写shell脚本

1
2
#!/bin/bash
echo "你好"

sh 脚本文件名 会启动子进程

source 脚本文件名 # 不会启动子进程

各类括号的作用

单小括号()

  • 命令组合 括号中的命令会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。
  • 命令替换 shell扫描一遍命令行发现$(ls)结构,便将里面的ls执行一次,得到其标准输出,有些shell不支持,如tcsh
  • 用于初始化数组。如a=(a b c)

双小括号(())

常用于算数运算比较,双括号中的变量可以不使用$符号前缀,括号内支持多个表达式用分号;隔开。括号中的表达式需要符合c语言运算规则,比如for循环 for ((i=1;i>10;i++))。再如if (($i<10))如果不使用双括号,则为if [ $i -lt 10 ]

中括号[]

  1. 单中括号,bash的内部命令,与test相同。
  2. 双中括号
    • 是bash程序语言的关键字,并不是一个命令,双中括号比单中括号的结构更加通用,在双中括号中所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数和命令替换。
    • 支持字符串模式匹配,使用=~操作符时甚至支持shell正则表达式,字符串比较时可以把右边作为一个模式,而不仅仅是一个字符串,比如[[ hello == hell?]],结果为真,[[]]中匹配字符串或通配符,不需要引号。
    • 使用双括号条件判断结构,能够防止脚本中许多逻辑错误,比如&&,||操作符能够正常存在于[[]]条件判断结构中,但是如果出现在单中括号中会报错,比如可以直接使用 if [[ $a -ne 1 && $a -ne 2 ]],如果不使用单括号则为if [ $a -ne 1 ]&&[ $a -ne 2 ]或者 if [ $a -ne 1 -a $a -ne 2 ]
    • bash中把双括号中的表达式看作一个单独的元素,并返回一个退出状态码

大括号

常规用法

  1. 大括号拓展。(通配(globbing))将对大括号中的文件名做扩展。在大括号中,不允许有空白,除非这个空白被引用或转义。第一种:对大括号中的以逗号分割的文件列表进行拓展。如 touch {a,b}.txt 结果为a.txt b.txt。第二种:对大括号中以点点(..)分割的顺序文件列表起拓展作用,如:touch {a..d}.txt 结果为a.txt b.txt c.txt d.txt
  2. 代码块,又被称为内部组,这个结构事实上创建了一个匿名函数 。与小括号中的命令不同,大括号内的命令不会新开一个子shell运行,即脚本余下部分仍可使用括号内变量。括号内的命令间用分号隔开,最后一个也必须有分号。{}的第一个命令和左括号之间必须要有一个空格。

符号$后的括号

  • $(a)变量a的值,在不引起歧义的情况下可以省略大括号
  • $(ls -l)命令替换,如docker中docker rm $(docker ps -qa)批量删除所有容器
  • $(())用于获得计算结果。echo $((3-2))

变量

以固定的名称,存放可以能有变化的值

定义变量的格式:

变量名=变量值

取消变量的格式

unset 变量名

注意事项:

  • =两边不能有空格,不要使用关键字做变量名。
  • 如果变量名已经存在则覆盖之前的变量值。
  • 变量名称有:字母/数字/下划线组成,不能以数字开始,不可以使用特殊字符。

查看变量的语法格式:

$变量名

${变量名}

可以使用echo打印出来变量的值

使用unset 变量名取消变量

环境变量

变量名通常大写,有操作系统维护

环境变量存储在/etc/profile或~/.bash_profile

命令env可以列出所有环境变脸

常见的环境变量有:

1
2
3
4
5
6
7
8
9
10
11
12
PATH 
# 命令搜索路径
PWD
# 当前工作目录
USER
# 当前登录用户的用户名
UID
# 当前登录用户的ID号
HOME
# 当前用户的家目录
SHELL
# 当前用户使用的shell解释器,大部分是/bin/bash

位置变量

bash内置变量,存储脚本执行时的参数

使用$n表示,n为数字序列号

在$1~$9代表第1~9参数,10和10以上的参数需要用大括号包含,如${10}

$1,$2,$3,…,${10},${11},…

预定义变量

bash内置变量,可以调用但是不能赋值或修改

用来保存脚本程序的执行信息

直接使用这些变量

不能为这些变量赋值

变量名含义
$0表示当前所在的进程或脚本名
$$当前运行进程的PID
$?命令执行后的返回状态,0表示正常,1或其他表示异常
$#已加载的位置变量的个数
$*所有位置变量的值

自定义变量

用户自主设置的变量

变量名=值

变量的扩展及应用

各类引号的作用

shell脚本中我们常用的有三种引号,双引号,单引号,反引号

双引号" ":允许扩展,以$引用其他变量

单引号' ': 禁用扩展,及时$引用也视作普通字符

反引号` `: 将命令的执行输出作为变量值,$()与反引号等效

read命令定义变量

read从键盘读入变量值完成赋值

格式:read -p "提示信息" 变量名

选项:

  • -p 提示信息
  • -t 指定超时秒数
  • -s 设置是否在终端显示输入的内容

全局变量和局部变量

局部变量:新定义的变量默认只在当前shell中有效,无法在子shell环境中使用

全局变量:全局变量在当前shell及子shell和未来开启的子shell环境中均有效

1
2
3
4
变量名=值
# 定义局部变量
export 变量名=值
# 定义全局变量

运算

整数运算

四则运算:

  • 加法:num1 + num2
  • 减法:num1 - num2
  • 乘法:num1 * num2
  • 整除:num1 / num2

取余数运算:

  • 求余数:num1 % num2

使用$[]和$(())表达式

语法格式:

1
2
3
4
$[整数1 运算符 整数2]

echo $[1+2]
echo $((1+2))
完整表达式简写表达式
i=i+1i++
i=i-1i–
i=i+2i+=2
i=i-2i-=2
i=i*2i*=2
i=i/2i/=2
i=i%2i%=2

小数运算

  • bash内仅支持整数运算,不支持小数运算
  • 我们可以通过计算器软件bc实现小数运算

如果没有该软件需要安装。centos系统安装方法

1
yum install -y bc

bc支持交互式和非交互式两种方式计算,scale=n可以约束小数位

交互式运算直接bc回车就可以

非交互式计算

1
2
3
4
5
6
7
8
9
10
[root@yyt ~]# echo "1.2+1.1" |bc
2.3
# 使用echo命令把要运算的式子通过管道符交给bc计算器
[root@yyt ~]# echo "1.2+1.3+2.5;1.2+1" |bc
5.0
2.2
# 需要多个运算公式可以用分号号:; 隔开
[root@yyt ~]# echo "scale=2;10/2" |bc
5.00
# 需要约束小数,提前使用scale=n然后用分号隔开后面跟上计算公式

bc 支持比较操作符:>、>=、<、<=、==、!=

表达式成立则返回1,否则返回0

条件测试

语法格式:

  • test 选项 参数
  • [选项 参数] (日后推荐使用这种)

字符串的比较

1
2
3
4
5
6
7
8
9
# 注意使用[]是选项和参数的两边都必须要有一个空格存在
判断字符串是否为空
[ -z 字符串 ]

判断两个字符串等于
[ 字符串1 == 字符串2 ]

判断两个字符串不等于
[ 字符串1 !\= 字符串2 ]

整数值比较

1
[ 整数值1 操作符 整数值2 ]
操作符含义
-eq等于
-ne不等于
-ge大于或等于
-le小于或等于
-gt大于
-lt小于

文件目录状态测试

1
[ 操作符 文件或目录]
操作符含义
-e判断文件或目录是否存在,若存在则结果为真
-d判断对象是否为目录,是则为真
-f判断对象是否为普通文件,是则为真
-r判断文件或目录是否有可读权限,是则为真
-w判断文件或目录是否有可写权限,是则为真
-x对象是否有可执行权限,是则为真

控制操作符

使用控制符组合多个命令

1
2
3
4
5
6
7
8
9
;
示例:lsls -l
# 分号分隔命令,如果使用分号,就表示按顺序执行,没有逻辑关系
&&
示例:ls && ls -l
# 使用两个&符号,也是按顺序执行,但只有当前面一条命令执行成功时,才会执行后面的命令
||
示例:ls || ls -l
# 代表或者如果||前面的命令执行成功了,后面则不会执行,前面的失败了。才会执行后面的命令

监控脚本

tr -s 删除多余重复的字串(如空格)

1
2
3
4
5
ls | tr -s " " 
# 删除ls命令多余的空格

echo "aaaaaaaaacab" |tr -s "a"
# 删除多余的a

cut命令过滤数据

cut -d "以什么分隔" -f数字(第几个)

1
2
cut -d ":" -f1 /etc/passwd
# 以冒号分隔,过滤第一列
image-20230403104945916
image-20230403105621186
image-20230403105850077

if语句

单分支

语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 第一种写法

if 条件测试
then 命令序列
fi

# 第二种写法
if 条件测试;then
命令序列
fi
# 我比较喜欢第二种写法

[root@yyt ~]# if [ $USER == root ];then echo "您当前使用的是管理员用户" ;fi
image-20230403110829345

双分支

1
2
3
4
5
if 条件测试;then
命令序列1
else
命令序列2
fi

当条件成立时执行命令序列1,否则执行命令序列2

多分支

1
2
3
4
5
6
7
8
9
if 条件判断1;then
命令序列1
elif 条件判断2;then
命令序列2
elif 条件判断3;then
命令序列3
else
命令序列n
fi

当条件判断1满足就会执行then后面的命令序列1.如果不满足则否则去判断条件判断2.在不满足就是3,全部不满足了执行else里面的命令序列n。

for循环

根据变量的不同取值,重复执行命令序列

1
2
3
4
for 变量 in 值列表
do
命令序列
done

值列表可以是一个值,或多个值。循环多少次取决于值列表里面有多少值。

1
2
3
4
for ((初值;条件;步长))
do
命令序列
done

while循环

1
2
3
4
5
6
7
8
9
while 条件测试
do
命令序列
done

while 未猜中正确的价格
do
反复猜商品价格
done

反复测试条件,只要成立就执行命令序列

简单的例子

1
2
3
4
5
6
7
# 只要i小于等于1000就让他一直打印yyt

i=1
while [ $i -le 1000 ];do
echo "yyt"
let i++
done

case语句

检查,判断变量的取值

  • 功能类似于多分之的if语句
  • 如果与预设的值相匹配,则执行对应的操作
  • 命令序列最后必须以分号结尾
1
2
3
4
5
6
7
8
9
case 变量 in
模式1)
命令序列1;;
模式2)
命令序列2;;
... ...
*)
默认命令序列
esac

命令序列可以打很多个命令,但是需要注意如果只有一条命令,必须使用双分号结尾,如果有多条命令,那么最后一条命令必须以双分号结尾。

双分号是结束符

示例

获取输入,输入redhat返回fedora,输入fedora返回redhat,输入其它,返回必须输入指定要求的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

read -p "请输入redhat|fedora:" key
case $key in
redhat)
echo "fedora";;
fedora)
echo "redhat";;
*)
echo "必须输入redhat或fedora"
esac

# *)中的命令序列不需要双分号,因为这已经是结尾了,但是加上也可以正常使用

在case语句中的模式)前还可以写

1
2
3
Y|y|yes|YES)
N|n|NO|no)
|代表或者的意思

数组

数组也是一个变量,是一个有点特殊的变量

存储多个数据的集合就是数组

1
2
3
4
5
6
7
8
9
[root@yyt ~]# a1=(11 22 33) #定义一个数组
[root@yyt ~]# echo ${a1[0]} #调用数组的值
11
[root@yyt ~]# echo ${a1[1]}
22
[root@yyt ~]# echo ${a1[2]}
33
[root@yyt ~]# echo ${a1[*]}
11 22 33

shell函数

在shell环境中,将一些需要重复使用的操作操作,定义为公共的语句块,讲某一段代码取一个名称就是函数。

语法格式1

1
2
3
4
function 函数名{
命令序列
... ...
}

语法格式2

1
2
3
4
函数名(){
命令序列
... ...
}

调用已定义的函数

  • 格式: 函数名

  • 函数传值

    • 格式:函数名 值1 值2 …
    • 传递的值作为函数的”位置参数”

示例

1
2
3
4
5
6
7
8
[root@yyt ~]# imsg(){
echo "hello world"
echo "compute cloud"
}

[root@yyt ~]# imsg
hello wprld
compute cloud

另一种格式

1
2
3
4
5
6
7
8
[root@yyt ~]# function msg{
echo "hello world"
echo "compute cloud"
}

[root@yyt ~]# msg
hello wprld
compute cloud

输出颜色

1
2
3
4
5
6
7
8
#!/bin/bash
cecho(){
echo -e "\033[$1m$2\033[0m"
}
cecho 33 ok
cecho 34 ok
cecho 35 ok
cecho 36 ok

多进程版ping

image-20230404184845951

中断与退出

  • continue 可以结束单次循环
  • break可以结束循环体
  • exit可以退出脚本
image-20230404185308707 image-20230404185529679 image-20230404185751457

image-20230404185901348

字符串的处理和变量初始化

字符串的处理

字符串的截取

image-20230405101745747

语法格式:

1
2
3
${变量:起始位置:长度}

# 注意起始长度是从0开始的

示例:

1
2
3
4
5
6
7
[root@yyt ~]# s=1234567
# 从0的位置开始截取,截取三位
[root@yyt ~]# echo ${s:0:3}
123
# 从3的位置开始截取,截取一位
[root@yyt ~]# echo ${s:3:1}
4

字符串的替换

替换一个结果

${变量/旧字符/新字串}

替换全部结果

${变量//旧字串/新字串}

image-20230405103056506

字符串掐头

image-20230405103258988

字符串去尾

image-20230405103529216

批量更改扩展名

image-20230405103753666 ## 变量初始化

原变量有值,返回该变量的值

原变量无值,返回初始值

语法格式

1
${变量:-关键词}
image-20230405104226200
1
2
3
4
5
[root@yyt ~]# a=123
[root@yyt ~]# echo ${a:-yyt}
123
[root@yyt ~]# echo ${b:-yyt}
yyt

随机密码

image-20230405105016154
1
2
3
4
5
6
7
# 环境变量

RANDOM 范围是0--32767
# 查看上一次产生的随机数
set |grep RANDOM
如果我们想产生0-25范围内的数:$(($RANDOM%26))
产生1-68的数:$(($RANDOM%68+1))
image-20230405111339554 image-20230405111411103

正则表达式

grep语法格式

1
2
3
4
5
6
grep 选项 匹配模式 文件

-i 忽略大小写
-v 取反匹配
-w 匹配单词
-q 静默匹配不将结果显示在屏幕

描述一个字符集合的表达方式

模糊匹配

正则符号描述
abc匹配abc
^匹配开头
$匹配结尾
[集合]匹配集合中的任意单个字符
[^集合]对集合取反
.代表任意单个字符
*匹配前一个字符任意次(包含0次)
.*匹配任意长度的任意字符
\{n,m\}匹配前一个字符n到m次
\{n,\}匹配前一个字符至少n次
\{n\}匹配前一个字符n次

例如

1
2
3
4
grep "[0-9]\{3,4}"
# 意思就是想在一个文件里面找数字,需要的是3-4位数字
grep "[0-9]\{3\}"
# 我要的数字必须正好是三位

扩展正则

就是把基本正则给简化了

正则符号描述
+匹配前面的字符至少一次
?匹配前面的字符0或1次
()组合与保留
|或者
{n,m}匹配前面的字符n到m次
{n,}匹配前面的字符至少n次
{n}匹配前面的字符n次

注意:grep默认不支持扩展正则需要加上-E参数。

Perl兼容正则

特点都是以右斜线开头

正则符号描述
\b匹配单词边界
\w匹配字符数字下划线
\W和\w相反
\s匹配空白
\d匹配数字
\d+匹配多个数字
\D匹配非数字

注意:默认的grep不支持perl正则需要加上-P参数

\b的示例

image-20230406094928518

sed基础

流式编辑器

  • 非交互式
  • 逐行处理
  • 可以文本进行增删改查等操作

语法格式

1
2
3
4
5
6
7
sed [选项] '[定位符]指令' 文件名
命令 | sed [选项] '[定位符]指令'

sed -n '3p' /etc/passwd
-n 屏蔽默认输出,不要打印全部出来
sed -n '1,3p' /etc/passwd
表示打印从第一行到第三行

常用命令选项

命令选项描述
-n使用安静模式,加上-n选项后,只有经过sed特殊处理的那一行才会被列出来。
-e允许在该选项后添加一条新的编辑指令,当有多条编辑指令,时应该使用该选项逐一添加。
-i直接修改读取文件内容,而不是由屏幕输出。(常用)
-r支持扩展正则表达式。
-f直接将sed的操作写在一个文件内,-f 文件名即可执行文件里面的sed操作
-h输出sed的帮助信息

数据定位

行号定位

sed可以使用行号;来定位自己需要修改的数据内容

1
2
3
4
5
sed -n '3p'  # 打印第3行
sed -n '1,3p' # 打印1到3行
sed -n '1~2p' # 从第1行开始步长为2,也就是第3行第5行会被打印出来
sed -n '2~2p' # 从第2行开始,步长为2
sed -n '2,+3p' # 第二行及后面的三行

正则定位

sed可以使用正则匹配需要的数据,然后再编辑对应的内容。

操作子命令

操作子命令指示sed对指定行进行何种操作,包括打印,删除,修改等。

命令描述
p打印输出被选中的行
d删除被选中的行
a 字符串字符串单独作为一行追加到被选中的行之后
c 字符串将选中的行替换替换为字符串
i 字符串在被选中的行之前插入字符串。
s/字符串1/字符串2/标志查找替换,通常s可以搭配正则表达式,将字符串1替换为字符串2
r 文件名在选中的行之后追加文件的内容
w 文件名将选择的行内容写入文件
=打印当前行号

子命令d:删除行演示

以etc下面的passwd文件为例

注意,这里只是演示时临时删除并没有真正删除

  • 删除所有行

    1
    cat -n /etc/passwd |sed 'd'
  • 删除第一行

    1
    2
    3
    4
    5
    6
    7
    [root@yyt ~]# cat -n /etc/passwd |sed  '1d'
    2 bin:x:1:1:bin:/bin:/sbin/nologin
    3 daemon:x:2:2:daemon:/sbin:/sbin/nologin
    4 adm:x:3:4:adm:/var/adm:/sbin/nologin
    5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    6 sync:x:5:0:sync:/sbin:/bin/sync
    7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
  • 删除第2行到最后一行

    1
    2
    [root@yyt ~]# cat -n /etc/passwd |sed '2,$d'
    1 root:x:0:0:root:/root:/bin/bash

子命令a:追加内容

子命令a表示在指定行下边插入指定行的内容。

  • 在/etc/hosts每行之下后插入一行,内容为A

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [root@yyt ~]# cat -n /etc/hosts |sed 'a A'
    1 ::1 localhost
    A
    2 127.0.0.1 localhost
    A
    3
    A
    4 172.25.101.112 yyt yyt
    A
    5
    A
  • 在/etc/hosts第一行之下插入1行,内容为A

    1
    2
    3
    4
    5
    6
    7
    [root@yyt ~]# cat -n /etc/hosts |sed   '1a A'
    1 ::1 localhost
    A
    2 127.0.0.1 localhost
    3
    4 172.25.101.112 yyt yyt
    5
  • 在/etc/hosts第一行之下插入多行,内容用\n换行符换行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [root@yyt ~]# cat -n /etc/hosts |sed   '1a A\nB\nC'
    1 ::1 localhost
    A
    B
    C
    2 127.0.0.1 localhost
    3
    4 172.25.101.112 yyt yyt
    5

子命令i:插入内容

子命令ia功能类似,只不过是在指定行上边插入指定行的内容。

  • 在/etc/hosts第1行之上插入1行,内容为A

    1
    2
    3
    4
    5
    6
    7
    [root@yyt ~]# cat -n /etc/hosts |sed   '1i A'
    A
    1 ::1 localhost
    2 127.0.0.1 localhost
    3
    4 172.25.101.112 yyt yyt
    5

子命令c:内容替换

c是表示把指定行内容替换为自己需要的行内容

  • 将/etc/hosts中每行的内容都替换为A

    1
    2
    3
    4
    5
    6
    [root@yyt ~]# cat -n /etc/hosts |sed   'c A'
    A
    A
    A
    A
    A
  • 将/etc/hosts中第一行的内容替换为A

    1
    2
    3
    4
    5
    6
    [root@yyt ~]# cat -n /etc/hosts |sed   '1c A'
    A
    2 127.0.0.1 localhost
    3
    4 172.25.101.112 yyt yyt
    5

子命令s:替换内容

子命令s为替换子命令,是sed命令使用频率最高的子命令,没有之一,因为支持正则表达式,所有功能变得强大无比

基本语法:s/模式/替换字符串/标志

注意:/ 可以换成其它的符号。

子命令s支持的标志如下。

  • g全局更改。
  • n:可以是1-512,表示第n次出现的情况进行替换
  • w 文件名:写入到一个文件file中。