本篇博文总结常用的shell编程语法,有问题可及时在本博客或CSDN下留言。
shell编程基础
shell脚本首行
bash shell脚本首行#!/bin/bash
,用于确保bash作为脚本的解释程序,固定格式,记住即可,当不指定首行时,默认采用/bin/sh作为解释程序。
1 | #!/bin/bash |
shell脚本注释
shell中添加注释方法如下:
1 | #第一种,"#"用于注释单行 |
shell脚本执行权限
shell脚本要想执行,必须具有执行权限,设置权限的命令可以查看权限设置,下面给出例子:
1 | #给当前用户添加执行权限 |
运行shell脚本,可以通过绝对路径或相对路径
1 | /home/scripts/myshell.sh |
想要在任何路径都能直接通过脚本名执行脚本,需要将shell脚本所在路径添加到path环境变量
1 | export PATH=$PATH:/home/scripts |
之后在任意路径直接myshell.sh即可执行脚本
shell变量扩展
基本的shell变量拓展
在之前已经讲过,采用${param}
1 | ${param} |
间接参数拓展
${!param}
表示引用的参数不是param自身,而是其对应的值,比如param的值是temp,通过${!param}将拓展为参数temp的值,如下:
1 | var=temp |
####大小写修改
${param^}
,将首字符改为大写,${param^^}
所有字符改为大写
${param,}
将首字符改为小写,${param,,}
所有字符改为小写
${param~}
将首字符大小写反转(原来大写改为小写,原来小写改为大写);${param~~}
将所有字符大小写反转
1 | var="this is real me" |
变量名拓展
${!prefix*}或${!prefix@}
,使用变量名拓展将列出以字符串prefix为前缀的所有变量名,默认以空格隔开
1 | var=1 |
字符串移除
${param#parttern}
${param##parttern}
${param%parttern}
${param%%parttern}
上述前两个语句从开头开始匹配移除,后两个语句从结尾开始匹配移除,其中单个的”#”,”%”表示移除匹配指定模式的最短文本,两个的表示移除匹配指定模式的最长文本,直接看例子:
1 | filename=myshell.sh |
字符串搜索与替换
${param/pattern/string}
${param//pattern/string}
${param/pattern}
${param//pattern}
操作符”/“表示替换一个匹配的字符串,操作符”//“表示替换所有匹配的字符串,如果没有指定匹配的字符串,那么匹配的内容将会被替换为空字符串也就是删除掉。
1 | var="I think I understand you" |
求字符串长度
1 | ${#param} |
子字符串扩展
${param:offset}
${param:offset:length}
从指定位置开始截取指定长度的字符串,如果省略length,将截取到参数值末尾。
1 | echo ${var:1} |
使用默认值
${param:-word}
${param-word}
第一种,当param参数为未定义或者为null(shell中指为空字符串而不是null)时,输出word,否则输出param;第二种只用在param未定义,才会输出word。
1 | var="I know U" |
指定默认值
${param:=word}
${param=word}
这种模式和使用默认值的输出类似,区别在于这种模式会将word赋值给param,作为param的值
1 | echo ${var} |
使用替代值
${param:+word}
${param+word}
如果param未定义或者为空,不输出任何内容,如果已定义且不为空,输出word,且不会拓展为param的值
1 | echo ${age} |
Bash内部变量
编写shell脚本时注意不要与shell内部变量重合,可以通过env
查看现有的系统变量,下面是一些内部变量(使用env不一定会展示下面变量)
1 | $BASH-引用Bash实例的全路径名 |
Bash中的位置参数和特殊参数
位置参数
位置参数是由除0意外的一个或多个数字表示的参数,当shell或shell的函数被引用时,由shell或shell函数的参数赋值,并且可以使用bash内部命令set
来重新赋值,位置参数N引用时语法为${N},当位置顺序为个位数时,可以写成$N,超过一位数必须加大括号。
位置参数可以用来给shell脚本指定参数,不能通过赋值语句来赋值,只能通过bash内部命令set
和shift
来设置和取消,shell脚本运行是,位置参数会被临时地替换。
1 | #如下脚本test.sh |
某些特殊参数只能被引用,不能改变值,这些参数是*,@,#,?,-,$,!,0,_
特殊参数*
引用特殊参数* 将输出从位置1开始的所有位置参数(有几个位置参数就输出几个单词),如果是在双引号内引用,如”$*”则输出一个包含所有位置参数的单词(多个位置参数合并为一个单词),此时每个单词中间用内部变量$IFS的第一个字符连接,如果变量IFS没有定义,则默认使用空格连接,如果为空””,则参数直接相连。
1 | set one two three |
特殊参数@
引用特殊参数@也将输出从位置1开始的所有位置参数,但是当在双引号内引用时,它的输出还是将多个参数认为是不同的多个参数,这点会在for循环调用中体现出来,例子可以参考上面,此处不再举例。
特殊参数#
引用特殊参数#将输出位置参数的个数,如下:
1 | echo $# |
特殊参数?
将输出最近一个在前台执行的命令的退出状态,命令正确执行没报错,退出状态返回0,否则将是其他数字,如下:
1 | ll |
特殊参数-
输出当前的选项标志,这个标志是调用时内部命令set
指定,或者shell自身指定,和用户无关,了解即可
1 | echo $- |
特殊参数$
输出当前shell的进程号,在子shell中输出的是调用该子shell的进程号,而不是子shell的进程号
1 | echo $$ |
特殊参数0
输出当前shell或当前shell脚本的名称,在shell初始化时设置
1 | #假设当前脚本test.sh内容如下 |
特殊参数_
在shell启动时,设置为开始运行的shell或shell脚本的路径,随后输出前一个命令的最后一个参数,如下:
1 | 假设当前的test.sh脚本内容如下 |
declare指定变量类型
declare
命令是Bash的内部命令,用于声明变量和修改变量的属性,它与Bash的另一个内部命令typeset
用法和用途完全相同
declare
直接使用declare命令,不指定变量,将显示所有变量的值
1 | declare |
declare -r定义变量为只读
将指定变量定义为只读变量,这个变量不能被赋予新值或清除
1 | declare -r qq=123 |
declare -i定义变量为整数型
将指定变量定义为整数型变量,对该变量的任何赋值将会被转化为整数
1 | declare -i var |
declare -x声明环境变量
指定的变量会成为环境变量,可供shell以外的程序来使用
declare -p显示指定变量的属性和值
1 | 接上文 |
declare -a声明数组
声明数组可以采用arrname=(v1 v2 v3)
或者declare -a arrname=(v1 v2 v3)
数组属性可以通过declare声明,作用于数组每个成员
引用数组成员时,从下标0开始引用,${arr[0]}(花括号是必须要的) ,改变某个位置的值通过arr[i]=v来实现,如果下标是@或者*会引用所有的变量,不指定下标时输出第一个位置的元素
1 | arr=(1 2 3) |
shell算术运算
shell可以进行算数运算,可以通过算数拓展或者通过内部命令let
实现
bash运算符
bash运算符的优先级,结合性和值都与C语言相同,下面是优先级从高到低排列:
操作符 | 用途 |
---|---|
id++ id– | 后递增,后递减 |
++id –id | 前递增,前递减 |
-+ | 单目负号和正号(用在数字前边) |
!~ | 逻辑取反,按位取反 |
** | 求n次方,如5**2=25 |
*、/、% | 乘、除、取余 |
+- | 加减 |
<<、 >> | 按位左移,按位右移 |
<= 、>=、<、> | 比较符号 |
==、!= | 相等,不等 |
&、^、| | 按位与、按位异或、按位或 |
&&、|| | 逻辑与、逻辑或 |
expr?expr:expr | 条件运算符 |
=、*=、/=、%=、+=、-=、<<=、>>=、&=、^=、|= | 赋值 |
expr1,expr2 | 逗号运算,连接多个运算,只有最后一个运算值返回 |
数字常量
默认情况,shell运算采用十进制,除非数字有特定前缀标志,以0开头的常量将被当做八进制数解释,而以”0x”或”0X”开头的为十六进制数。如果数值格式是BASE#NUMBER,BASE是介于2-64之间的十进制数,表示算数进制技术,例如BASE为12,12#NUMBER表示12进制数,NUMBER是该进制下的数值。
1 | let dec=20 |
在64进制中,0-9即使用0-9表示,10-35用a-z表示,36-61用A-Z表示,62和63分别用@和_表示
使用算术扩展和let进行算数运算
算数扩展只能运算整数,不能对浮点数进行算术运算
直接上示例:
1 | var=5 |
let
也可以进行算数运算,默认情况下运算符左右两边不允许有空格,如果有空格,需要用双引号包起来。
1 | let i=i+5 |
使用expr命令
expr
命令用于对表达式进行求值并输出相应结果,只支持整数运算,不支持浮点数运算。与命令相反,使用该命令,运算符左右两边必须包含空格,如果没有空格,将不会求值而是直接输出算数表达式,有些运算符需要使用”\“进行转义,否则会提示语法错误(包括*、<、>、<=、>=、|、&)。通过expr
给变量赋值时需要使用shell拓展中的命令替换
1 | expr 6 + 8 |
退出脚本
exit
命令用于结束并退出一个shell脚本
一个运行成功的命令会返回一个0,不成功会返回其他值,shell脚本及里面的函数也会返回一个退出状态码,在shell脚本或函数中,最后执行的一条命令决定其退出状态。通过特殊参数?可以得知退出状态码。
校验程序的退出状态码是有用且必要的(某些危险指令下),比如下面两条:
1 | cd $DIR |
脚本的本意是切换到DIR目录下,删除该目录下所有文件,加入我们不对cd命令结果进行验证,假如该文件夹不存在,那么rm命令将在当前文件夹下执行,这可能产生不可预料的损失。
在shell脚本中,通过exit N
命令可以用于提交一个退出状态码给shell(N必须是介于0-255之间的整数),如果省略了退出状态码N,则将把最后一条运行的命令的退出状态作为脚本的退出状态码。
结合退出状态码,对上面的脚本进行改写:
1 | cd $DIR |
调试脚本
通过bash -x xxx.sh
可以调试脚本,该命令会启动子shell,以调试模式运行脚本,在执行过程中将实际执行的命令显示出来,其中的参数也是实际的运行时参数,每个命令行前面有个+号,如下例子中+号后面是实际运行的命令,其他是运行结果
1 | bash -x test.sh |
在shell脚本中使用’”set -x”表示启动某选项,”set +x”表示关闭某选项,通过这两个命令可以只调试shell中的某一段脚本。比如我们先将前述test.sh脚本修改成如下内容:
1 | #!/bin/bash |
bash中-v
选项可以激活详细输出模式,通常调试是会将-v和-x配合使用,得到更详细的脚本信息
1 | bash -xv test.sh |
使用-x虽然方便,但是没有显示代码行号等信息,下面几个bash内部环境变量配合起来可以更方便显示调试信息:
$LINENO:表示shell脚本的当前行号
$FUNCNAME:包含当前在执行调用堆栈中的所有shell函数名称的数组变量。${FUNCNAME[0]}表示当前正在执行的shell函数的名称,${FUNCNAME[1]}代表调用函数${FUNCNAME[0]}的函数名字,依次类推
$PS4:之前调试中每一行命令前的+号就是该变量的默认值
通过这几个变量配合,我们可以通过重新定义$PS4,增强-x输出的信息
此外还可以通过bash -n
检查脚本是否有语法错误。
shell条件执行
条件测试
test
命令测试成功返回0(真),失败返回1(假),test命令可以用于文件属性,字符串,算术测试,语法为test expression或[空格expression空格]
1 | test -d "$HOME";echo $? |
常用文件属性测试操作符
-e:文件存在则为真
-f:存在且为常规文件则为真
-d:存在且是一个目录则为真
-r:存在且是可读的则为真
-w:存在且是可写的则为真
-x:存在且是可执行的则为真
-s:存在且不为空则为真
1 | test -e /bin/cp && echo "The command $_ found" || echo "The command $_ not found." |
命令中$_表示前一个执行的命令中的最后一个参数。
常用字符串操作符如下
-z:字符串为空则为真
-n:字符串不为空则为真
=(两字符串相等)!=、<(比较字典序)、>
由于大于小于在shell中也被用于重定向,因此比较字符串的时候需要加转义
1 | test "abc" \< "def";echo $? |
常用算数测试操作符
-eq:等于;-ne:不等于;-le:小于等于;-ge:大于等于;-lt:小于;-gt:大于
1 | test 5 -eq 5 && echo YES || echo NO |
if结构语法格式
if语句的条件判断命令可以使用test命令,也可以是其他运行成功返回状态码0,失败返回其他状态码的命令,if语法格式如下;
1
2
3
4
5
6
7
8
9
10 if [ test-commands ]; then
其他指令
fi
或者
if test-commands
then
其他指令
fi
或者
if [ test-commands ]; then 其他指令;fi
if…else…fi语句
语法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 >if [ test-commands ]
>then
其他指令
>else
其他指令(或者继续跟着if语句)
>fi
>还可以嵌套
>if [ test-commands1 ]
>then
执行指令1
if [ test-commands2 ];
then
执行指令2
fi
>else
执行指令3
>fi
if…elif…elif…fi
1 | if [ test-commands1 ] |
条件执行
bash下可以根据最后一个命令的退出状态使用条件执行来连接两个命令,也可以直接在if语句中使用条件执行,bash支持以下两种条件执行:
逻辑与—只有当前一个命令执行成功时才执行后一个命令
逻辑或—只有当前一个命令执行失败时才执行后一个命令
逻辑与&&
逻辑与&&
用法为command1&&command2
,只有当command1返回一个退出状态码0时,才会执行command2,也就是1执行成功才会执行2,前面已经有例子,此处不再举例。
也可以通过&&
在if语句中将多个test命令连接在一起,比如:
1 | var=123 |
需要注意的是使用-a代替&&有一点不同,-a会将前后两个test命令参数都拓展执行,但是&&只有前一个为真才执行后一个,假如两个命令前一个结果是假,后一个为echo语句,使用&&不会执行后面的输出语句,但使用-a会执行后面的输出语句,此外从可读性方面也不建议使用-a代替&&
逻辑或||
用法command1||command2
,只有当command1返回假(非0)时才会运行command2,也就是只有command1执行失败才会执行command2
||
可以与逻辑与&&
联合使用,也可以多个逻辑或联合使用,因概念比较简单,不再举例
在逻辑或语句中可以使用-o
代替||
,但是从可读性,运行效率方面看也不建议这么做。
逻辑非!
用法!command
用来测试真假,比较简单,不展开了。
case语句
case语句是多级if…then…else…fi语句的替代方式,可以让一个条件与多个模式比较,类似java中switch,case。语法如下:
1 | case EXPRESSION in |
case语句一定要以esac
结尾,每一个命令列表都以两个分号”;;”为终结,只有最后一个命令列表(即esac语句之前)的”;;”可以省略
bash循环
for循环语法
1 | #基本语法 |
while循环
1 | while [ condition ]; |
使用true
,false
或:
可以定义无限循环,如下
1 | while true; |
until循环
until循环与while循环类似,也是基于一个条件,但其与while循环的逻辑正好相反,当条件被满足时退出循环,不满足时才持续运行。until循环语法如下:
1 | until [ EXPRESSION ]; |
1 | #将test.sh修改为如下内容 |
select循环
语法如下:
1 | select var in list |
select循环特点:
- select语句使用bash内部变量PS3的值作为提示符
- 打印到屏幕上的列表list当中的每一项会在前面加上数字编号
- 当用户输入的数字与某一个数字编号一致是,列表中相应的项即被赋予变量var
- 如果用户输入的内容为空,则重新显示列表list中的项和提示符信息
- 可以添加一个退出选项,或者按ctrl+C或者ctrl+D组合键退出select循环
1 | #test.sh |
循环控制
break
,continue
用来进行循环控制,与其他语句含义一致。
break
用于从for、while、until或select循环中退出,停止,语法如下:break n
其中n代表嵌套循环的层级,指定了n,将退出n级嵌套循环,没有指定或者n小于1,则退出状态码为0,否则退出状态码为n
1 | #退出两层嵌套循环 |
continue
continue语句用于跳过循环体中剩余的命令直接跳转到循环体的顶部,重新开始循环的下一次重复,continue用于for、while或until循环
语法为continue n
shell函数
函数定义
函数的目的就是为了复用,函数定义语法如下:
1 | function_name() |
可以使用内部命令unset的”-f”选项来取消函数的定义,通常情况下函数体外的大括号与函数体之间必须用空白符(空格、回车或制表符等)或换行符分开。
函数的参数、变量与返回值
向函数传递参数
函数中使用参数的语法规则如下:
1 | name(){ |
使用如下语法调用函数:
1 | #name-函数名;param1-参数1;param-参数2 |
回顾下之前提到的位置参数:
- 所有函数参数都可以通过$1,$2,…即位置参数来访问
- $0指代脚本名称
- $*或$@保存传递给函数的所有参数
- $#保存传递给函数的参数个数
本地变量
默认情况下函数的变量为全局变量,在函数内改变变量值之后外面的变量值也会变化,local
命令用来创建一个本地变量,其语法如下所示:
1 | local var=value |
local命令只能在函数内部使用;local命令将变量名的可见范围限制在函数内部
return命令
return语句可选,没有return语句,则以函数最后一条命令的运行结果作为返回值;如果使用return语句,则return后跟数值n(0-255)
函数调用
函数调用方式有多种,可以直接在shell命令行调用,或者脚本内部调用,或者从其他函数文件中调用,也可以递归调用。
在命令行中调用
可以通过直接输入函数的名字,来调用或引用函数
function_name
在脚本中调用
在脚本中定义并且调用函数的方法如下:
1 | functionname(){ |
要在脚本中调用函数,首先要定义函数,并且保证位于调用此函数的语句之前,所以脚本对于变量和函数的定义尽量放在脚本最前面
从函数文件中调用函数
可以把所有的函数存储在一个函数文件中,或者把所有的函数加载到当前脚本或是命令行。加载函数文件中所有函数的语法如下,可以在命令行调用或者脚本文件中调用:
1 | . /path/to/your/functions.sh |
source
命令也可以用于加载任何函数文件到当前shell脚本或者命令行,语法如下:source filename [arguments]
1 | source functions.sh |
递归函数调用
1 | #!/bin/bash |
bash下函数递归调用很慢,尽量避免
将函数放在后台运行
将函数放在后台运行如下,在脚本中调用函数:
1 | functionname(){ |