Shell编程基本使用
Shell概述
Shell是一个命令行解释器
,他接收应用程序/用户命令,然后调用操作系统内核。
Shell还是一个功能相当强大的编程语言,易编写,易调试,灵活性强。
Linux提供的Shell解释器有
1 | 查看Linux支持的shell解释器 |
bash和sh关系
1 | 进入bin目录 |
CentOS默认解析器bash
1 | 查看CentOS7默认解析器 |
Shell脚本入门
脚本格式
脚本以#!/bin/bash
开头(指定解析器)
第一个Shell脚本
创建一个shell脚本,输出helloworld
1 | 创建文件夹 |
脚本内容
1 | !/bin/bash |
脚本执行
采用
bash
或sh
+脚本的相对路径或绝对路径(不需要赋予脚本+x
权限)1
2
3
4
5
6
7
8
9
10
11sh+脚本相对路径
sh ./helloworld.sh
sh+绝对路径
sh /root/script/helloworld.sh
bash+脚本相对路径
bash ./helloworld.sh
bash+绝对路径
bash /root/script/helloworld.sh采用输入脚本的绝对路径或相对路径执行脚本(必须具有可执行权限
+x
)1
2
3
4
5
6
7
8赋予脚本可执行权限
chmod +x helloworld.sh
相对路径
./helloworld.sh
绝对路径
/root/script/helloworld.sh在脚本前加上
.
或source
1
2
3
4
5. 执行脚本
. helloworld.sh
source执行脚本
source helloworld.sh
- 前两种方式都是在当前shell中打开一个子shell来执行脚本内容,当脚本内容结束,则子shell关闭,回到父shell中。
- 第三种,也就是使用在脚本路径前加“.”或者source的方式,可以使脚本内容在当前shell里执行,而无需打开子shell!这也是为什么我们每次要修改完/etc/profile文件以后,需要source一下的原因。
- 开子shell与不开子shell的区别就在于,环境变量的继承关系,如在子shell中设置的当前变量,父shell是不可见的。
注意:第一种执行方法,本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限。第二种执行方法,本质是脚本需要自己执行,所以需要执行权限。
变量
系统预定义变量
常用系统变量
$HOME、$PWD、$SHELL、$USER等
操作
查看系统变量的值
1
2
3
4echo $HOME
echo $PWD
echo $SHELL
echo $USER显示当前Shell中所有变量:set
1
set
自定义变量
基本语法
- 定义变量:变量名=变量值。注意:=号前后不能有空格
- 撤销变量:unset 变量名
- 声明静态变量:readonly变量。注意:静态变量不能unset(撤销变量)
自定义变量规则
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写。
- 等号两侧不能有空格
- 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算。
- 变量的值如果有空格,需要使用双引号或单引号括起来。
操作
定义变量
1
2
3
4
5定义变量(下面实操不需要进入Shell脚本,直接命令行操作即可)
A=5
输出变量
echo $A给变量重新赋值
1
2
3
4
5给变量重新赋值
A=8
输出变量
echo $A撤销变量
1
2
3
4
5撤销变量
unset A
输出变量
echo $A声名静态变量,不能使用unset删除,也不能对其进行修改
1
2
3
4
5设置静态变量
readonly B=2
输出静态变量B
echo $B在bash中,变量默认类型都是字符串类型,无法直接进行数值运算
1
2
3
4
5设置变量进行算术运算
C=1+2
输出变量
echo $C变量的值如果有空格,需要使用双引号或单引号括起来
1
2
3
4
5
6
7设置变量
D='Hello World'
E="Hello World"
输出变量
echo $D
echo $E将变量提升为全局变量,可供其他Shell程序使用
1
2
3
4
5
6
7
8
9
10
11格式
export 变量名
设置变量
F=20
提升变量为全局变量
export F
设置脚本内容为如下内容
vim helloworld.sh1
2
3!/bin/bash
echo "helloworld"
echo $F1
2执行脚本
./helloworld.sh
特殊变量
执行脚本将参数传入进去。格式:./脚本 参数列表,空格隔开
- $n:获取传入的参数,例如第1-9个参数取值方式为
$1
等,超过第9个参数取值方式为${10}
- $#:获取传入参数的个数
- $*:将所有传入的所有参数拼接为一个整体
- $@:将所有参数放入一个集合中,可以进行遍历,遍历方法看后面笔记
- $?:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了
脚本编写
1 | !/bin/bash |
脚本执行
1 | ./parameter.sh 1 2 3 |
算术运算
执行算术运算。格式$((运算式))或$[运算式]
1 | echo $((1+1)) |
条件判断
语法
- test 条件表达式 (注意:条件表达式中的
=
的前后都需要有空格) - [ 条件表达式 ](注意:条件表达式前后都要有空格)
注意:条件非空即为true,[atguigu]返回true,[ ] 返回false
常用判断条件
两个整数之间比较
注意:如果是字符串之间比较,用等号=
判断相等;用!=
判断不等
- -eq:等于 (equal)
- -ne:不等于(not equal)
- -lt:小于(less than)
- -le:小于等于(less equal)
- -gt:大于(greater than)
- -ge:大于等于(greater equal)
根据文件权限进行判断
- -r:有读权限(read)
- -w:有写权限(write)
- -x:有执行权限(execute)
按照文件类型进行判断
- -e:文件存在(existence)
- -f:文件存在并且是一个常规文件(file)
- -d:文件存在并且是一个目录(directory)
操作
23是否大于等于22
1
2
3
4
5使用[ ]进行比较
[ 23 -ge 22 ]
使用$?查看执行状态【0:true。1:false】
echo $?判断helloworld.sh是否有写权限
1
2
3
4
5查看文件是否有权限
[ -w helloworld.sh ]
查看结果
echo $?查看指定目录指定文件是否存在
1
2
3
4
5查看文件是否有权限
[ -e /root/script/xiaofei.txt ]
查看结果
echo $?多条件判断(&&表示前一条命令执行成功时,才执行后一条命令,|| 表示上一条命令执行失败后才执行下一条命令)
1
2
3
4
5[ 23 -ge 22 ] && echo OK || echo NotOk
输出:OK
[ 20 -ge 22 ] && echo OK || echo NotOk
输出:NotOk
流程控制
Shell脚本if的使用:https://blog.csdn.net/tootsy_you/article/details/95597376
if
语法
单分支
1
2
3if [ 条件判断式 ];then
程序
fi或者
1
2
3
4if [ 条件判式 ]
then
程序
fi多分支
1
2
3
4
5
6
7
8
9if [ 条件判断式 ]
then
程序
elif [ 条件判断式 ]
then
程序
else
程序
fi
注意事项:
- [ 条件判断式 ]:中括号个条件判断式之间必须有空格
- if后面要有空格
案例
编写Shell脚本
1 | !/bin/bash |
测试
1 | ./if.sh 1 |
case
语法
1 | case: $变量名 in |
案例
编写Shell脚本
1 | case $1 in |
测试
1 | ./case.sh 1 |
for
语法一
1 | for (( 初始值;循环控制条件;变量变化 )) |
案例一
编写Shell脚本
1 | !/bin/bash |
测试
1 | ./for1.sh |
语法二
1 | for 变量 in 值1 值2 值3.... |
案例二
编写Shell脚本
1 | !/bin/bash |
测试
1 | ./for2-1.sh |
比较$*和$@区别
$*和$@都表示传递给函数或脚本的所有参数,不被双引号""
包含时,都以$1$2…$n的形式输出所有参数。
不被
""
包含时1
2
3
4
5!/bin/bash
for i in $*
do
echo $i
done1
2
3
4
5!/bin/bash
for i in $@
do
echo $i
done1
2
3
4
5./for2.sh 1 2
两个shell脚本都是输出:
1
2被
""
包含时1
2
3
4
5!/bin/bash
for i in "$*"
do
echo $i
done1
2
3
4
5!/bin/bash
for i in "$@"
do
echo $i
done1
2
3
4
5
6
7
8./for2.sh 1 2
"$*"输出:
1 2
"$@"输出:
1
2
while循环
语法
1 | while [ 条件判断式 ] |
案例
1 | !/bin/bash |
read读取控制台输入
Shell脚本执行之后,可以将从控制台输入的值,赋值给指定变量
语法
read (选项) (参数)
选项:
-p:指定读取值时的提示
-t:指定读取值时等待的时间(秒),如果-t不加表示一直等待
参数
变量:指定读取值的变量名
案例
1 | !/bin/bash |
函数
系统函数
basename
功能
basename:basename命令会删除所有的前缀包括最后一个(‘/‘)字符,然后将字符串显示出来
basename可以理解为获取路径里面的文件名称
语法
basename [string / pathname] [suffix]
选项:
- suffix为后缀,如果传入了suffix的值,basename会将pathname或string中的suffix去掉
案例
获取/root/script/for2-1.sh
的文件名字
1 | 获取全部文件名字 |
dirname
功能
dirname:获取文件的绝对路径名称
dirname:从给定的包含绝对路径的文件名中去除文件名(非目录部分),然后返回剩下的路径(目录部分)
语法
dirname 文件绝对路径
案例
1 | 获取/root/script/if.sh的路径名称 |
自定义函数
语法
1 | [ function ] funname[()] |
经验技巧
- 必须在调用函数之前声明函数,先声明函数,Shell脚本都是逐行执行的,不会像其他语言一样先进行编译
- 函数返回值,只能通过
$?
系统变量获得,使用return
返回。如果不使用return
指定返回值,则程序将会默认把最后一条执行的语句运行的结果最为返回值。return 后面跟数值0-255
案例
计算两个输入参数的和
1 | !/bin/bash |
1 | 执行脚本 |
正则表达式
正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在Linux中,grep,
sed,awk等文本处理工具都支持通过正则表达式进行模式匹配。
常规匹配
特殊字符^
^
匹配一行的开头
1 | 匹配虽有以a开头的行 |
匹配特殊字符$
$
匹配一行的结束,例如
1 | 获取所有以n结束的行的内容 |
^$会匹配什么
特殊字符.
.
匹配一个任意的字符
1 | 匹配r`a-z`t,例如【rat、rbt、rct......rzt】 |
特殊字符*
*
不单独使用,他和上一个字符连用,表示匹配上一个字符0次或多次
1 | 会匹配rt、root、rooot、roooot、rooooot等宝航的所有行 |
.*会匹配什么
字符区间(中括号):[]
[ ]表示匹配某个方位内的一个字符,例如
[6,8]———匹配6或者8
[0-9]———匹配一个0-9的数字
[0-9]*———匹配任意长度的数字字符串
[a-z]———匹配一个a-z治安的字符
[a-z]*———匹配任意长度的字符字符串
[a-c,e-f]匹配a-c或者e-f之间的任意字符
1 | 会匹配rt、rat、rabt、rbact、rabccbaaacbt等所有行 |
特殊字符
\
表示转义,并不会单独使用。由于所有特殊字符都有其特特定匹配模式,当想匹配某一特殊字符本身时(例如,想找出所有包含$
的行),就会碰到困难,此时就要将转义字符和特殊字符连用来表示特殊字符本身
1 | 查找所有包含 a$b的行。使用时需要使用单引号将表达式引起来 |
文本处理工具
cut
cut的工作就是剪
,具体的说就是在文件中负责剪切数据用的。cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出
用法
cut [选项参数] filename
说明:默认分隔符就是制表符
选项参数
选项参数 | 功能 |
---|---|
-f 数字[-] | 列号,提取第几列,- 截取到指定 |
-d “分隔符” | 指定分隔符进行切割,例如-d " " 则使用`进行切割,默认为 \t`, |
-c 数字 | 按字符进行切割,后加n表示取第几列,比如-c 1 |
案例
数据准备
1
2
3
4
5
6
7vim cut.txt
dong shen
guan zhen
wo wo
lai lai
le le切割第一列
1
2
3
4
5
6
7
8
9切割第一列
cut -d " " -f 1 cut.txt
输出
dong
guan
wo
lai
le切割第二三列
1
2
3
4
5
6
7
8切割第二三列
cut -d " " -f 2,3 cut.txt
输出
shen
zhen
wo
lai
le获取第一列的值
1
2
3
4
5
6
7
8获取第一列的值
cut -c 1 cut.txt
输出
d
g
w
l
l在cut.txt中切割出guan
1
2从cut.txt文件中获取guan【先过滤以guan开头的行,在进行切割】
cat cut.txt | grep guan | cut -d " " -f 1获取$PATH中第二个 : 后面的值
1
2
3
4
5
6
7
8查看PATH的值
echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
获取环境变量的值,再根据 : 进行切割 ,然后获取第二个 : 后面的所有值
echo $PATH | cut -d ":" -f 3
输出
/usr/sbin:/usr/bin:/root/bin获取 ifconfig 中网卡的IP地址
1
ifconfig ens33 | grep netmask | cut -d " " -f 10
awk
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理
用法
awk [ 选项参数 ] ‘/pattern1/{action1} /pattern2/{action2}…’ filename
pattern:表示awk再数据中查找的内容,就是匹配模式,可以输入正则表达式
action:再找到匹配内容时所执行的一系列命令
只有pattern匹配了才会执行action,如果pattern没有匹配,则对应的action不会执行
选项参数
选项参数 | 功能 |
---|---|
-F | 指定输入文件分隔符 |
-v | 赋值一个用户定义变量 |
案例
数据准备
1
2
3
4
5准备数据
cp /etc/passwd ./
passwd文件含义
用户名:密码(加密过后的)用户id:组id:注释:用户家目录:shell解释器搜索passwd文件以root关键字开头的所有行,并输出该行的第七列
1
awk -F : '/^root/{print $7}' passwd
搜索passwd文件以root关键字开头的所有行,并输出改行的第一列和第七列,中间以
,
分开1
awk -F : '/^root/{print $1","$7}' passwd
搜索passwd文件的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell在最后一行添加”dahaige,/bin/zuishuai”
1
2
3awk -F : 'BEGIN{print "user, shell"} {print $1","$7} END{print "dahaige,/bin/zuishuai"}' passwd
注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。将 passwd文件中的用户id增加数值1并输出
1
awk -v i=1 -F : '{print $3+i}' passwd
awk内置变量
变量 | 说明 |
---|---|
FILENAME | 文件名 |
NR | 已读的记录数(行号) |
NF | 浏览记录的域的个数(切割后,列的个数) |
案例
统计passwd文件名,每行的行号,每行的列数
1
awk -F : '{print "filename:" FILENAME ",linenum:" NR ",col:"NF}' passwd
查询ifconfig命令输出结果中的空行所在行号
1
ifconfig | awk '/^$/{print NR}'
切割IP
1
ifconfig ens33 | awk '/netmask/ {print $2}'
综合应用案例
归档文件
实际生产应用中,往往需要对重要数据进行归档备份。
需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将目录下所有文件按天归档保存,并将归档日期附加在归档文件名上,放在/root/archive下。
这里用到了归档命令:tar
后面可以加上-c选项表示归档,加上-z选项表示同时进行压缩,得到的文件后缀名为.tar.gz。
脚本实现如下:
1 | !/bin/bash |
发送消息
Linux自带的mesg和write工具,向其它用户发送消息。
需求:实现一个向某个用户快速发送消息的脚本,输入用户名作为第一个参数,后面直接跟要发送的消息。脚本需要检测用户是否登录在系统中、是否打开消息功能,以及当前发送消息是否为空。
脚本实现如下:
1 | !/bin/bash l |