1.Shell脚本基础
(1)shell脚本
包含一些命令或声明,并符合一定格式的文本文件
格式要求:首行shebang机制
#!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
(2)格式要求
脚本代码开头约定:
1、第一行一般为调用使用的语言
3、版本号
4、更改后的时间
5、作者相关信息
6、该程序的作用,及注意事项
7、最后是各版本的更新简要说明(也可以不写)
其实最重要的是第一行,其他行也可以不写,但是为了一个良好的变成习惯,建议还是写上。
(3)脚本调试
检测脚本中的语法错误
bash -n/path/to/some_script
调试执行
bash -x/path/to/some_script
会一条一条的调试执行,可以找出哪一句错误了
2.变量
(1)变量介绍
作用:
1、数据存储格式
2、参与的运算
3、表示的数据范围
类型:
字符
数值:整型、浮点型
变量命名法则:
1、不能使程序中的保留字:例如if,for
2、只能使用数字、字母及下划线,且不能以数字开头
3、见名知义
4、统一命名规则:驼峰命名法
(2)bash中变量的种类
根据变量的生效范围等标准:
本地变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell进程均无效
环境变量:生效范围为当前shell进程及其子进程
局部变量:生效范围为当前shell进程中某代码片断(通常指函数)
位置变量:$1,$2,...来表示,用于在脚本代码中调用通过命令行传递给它的参数。
特殊变量:$?,$0,$*,$@,$#,$$
(3)本地变量
变量赋值:name=‘value’
可以使用引用value:
(1) 可以是直接字串;name=“root"
(2) 变量引用:name="$USER"
(3) 命令引用:name=`COMMAND` name=$(COMMAND)
变量引用:${name} $name
"":弱引用,其中的变量引用会被替换为变量值
'':强引用,其中的变量引用不会被替换为变量值,而保
持原字符串
显示已定义的所有变量:set
删除变量:unset name
(4)环境变量
变量声明、赋值:
export name=VALUE
declare -x name=VALUE
变量引用:$name,${name}
显示所有环境变量:
env
printenv
export
declare -x
删除变量:
unset name
bash内建的环境变量:
PATHSHELL USER UID HOME PWD SHLVL
LANG MAIL HOSTNAME HISTSIZE―
(5)只读和位置变量
声明只读变量:
readonly name
declare -r name
查看只读变量:
readonly -p
$1,...:对应第1、第2等参数。$1表示第一个参数,$2表示第二个参数
$0: 命令本身
$#: 传递给脚本的参数的个数
$*: 传递给脚本的所有参数,全部参数合为一个字符串
$@: 传递给脚本的所有参数,每个参数为独立字符串
$@和$* 只在被双引号包起来的时候才会有差异
假设你的脚本运行时你写了三个参数 分别存储在$1 $2 $3中
则"$*" 等价于 “$1 $2 $3 $4" ---》传递了一个参数
而“$@" 等价于 "$1" "$2" "$3" "$4" ---》传递了四个参数
如图,分别使用两种方式将1 2 3 4传给aa.sh和bb.sh,然后取第一个参数,果然发现输出的结果表明$@传递了四个参数,因此取第一个才会是1。
set -- 清空所有位置变量
如图,使用set --后所有变量被清空
PS:引用一位数以上的变量(如上图的$10)时要加{},不然系统会认为是$1和0。
(6)参数左移
shift n用于对参数的移动(左移)
如图,参数向左移动了两位(就是删除最左边两个)。由abcdefg变成cdefg
3.退出状态
(1)退出状态
进程使用退出状态来报告成功或失败
0 代表成功,1-255代表失败
$? 变量保存最近的命令退出状态
正常状态,$?值为0
最后一条失败,$?值不为0
(2)自定义退出状态码
exit [n]:
使用exit [n]也可以自定义退出状态码。注意:脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字。
注意:如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码。
4.算术运算
bash中的算术运算:help let
+,-,*,/,%取模(取余),**(乘方)
实现算术运算:
(1) let var=算术表达式
(2) var=$[算术表达式]
(3) var=$((算术表达式))
(4) var=$(exprarg1 arg2 arg3 ...)
(5) declare �Ci var = 数值
(6) echo ‘算术表达式’ | bc
乘法符号有些场景中需要转义。
bash有内建的随机数生成器:$RANDOM(0-32767)
echo$[$RANDOM%50] :0-49之间随机数
(同理可以使用echo$[$RANDOM%50+1]随机产生1-50之间的随机数)
5.赋值
增强型赋值:
+=,-=,*=,/=,%=
let varOPERvalue
例如:let count+=3
自加3后自赋值
自增,自减:
let var+=1
let var++
let var-=1
let var--
6.逻辑运算
(1)与
真与真 真
真与假 假
假与真 假
假与假 假
总结:只要有一个假则为假
短路与:若前一个为假,则不再判断第二个值,因为结果必为假。
(2)或
真或真真
真或假真
假或真真
假或假假
总结:只要有一个真则为真
短路或:若前一个为真,则不再判断第二个值,因为结果必为真。
7.条件测试
判断某需求是否满足,需要由测试机制来实现。专用的测试表达式需要由测试命令辅助完成测试过程。
评估布尔声明,以便用在条件性执行中
若真,则返回0
若假,则返回1
测试命令:
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
注意:EXPRESSION前后必须有空白字符
根据退出状态而定,命令可以有条件地运行
&& 代表条件性的AND THEN
|| 代表条件性的OR ELSE
就是说如果前一条命令输出为0,就执行&&后面的内容,否则执行||后面的内容。
使用这种方法就不用每次使用$?调取结果了
另外,使用[]可以判断是否有值,如有则输出0,如没有则输出1
8.bash的测试
(1)数值测试
-gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于
(2)字符串测试
==是否等于
> ascii码是否大于ascii码
< 是否小于
!=是否不等于
=~ 左侧字符串是否能够被右侧的PATTERN所匹配(部分内容被匹配即可)
注意: 此表达式一般用于[[ ]]中;扩展的正则表达式
-z "STRING“ 字符串是否为空,空为真,不空为假
-n "STRING“ 字符串是否不空,不空为真,空为假
注意:用于字符串比较时的用到的操作数都应该使用引号
(3)文件测试
使用方式为选项加文件:-x FILE
存在性测试:
-a 判断文件存在则为真
-e同-a
存在性及类别测试:
-L同-h
-p是否存在且为命名管道文件
文件权限测试:
-x判断当前用户对该文件可执行为真(只要有任意位置有x,root则为真)
文件特殊权限测试:
-u判断该文件有suid权限为真(对于非二进制文件加上suid即使为真也无效)
-g判断该文件有sgid权限为真(对于非二进制文件加上sgid即使为真也无效,目录有效)
文件大小测试:
-s判断文件(非目录)为非空时为真
文件是否打开:
-N判断文件在上一次读取后被改过则为真。
双文件测试:
使用方式为FILE1 -ef FILE2
(4)组合测试条件
第一种方式:
COMMAND1 && COMMAND2 并且
COMMAND1 || COMMAND2 或者
! COMMAND 非
如:[[ -r FILE ]]&&[[ -w FILE ]]
第二种方式:
EXPRESSION1 -a EXPRESSION2 并且
EXPRESSION1 -o EXPRESSION2 或者
! EXPRESSION
必须使用测试命令进行
9.接受输入
使用read来把输入值分配给一个或多个shell变量
read -p “Enter a filename: “ FILE
-s静默输入,一般用于密码
(注意,必须是sp如果一起使用必须是-sp,不能用-ps)
-n N 指定输入的字符长度N
-d ‘字符’ 输入结束符(就是当输入指定结束符时就算输入结束,会自动跳到下一行)
-t N TIMEOUT为N秒 (给用户的输入做限时规定。若超出-t参数后所规定的时间值后,脚本将终止用户的输入,可单独使用也可和其它参数配合连用。)
read 也可以从标准输入中读取值,给每个单词分配一个变量(注意:不支持管道)。如果变量数少于单词数,则所有剩余单词都被分配给最后一个变量
read 变量名1 [变量名2] < 文件名
也可以使用<<<直接导入具体值:
read 变量名1 [变量名2]<<< "值1 [值2] ..."
10.防止扩展
反斜线(\)会使随后的字符按原意解释
如图,此时我们只想使用$作为美元符号,因此需要加上\,如果不加则意思为引用变量5。但是我们没有定义变量5,所以为空。
也可以加引号来防止扩展
单引号(’)防止所有扩展
双引号(”)也防止所有扩展,但是以下情况例外:
$(美元符号) - 变量扩展
`(反引号) - 命令替换
\(反斜线) - 禁止单个字符扩展
!(叹号) - 历史命令替换
11. bash的配置文件
按生效范围划分,存在两类:
全局配置:
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
个人配置:
~/.bash_profile
~/.bashrc
12.shell登录的两种方式
(1)交互式登录:
(1)直接通过终端输入账号密码登录
(2)使用“su - UserName”切换的用户
执行顺序:/etc/profile--> /etc/profile.d/*.sh ; ~/.bash_profile --> ~/.bashrc-->/etc/bashrc
bash已经定义好了,只要登录bash,就先加载/etc/profile文件
通过/etc/profile中的代码调用/etc/profile.d/ 下所有的.sh文件
当读完所有.sh文件后,返回/etc/profile结束读取。然后bash会接着自动读取~/.bash_profile文件
如图,~/.bash_profile文件中的命令调用~/.bashrc文件
而~/.bashrc文件中的命令又调用了/etc/bashrc文件
其实在/etc/bashrc文件中也有命令调用/etc/profile.d/*.sh文件,但是由于之前已经调用过了,里面会判断就不再调用了
总结:bash自身只会读/etc/profile和 ~/.bash_profile 其他都是在读取这两个文件时调用读取出来的
(2)非交互式登录:
(1)su UserName
(2)图形界面下打开的终端
(3)执行脚本
(4)任何其它的bash实例
执行顺序:~/.bashrc --> /etc/bashrc --> /etc/profile.d/*.sh
总结:非交互式登陆下,bash自身只读取~/.bashrc 文件,然后文件中调用/etc/bashrc文件中又继续调用/etc/profile.d/*.sh
※举个具体例子加深理解吧:
如图,我们分别在这5个文件中加上5段定义变量的话。此时查看5个变量的值,发现为空。因为此时并没有重新登录所以这些文件不重新读取。
于是我们退出并重新登陆。
此时可以看到,分别有定义的值了。
然后我们将C3改成C6
(改完之后目前还是原来的结果,因为没有重新登录)
然后我们使用非交互式登陆root
注意:非交互式登陆有一个特点,它不是登陆一个新的环境,会继承之前shell的所有变量,当然了,如果重新赋值了,它当然会被重新覆盖掉。
因此我们发现变量的值仍然不变。因为即使修改了C,但是非登录shell并不读取~/.bash_profile文件,所以不会被重新赋值。
然后我们退出这个shell到之前的shell,将/etc/bashrc中的E5改为E7。
因为从来都没有重新读取过,因此此时读取变量的话,仍然是12345这5个值。
然后再非交互式登陆到root下,
发现E的值改变了,因为非交互式登陆是需要读取/etc/bashrc文件的。
然后我们再退出到上一个shell,将B2改成B8
此时读取变量仍然是12345,
然后使用非交互式登陆到liubei用户下,发现B的值也改变了。因为非交互式登陆也需要读取/etc/profile.d/*.sh文件的。
然后我们再退出,将D4改为D9
发现变量仍然为
因为我们改的是root下的.bashrc文件,而liubei用户读取的应该是自己家目录的.bashrc文件,因此D的值不变。
此时变量为:
因为交互式登录不会继承之前shell的内容而是全部重新读取,而liubei用户又只会读自己家目录下的.bash_profile文件,而不会读取root下的.bash_profile文件,因此C的值为空。同理,也不会读D的值。因此这两个变量均为空。
1重新启动shell进程(像刚才举例子中的方式)
2使用. 或source 例如: . ~/.bashrc
13. $-变量
h:hashall,打开这个选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭
i:interactive-comments,包含这个选项说明当前的shell 是一个交互式的 shell。所谓的交互式shell,在脚本中,i选项是关闭的。
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等。
B:braceexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一个历史命令,“!n”返回第 n 个历史命令
如图,关闭h选项。