Bash/Shell学习笔记
Bash脚本的声明
在我们写.sh文件时要在文档头部做出#!/bin/bash
的声明。
1 |
|
如上所示,写出声明后我们的bash脚本才可以正常的运行。
其实第一行#!/bin/bash
写错或者不写时,系统会有一个默认的解释器进行解释,此处为bash,但是为了标准化我们要加上它!
笔记其余的地方我都会省略这个声明,但是在读者写代码时记得要在你自己写的代码里加上这段声明~
变量
定义变量
var=Hello World!
就是这么简单就声明了一个全局变量。varnum=32
但是要注意的是:这样直接声明的变量不论存入的数据是什么类型,都会当做字符串对待。- 例如:
1 | varnum=10 |
可以看出,直接对变量进行增加或进行数学运算是不行的,因为直接声明的变量的类型就是字符串!如果想对变量进行数学运算那么你可能需要declare
来帮助你给定义好的变量加上它们应有的“变量类型”
定义有类型的变量
declare [+/-][rxi][变量名称=设置值]
- 参数说明:
- +/-:+ 为取消相应的属性 - 为添加相应的属性
- rxi:r 为只读变量(常量) x 为环境常量(在Bash脚本中也可以使用外面的定义的变量) i 为数值类型
例如:
1
2
3
4
5declare -i numsum
numtest=30
numsum=$numtest+10
echo $numsum
输出:40
可以看出如果我们给numsum这个变量设置好了他的类型,那么在输出的时候就会根据类型对其进行输出。declare
也可以更改已经声明好的变量的类型,不是必须要通过declare
声明的变量才有效果。
例如:
1
2var=300
declare -i var
也可以将var变量设置为数值型。
注意:假如上面的var这个变量值为文本型或不是数值型或数学表达式的话会将其原有的变量值替换为0,而且无法找回原来的变量内容!
变量的删除
${ variable#[匹配符] }
从这个变量的前面开始删除存储的信息,删除符合匹配条件的第一个。${ variable##[匹配符] }
从这个变量的前面开始删除存储的信息,删除符合匹配条件的最长匹配${ variable%[匹配符] }
从这个变量的后面开始删除存储的信息,删除符合匹配条件的第一个${ variable%%[匹配符] }
从这个变量的后面开始删除存储的信息,删除符合匹配条件的最长匹配
变量的替换
var1=${ variable/要替换的内容/替换为 }
从这个变量前面仅寻找要替换的第一个内容,之后替换为新的内容并赋值到一个新变量上。var2=${ variable//要替换的内容/替换为 }
从这个变量前面寻找要替换的全部内容,之后替换为新的内容并赋值到一个新变量上。
1 | var1=${ PATH/bin/Bin } |
局部变量和全局变量
- 不做特殊声明,Shell中变量都是全局变量
局部变量
定义局部变量时,使用local关键字
如果函数内与函数外变量名重复,那么在函数内会覆盖函数外的全局变量。
局部变量使用
local
关键字声明local var_local="Hello World"
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15var1="Hello World"
function new
{
local var2="Local Var"
var3="Public Var"
}
# 调用函数
new
echo var1=$var1
echo var2=$var2
echo var3=$var3
输出
var1=Hello World
var2=
var3=Public Var通过例子可以看出用
local
关键字声明出的var2变量在其所在函数外是无法使用的。
变量的测试
很少会用到,一般来说我们使用if-else来进行替换变量的测试,只不过if-else麻烦一些。变量的检查一般用于初始化变量。
1 | str1=test1 |
字符串的处理
计算字符串的长度
echo ${ #varstr }
输出varstr字符串变量的字符长度
1 | varstr=teststring-HelloWorld! |
抽取子串
${ varstr:num1 }
截取从 num1 开始到末尾的所有字符${ varstr:num1:num2 }
从 num1 开始截取之后的 num2 个字符${ varstr: -num1 }
从末尾向前截取从倒数 num1 个字符开始到末尾的所有字符(注意冒号后有空格)${ varstr: -num1:-num2 }
从末尾向前截取 num2 到 num1 之间的所有字符(注意 -num1 要小于 -num2,下面有例子方便理解)${ varstr: -num1:num2 }
从末尾倒数第num1个字符开始向后截取 num2 个字符${ varstr:num1:-num2 }
从开头第 num1 个的字符截取到倒数第 num2 个字符
注意:以这种方式抽取字符串索引是从0开始的,上面所提到的所有数字都是以索引的形式体现。所有符号都是英文,并且在num为负值时在冒号(:)的后面要有一个空格
1 | varstr=Hello,World! |
命令替换和数学运算
命令替换
实现 | 备注 | |
---|---|---|
方法一 | $() | |
方法二 | `` | 注意是反引号,不是单引号 |
在上面已经用到了命令替换,简单来说在用echo输出的时候如果使用命令替换,就会先执行命令,之后把返回值再输出回来。
- 比如:获得今年是第几年
echo 今年是第 $(date +%Y) 年
- 输出 今年是第 2019 年
数学计算
因为数学计算和命令替换的bash语法很相近,所以一起学习效果会好些吧。
1、用$(( ))来表达数学计算,和命令替换一起使用时要注意括号的个数
- 比如:获得明年的年数
echo 明年是第 $(( $(date +%Y) +1))年
- 输出 明年是第 2020年
2、使用bc来处理浮点型数学运算
- 比如:
echo "3.14*3.14" | bc
- 输出:9.85 (黑人问号?不对啊,怎么精度只有2位?)
注意咯,使用bc时默认保留小数点两位,如果可以整除的时候这两位也给你省去了!如果想保留更高精度的数,你需要使用scale
。
浮点数的小数位数是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位,否则无法得到期望的结果
- 比如:
echo "scale=6;3.14*3.14" | bc
- 输出:9.8596
- 精度设置为小数点后6位,但是后两位位是0,所以隐藏掉了。
函数
定义函数
定义函数有两种方法:
1 | #第一种 |
函数的返回值
函数有两种返回值的方法
代码 | 备注 |
---|---|
return | return通常用来返回执行状态!执行成功返回 0 执行失败返回 1 ,返回值范围(0~255) |
echo | echo通常用来返回字符串或一个列表 |
函数库
- 使用函数库,我们可以避免对一段代码重复的书写,我们可以将常有的一些方法封装到函数库中。
- 写好的函数库一般不直接执行,而是由其他的脚本进行调用。
例如现在要定义一个函数库,要满足以下几个函数:
1、加法函数add
2、减法函数reduce
3、乘法函数multiple
4、除法函数divide
5、打印系统的运行情况的函数sys_info,该函数用来显示内存运行情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function add
{
echo $(($1+$2))
}
function reduce
{
echo $(($1-$2))
}
function multiple
{
echo $(($1\*$2))
}
function divide
{
echo $(($1/$2))
}
function sys_info
{
echo "内存的情况:"
echo `free -m`
echo "================================="
echo "硬盘剩余空间:"
echo `df -h`
}
OK,我们写好了一个函数库,把它命名为lib_test。现在需要来写一个普通脚本去调用我们的函数库。
1 |
|
条件、判断、分支
if判断
1 | if [ 条件语句 ];then |
- bash/shell 对空格非常敏感
- 如果按照下面的例子练习if语句时运行报错那么请耐心仔细检查空格或语法。
现在我们通过一个实例来学习if
要求:
- 1、检测nginx服务是否正在运行
- 2、如果检测到nginx服务已经宕掉,那么重新启动它!
- 3、在测试的时候先把nginx服务手动停止
service nginx stop
1 |
|
while循环
刚才学习了if条件判断,我们可以来监测nginx服务是否宕掉,但是它需要我们每次都执行它这个脚本才能够去检测nginx。那么如果知道了while循环,是不是就可以循环去监测它了?
1 | while 条件语句 |
例子要求:那么我们在if的例子基础上来完善一下它,让它每隔1分钟就检测一下Nginx服务
1 | while true |
我们使用while-true死循环来重复监测Nginx服务,每次循环过后都休眠60s后再次执行循环体!
sed 流编辑器
第一种形式:stdout | sed [option] "pattern command"
第二种形式:sed [option] "pattern command" fileaddress
stdout
意思为通过管道|
将文本传入给后面的命令。
简介:
Linux sed 命令是利用脚本来处理文本文件。
sed 可依照脚本的指令来处理、编辑文本文件。
sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。
选项 option(常用)
选项 | 含义 |
---|---|
-n | 只打印模式匹配行 |
-e | 直接在命令行进行sed编辑,默认选项 |
-f | 编辑的操作方式保存在文件中,指定文件执行 |
-r | 支持扩展正则表达式 |
-i | 直接修改文件内容(将所作的修改保存到文件中) |
需要注意的是sed在处理文本时默认每一行都会输出出来,如果只想打印与自己设置好的匹行,需要选项中使用-n
1 | # 我们先创建一个用来测试的文本 |
sed 的匹配模式
匹配模式 | 作用 | 注意 |
---|---|---|
8p |
打印第8行的内容 | |
8,10p |
打印第8行到第10行的内容 | 包含第8行和第10行 |
8,+5p |
打印第8行和其之后的5行 | 包含第8行 |
/正则表达式/p |
打印匹配正则表达式的行 | |
/正则表达式_1/,/正则表达式_2/p |
打印匹配正则表达式1和2之间的行 | |
8,/正则表达式/p |
打印第8行起直到匹配到正则表达式匹配行之间的所有内容 | |
/正则表达式/,8p |
打印从匹配到正则表达式开始到第8行之间的所有内容 |
sed 的增删改查
功能 | 指令 | 备注 |
---|---|---|
查 | p |
打印匹配的内容 |
删 | d |
删除匹配的内容 |
增 | a |
在匹配行的后一行追加内容 |
增 | i |
在匹配行的前一行增加一行内容 |
改 | s/匹配模式/要修改的内容/ |
查找符合匹配模式的字符串,将其匹配到的第一个字符串进行替换 |
改 | s/匹配模式/要修改的内容/g |
查找符合匹配模式的字符串,将匹配到的所有的字符串都进行替换 |
改 | s/匹配模式/要修改的内容/2g |
只替换从第二个符合匹配模式后面的所有字符串 |
改 | s/匹配模式/要修改的内容/ig |
忽略大小写查找符合匹配模式的字符串,将匹配到的所有字符串都进行替换 |
sed 的显示行号
=
可以显示输出的行号
例如:sed = /etc/passwd
以上是sed流编辑器的学习内容和笔记。
awk 文本处理器
awk 是一个文本处理工具,通常用于处理数据或者生成结果报告
第一种形式:awk [BEGIN{ }]匹配模式{ command命令 }[END{ }] 文件路径
第二种形式:stdout | awk [BEGIN{ }]匹配模式{ command命令 }[END{ }]
stdout
意思为通过管道|
将文本传入给后面的命令。
语法格式说明
语法格式 | 解释 |
---|---|
BEGIN{ } |
正式处理数据之前执行 |
pattern |
匹配格式 |
{ command } |
处理命令 |
END |
处理完所有匹配数据后执行 |
一行简单的awk命令:awk 'BEGIN{ FS=":" }{ print $1 }' /etc/passwd
它输出的是passwd中所有用户的用户名。那么FS
,print
,$1
…都代表什么意思?这里引入awk的内置变量以及command命令
内置变量
内置变量 | 含义 |
---|---|
$0 |
整行内容 |
$1-$n |
当前行的第1-n个字段 |
NF |
当前行的字段个数,也就是有多少列 |
NR |
当前行的行号,从1开始计数 |
FNR |
在处理多个文件时,每个文件的行号单独计数不累加,每个文件从1开始计数 |
FS |
输入字段的分隔符。不指定时默认以空格或者tab键分割 |
RS |
输入行的分隔符。不指定时默认为回车换行 |
OFS |
输出字段分隔符。默认为空格 |
ORS |
输出行分隔符。默认为回车换行 |
格式化输出 printf
格式符 | 含义 |
---|---|
%s |
打印字符串 |
%d |
打印10进制数 |
%f |
打印浮点数 |
%x |
打印16进制数 |
%o |
打印8进制数 |
%e |
打印数字的科学技术法格式 |
%c |
打印单个字符的ACSII码 |
修饰符 | 含义 |
---|---|
- |
左对齐 |
+ |
右对齐 |
# |
显示8进制在前面加0,16进制在前面加0x |
例子:
1 | # 1、以字符串的形式打印/etc/passwd中的第7个字段,以“:”作为分隔符。 |
awk 匹配模式
- 第一种匹配模式:正则表达式匹配
- 第二种匹配模式:关系运算匹配
正则表达式匹配
1、匹配/etc/passwd文件行中含有root字符串的所有行awk 'BEGIN{ FS=":" }/root/{ print $0 }' /etc/passwd
2、匹配/etc/passwd文件行中以sshd开头的所有行awk 'BEGIN{ FS=":" }/^sshd/{ print $0 }' /etc/passwd
关系运算符匹配
关系运算符 | 解释 |
---|---|
< | 小于 |
> | 大于 |
<= | 小于等于 |
>= | 大于等于 |
== | 等于 |
!= | 不等于(可用于字符串) |
~ | 匹配正则表达式 |
!~ | 不匹配正则表达式 |
匹配表达式(只与关系运算符连用) | 解释 |
---|---|
|| | 或 |
&& |
与 |
! |
非 |
例子:
- 1、以
:
为分隔符,匹配/etc/passwd文件中第三个字段小于50的所有行信息awk 'BEGIN{ FS=":" }$3<50{ print $0 }' /etc/passwd
- 2、以
:
为分隔符,匹配/etc/passwd文件中第三个字段等于0的行信息awk 'BEGIN{ FS=":" }$3==0{ print $0 }' /etc/passwd
- 3、以
:
为分隔符,匹配/etc/passwd文件中第七个字段不等于/bin/bash
所有行信息awk 'BEGIN{ FS=":" }$7!="/bin/bash"{ print $0 }' /etc/passwd
- 4、通过匹配正则表达式,匹配/etc/passwd中以root开头的所有行信息
awk 'BEGIN{ FS=":" }$1~/^root/{ print $0 }' /etc/passwd
- 5、以
:
为分隔符,匹配/etc/passwd文件中第一个字段等于root或sshd的所有行信息awk 'BEGIN{ FS=":" }$1=="root"||$1=="sshd"{ print $0 }' /etc/passwd
awk中的判断与循环
if判断
awk的if判断与C语言基本一致。下面是awk中if语句格式的举例:
1 | if($3>50) |
如果现在要求输出UID大于50的用户信息要咋办呢?可以看下面的这个例子。
1 | awk 'BEGIN{ FS=":" } |
将上面的命令直接复制到终端里便可以运行,但是这样是不是编写起来有些不太方便?
所以我更愿意将上面的一些命令抽象出一个awk脚本,然后让awk -f
去运行这个脚本,这样思路和结构也会更清楚一些。
那现在来建立一个awk脚本:vim if-else.awk
进入编辑模式,键入以下内容:
1 | BEGIN{ FS=":" } |
保存后我们可以使用-f
选项来使用这个awk脚本:
awk -f if-else.awk /etc/passwd
这样一来命令也很清晰很多了,舒服的一批!
awk的循环
拿一个从1一直加到100的这个小学问题来当做学习循环的例子吧~
1、while循环:
vim while.awk
1 | BEGIN{ |
awk -f while.awk
- 输出:5050
2、do-while循环:
vim do-while.awk
1 | BEGIN{ |
- 输出:5050
3、for循环:
vim for.awk
1 | BEGIN{ |
- 输出:5050
我是一名Linux初学者,如果你与我一样喜欢折腾,喜欢Linux,那么请加入我的电报群https://t.me/yeefire_blog,在这里畅所欲言,共同学习进步。