开发者学堂课程【Linux Shell 编程入门与实战:shell 脚本基础入门及变量使用】学习笔记,与课程紧密联系,让用户快速学习知识。
课程地址:https://developer.aliyun.com/learning/course/551/detail/7621
shell 脚本基础入门及变量使用
内容介绍:
一、回顾内容
二、shell 脚本示例
三、变量
四、bash 中变量的种类
一、回顾内容
正则表达式分为两种:基本正则表达式和扩展正则表达式
以扩展正则表达式为例,回顾用到哪些元字符
. 表示任意一个字符,可以表示汉字
x* 前面字符重复任意次 (举例:写 wang* 表示 g 字母重复任意次 wanggg)
.* 任意长度的任意字符串
(wang)* 将 wang 这个字符串重复任意次 wangwangwang,扩展正则表达式不用加\,基本正则表达式写为 (wang\)*
x+ 前面字符重复一次以上 xx xxx 写法等同于 x{1, }
x{n, } 至少 n 次
x{m,n} m 次到 n 次
x{,n} 最多 n 次
x{m} 精确匹配 m 次
x? 表示空,零次或一次,可有可无,用这个?可以实现:默认正则表达式用到的很多都是贪婪模式,可以用?来控制只使用一次实现所谓的懒惰模式
^ 表示行首
$ 表示行尾
\< 或者 \b 表示单词词首
\> 或者 \b 表示单词词尾
[wang] 表示其中任意一个字符 w a n g
[^.] 仅表示. 注意^放在[]中表示除了……之外任意一个字符
[:alpha:] 表示字母数字,用到系统自带的关键字比如字母alpha
(a|b)xy 表示 axy 或者 bxy 注意 a|bxy 表示 a 或者 bxy
(expr1) (expr2) 如果在该行中想要调用第一个正则表达式,对应的匹配出来的字符分别对应\1 \2 注意该正则表达式必须是匹配完之后的最终结果,而不是正则表达式本身 互相引用
& 在有些命令中可以表示前面搜索出来的一些字符
比如在 vim 中用 s 搜索出来的一个字符串
输入 :%s/xyz/&er/g 意思是将 xyz 替换为 xyzer,而且若一行中有多行 xyz 全部替换。如果不加 g表示默认替换一行中的第一个 xyz
vim 是基于模式的,常见的模式有三种:命令模式,插入模式,exe模式即可执行的扩展模式。此外还有替换模式、可视化模式、多窗口等模式
接上节表示 ip 地址内容
取地址 grep -o
分成0-9 10-99 100-199 200-249 250-255几段,各段之间都是或的关系,0-9用[0-9]表示,10-99用[1-9][0-9]表示,(即[1-9]中取一个数字,[0-9]中取一个数字,两个数字组合起来构成该数字),100-199用1[0-9][0-9]表示,200-249用2[0-4][0-9]表示,250-255用25[0-5]表示。数字表示完后要加.,并且用转义字符 \
ifconfig ens33| egrep -o “(([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])”
可以看到取出数字
执行 ifconfig 看到里面存在地址更多
输入ifconfig |egrep -o “(([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])”
但是这种写法有缺陷,比如 2192.168.30.1002按理超出范围
echo 2192.168.30.1002 |egrep -o “(([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])”
结果还是能找到192.168.30.100
所以应该加入\>,如下:
echo 2192.168.30.1002 |egrep -o “\<(([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\>”
把该地址规范后显示在范围内:
echo 192.168.30.100 |egrep -o “\<(([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\>”
也可以写为带有 ?的形式:
echo 192.168.30.100 |egrep -o ”\<(([1-9]?[0-9] |1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\. ) {3} ([0-9] | [1-9] [0-9] | 1 [0-9] [0-9] | 2 [0-4] [0-9] | 25 [0-5])\>”
以后我们可能用正则表达式过滤一些特定字符串,思考用正则表达式表示
1.手机号 检查页面中输入的手机号是否合格
2.邮箱 检查在网站注册时邮箱格式位数不够或者形式不对
3.QQ 号
4.身份证号
二、shell 脚本示例
要想编写一个脚本,该脚本要按照一定的语法书写,现在用 bash 语言来写 shell 脚本。
bash shell 脚本书写的格式要求第一行是 #!/bin/bash,后续可以加一些注释,然后按照执行顺序把现有学习过的linux命令按逻辑关系放入,就可以实现一个脚本。
脚本执行的方式有:
写绝对路径写相对路径,相对路径写.,但是这些方法都需要执行权限,否则运行不出。
那如果没有执行权限也想运行,方法用 bash,比如 bash hello.sh,或者 cat hello.sh |bash
以上都是脚本执行的方式,但是更多使用加执行权限,写路径方式。也可以把脚本放到一个对应的路径下,执行脚本时就不需要再每次写路径,只需要放到 path 变量任何一个目录下都可以,chmod +x hello.sh,
echo $PATH。
比如现在把 hello.sh 放到 root/bin 目录下,先创建 bin目录,输入 mkdir /root/bin,再将hello.sh移到文件中去,输入 mv hello.sh /root/bin,现在运行hello.sh就不再需要写路径,输入hello.sh,显示 hello world
my hostname is centos7.magedu.com
执行脚本就像执行外部命令一样,第一次执行就按照前面学习到的搜索路径来找,一旦找到就hash到内存中,可以看到如图 /root/bin/hello.sh 记录路径
如图自动生成所有描述,但是如果编辑别的文件比如 f1 不带 sh 后缀就不会自动添加描述
vim的配置文件功能如下:
其中 autocmd BufNewFile *.sh exec “:call SetTitle()”表示如果新建一个文件是 sh 后缀,将执行 call SetTitle()语句块,SetTitle()功能块如下图,首先判断是否是sh后缀,如果是sh后缀,就在第一行加 bash,第二行加#,第三行加*****等
Shell脚本示例:
#!/bin/bash
# -----------------------------------------------
# Filename: hello.sh
# Revision: 1.1
# Date: 2017/06/01
# Author: wang
# Email: wang@gmail.com
# Website: www.magedu.com
# Description: This is the first script
# ------------------------------------------------
# Copyright: 2017 wang
# License: GPL
echo “hello world”
脚本调试
检测脚本中的语法错误
bash -n /path/to/some_script
shell 脚本报错有多种可能性,比如输入 hostname 命令时少写 e:
cd bin
vim f1.sh
hostnam
又继续输入
ls
rm -rf /data/*
思考 hostname 命令错误后,后续命令还会继续执行吗?
先来添加权限,输入
ll
chmod +x f1.sh
然后执行,输入
pwd
f1.sh
显示/root/bin/f1.sh: line 12: hostnam: command not found
f1.sh
所以可知 hostnam 错误并不会阻止下列命令继续执行
再来将 hostnam 错误换成 if,
再执行 f1.sh,
显示 /root/bin/f1.sh: lin 15: syntax error: unexpected end of file
所以可知后续命令未执行,if 是关键字,不能单独使用,要与其他字符一起使用,造成了语法错误,语法错误不会继续执行后面的语句,而命令错误不会阻止后续命令的继续执行。
一般在执行脚本前要先检查脚本中的错误,使用 bash -n f1.sh 就可以检测上述语法错误。除此之外,可能想要查看脚本的执行过程,就用 -x 来跟踪调试脚本过程
调试执行
bash -x /path/to/some_script
比如输入:bash -x f1.sh,显示如图
修改 hostnam 为 echo myhostname is ’hostname’,可发现在上图执行过程中 hostname 前有+,+代表命令,脚本的一些指令,现在输入bash -x f1.sh执行
发现 hostname 前有两个+,输入 cat f1.sh 查看
两个+表示 echomyhostname is ’hostname’ 命令是嵌套的,hostname是被嵌套被调用的命令,所以有两个+,两个+表示嵌套深度。
先执行
hostname 再执行外层命令
三、变量
学习中已经学到一些变量,比如带$符号的 $USER、$UID、$HOSTNAME、$PWD等,变量使用中引用符号必须加$,特定情况下要调用变量使用变量就叫引用。
不加$无法分清是命令还是字符串
变量∶命名的内存空间
数据存储方式︰
字符:
数值:整型,浮点型
变量:变量类型
作用∶
1、数据存储格式
2、参与的运算
3、表示的数据范围
类型:
字符
数值:整型、浮点型
例如: $UID $HOSTNAME $PWD $OLDPWD $HISTSTIZE $PS1
NAME= “MAGE”
比如画一个内存空间,其中有一个数据字符串 MAGE,通常在内存里需要有一个存放字符串的地址0101011,对于用户来讲,通过01011011地址去访问 MAGE,我们将0101011记为 NAME,NAME理解为变量,指向该地址地址,实际上是指向地址所对应的空间MAGE,通过 NAME 就可以访问字符串。
变量名存放字符串 MAGE,在具体写指令时,就是 NAME= “MAGE”
输入 name = “mage”,相当于 name 对应一块内存空间,里面存的就是 MAGE 字符串
现在想要显示name中的值,输入 echo $name
结果显示 mage 可以看到存放它的值,这就是变量的赋值及引用,注意是=,而且前后不带空格。
如果想要修改 NAME 里面存放的值,不存放 MAGE,存放一个新的值 WANG,那么新值是存放在 MAGE 同一地方中还是存放在新的内存空间中呢?
存放时应该为如下形式:MAGE 还存在但是不指向 MAGE,将来系统会进行回收
输入
name = “wang”
echo $name
显示 wang
变量的值可以来源于人为的字符串,但是比如加入这样的字符串,输入 name = “wang xiacochun”
echo $name
显示 wang xiacochun
再比如不加 “”,输入name = wang xiacochun
显示 bash: xiacochun: command not found... 会将空格后面的字符串当作一个命令来执行,所以如果变量中有空格或者特殊符号等要加引号
除了人为的字符串也可以写入固定的命令:如果要将命令写入变量,先直接输入 name=hostname
echo $name
显示结果为hostname,只会当成字符串来处理,而不是当作命令
所以要给命令加上反引号,输入
name =‵hostname‵,将生成命令的标准输出作为 name 的值赋给它
echo $name
显示 centos7.magedu.com
当然name可以写hostname这种一行命令,name 里还可以放多行命令,输入 name =‵cat /etc/fstab‵
cat /etc/fstab
echo $name
但是显示出的是一行内容,没有保留原来分行的格式
改为 echo “$name”
就会保留原来的换行格式
除此之外,变量还可以用另一个变量赋值
输入name1 = mage
name2 = wang
name3 = $name1
echo $name3
显示 mage
若修改 name1内容为 zhangsir,输入 name1 = zhangsir
echo $name3
显示 mage
执行过程原理 name1 指向 mage ,name3 也相当于指向 mage,将 name1 改为 zhangsir后,name1 不指向 mage,但是 name3不变,还是指向 mage,所以 name3还是等于 mage
若要将 name3重新赋值,输入 name3 =$name1
echo $name3
则显示 zhangsir
变量
强类型:变量不经过强制转换,它永远是这个数据类型,不允许隐式的类型转换。一般定义变量时必须指定类型、参与运算必须符合类型要求;调用未声明变量会产生错误
如java,c#
弱类型:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会自动进行隐式类型转换;变量无须事先定义可直接调用
如: bash不支持浮点数,php
变量命名法则∶
1、不能使程序中的保留字∶例如 if, for
2、只能使用数字、字母及下划线,且不能以数字开头
3、见名知义
4、统一命名规则:驼峰命名法
四、bash中变量的种类
根据变量的生效范围等标准划分下面变量类型∶
局部变量︰生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
环境(全局)变量︰生效范围为当前 shell 进程及其子进程
本地变量∶生效范围为当前 shell进程中某代码片断,通常指函数
位置变量:$1,$2,...来表示,用于让脚本在脚本代码中调用通过命令行传递给它的参数
特殊变量∶$?,$0,$*,$@,$#, $$
例如:局部变量
输入name = wang
echo $name
显示的 wang 在其他终端显示不出
开启一个新的 bash 程序
输入 bash
cat /etc/passwd
事实上,root 账号一登陆就会运行一个 bash,而刚刚又再次运行了一个 bash,相当于两个 bash,而这两个 bash 进程之间是父子关系
查看父子关系:
输入 echo $PID
echo $$
显示2935,$ 表示进程的一个编号,每个程序系统会自动分配一个编号,$$ 表示当前进程的进程编号
我们可以看另一个 PPID
输入 echo $PPID PPID 就是父进程的进程编号
显示1858
查看父子关系还可以用一个更直观的方法 pstree
输入pstree -p
显示如图,可以看到 bash(1858)——bash(2935),左侧是父进程,右侧是子进程,再往上 systemd(1) 是所有进程的父进程
再开一个 bash 进程
输入echo $$
显示编号为2987,2987是2935的子进程
用 pstree 查看,可以看到多了一个子进程,并且 pstree 下开了一个进程 bash(1858)——bash(2935)——bash(2987)——pstree(3028)
思考在后面的子进程2987中可以使用前面进程里定义的变量吗?
输入 echo $$
显示2987
输入 echo $name
无显示
现在退出这个进程,相当于这个进程关闭,回到前面一级
输入 exit
显示 exit
再输入echo $PPID
显示1858
echo $$
显示2935
输入 echo $name
无显示,再退出 exit
echo $$ 显示1858
输入 pstree -p
1858下不再有子进程,现在再输入 echo $name
显示 wang
注意在下级子进程中定义的变量也不会影响上级,所以只在当前 shell进程中有效
全局变量
输入 echo $$
显示1858
输入 bash
exit
再输入 bash
sleep 100
显示 ^C
输入 ls
显示 fl.sh
输入 pwd
显示 /root/bin
再输入 pstree -p,显示关系树中有 bash(1858)——bash(3171)——pstree(3202)
输入exit
exit
全局变量赋值不像局部变量一样,要加一个关键字 export
现在来定义 name = mage
echo $$ 显示3215
输入 pstree -p 查看进程树关系看到进程 bash(3215)——pstree(3249)
输入 echo $name 显示 mage
想把父进程里的变量传给子进程,输入 export name,意味着将普通变量变为环境变量
再输入 pstree -p 看到 bash(3215)下面没有子进程
开一个子进程 bash
输入 echo $$ 显示进程编号为3266
输入 pstree -p 看到进程 bash(3215)——bash(3266)
现在显示一下刚才 name 的变量
输入 echo $name
显示 mage
也能修改当前内容 echo $$
显示3266
输入 name = wang
echo $name
显示 wang
如果再退出回到上级进程 输入 exit
echo $name
显示 mage
所以全局变量的特点是父进程的变量可以传给子进程,但是子进程变量若修改不影响父进程
若想删除变量,输入 unset name
echo $name 值为空
局部变量总结
变量赋值:name = ’value’
可以使用引用 value:
(1) 可以是直接字符:name= “root”
(2) 变量引用:name = “$USER”
(3) 命令引用:name = ‵COMMAND‵ name= $(COMMAND)
变量引用: ${name} $name
“” :弱引用,其中的变量引用会被替换为变量值
‘’ : 强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的所有变量:set
删除变量:unset name