本文记录一些自己在使用shell进行批量操作、任务调度等工作时用到的一些shell的基础知识,在此记录以备翻阅和查找。
基本使用
变量的定义和使用
变量的定义
在定义变量时,变量名不加美元符号,例如:
需要注意的是,变量名和等号之间不能有空格。同时,变量名的命名遵循如下规则:
首个字符必须是字母(a-z A-Z)。
中间不能有空格,可以使用下划线。
不能使用标点符号。
不能使用bash中的关键字。
除了显式的直接赋值,还可以使用语句给变量赋值,如:
1 for file in `ls /etc`
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号,例如:
上面的花括号是可选的,为了帮助解释器识别变量的边界,如:
注意,重复使用的变量赋值时不可以使用$符号。
只读变量
使用readonly命令定义可以将变量定义为只读变量,只读变量的值不能被改变。
这样的执行就会报错“/bin/sh: NAME: This variable is read only.”。
删除变量
使用unset命令可以删除变量,但是不能删除只读变量:
变量类型
运行shell的时候,会同时存在三种变量:
局部变量:局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
环境变量:所有程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候,shel也可以定义环境变量。
shell变量:shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行。
shell字符串
字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以使用双引号,也可以不用引号。
使用单引号的限制:
单引号里的任何字符都会原样输出,单引号中的变量是无效的。
单引号字符串中不能出现单引号(对单引号使用转义字符也不行)。
双引号的优点:
双引号里可以有变量。
双引号里可以出现转义字符。
拼接字符串
|
|
获取字符串长度
|
|
提取子字符串
|
|
查找子字符串
|
|
Shell数组
bash支持一维数组(不支持多维数组),并且没有限定数组的大小。数组的下标由0开始,获取数组的元素需要使用下标,下标可以是整数或算数表达式,算数表达式的值应该大于等于0。
数组定义
在shell中,用括号来表示数组,数组元素使用空格符号来分割。定义数组的一般形式为:
例如:
或
还可以单独定义数组的各个分量:
可以不使用连续的下标,而且下标的范围没有限制。
读取数组
读取数组需要使用下标,一般格式为:
使用@符号可以获取数组中的所有元素:
获取数组的长度
获取数组长度的方法与获取字符串长度的方法相同:
Shell的注释
以”#”开头的行就是注释,会被解释器忽略。
shell中没有多行注释,只能每行添加一个”#”。
Shell传递参数
我们可以在执行shell脚本时,向脚本传递参数。脚本获取参数的格式为$n,其中n代表一个数字。参数的下标是从1开始的,0表示执行的文件名。
其他参数:
参数表示 | 参数说明 |
---|---|
$# | 传递到脚本的参数的个数 |
$* | 以一个字符串显示所有向脚本传递的所有参数 |
$$ | 运行脚本的当前进程的ID |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数 |
$- | 显示Shell使用的当前选项,与set命令功能相同 |
$? | 显示命令的退出状态。0表示没有错误,其他值表示有错误 |
$*与$@的区别
相同点:都是引用所有参数。
不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数1 2 3,则$*等价于”1 2 3”(一个参数),而$@等价于”1” “2” “3”(三个参数)。
Shell基本运算符
shell支持的基本运算符有:
算数运算符
关系运算符
布尔运算符
字符串运算符
文件测试运算符
原生的bash不支持简单的数学运算,但是可以通过其他命令来实现,例如awk和expr。其中expr比较常用。expr是一款表达式计算工具,使用它能够完成表达式的求值操作。需要注意的两点:
表达式和运算符之间必须有空格。
完整的表达式要被``包含起来。
算数运算符
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法 | expr $a + $b 就是在计算a与b的和 |
- | 减法 | expr $a - $b 就是在计算a与b的查 |
* | 乘法 | expr $a \* $b 就是在计算a与b的乘积 |
/ | 除法 | expr $a / $b 就是在计算a与b的商 |
% | 取余 | expr $a % $b 就是在计算a与b的余 |
= | 赋值 | a=$b,将b的值赋值给a |
== | 相等,比较左右两边是否相当 | [ $a == $b ]比较a和b是否相当 |
!= | 不想当,比较左右两边是否不相等 | [ $a != $b ] |
注意,
条件表达式要放在放括号之间,并且要有空格。
乘号()前面必须加反斜杠才能实现乘法运算。
在MAC中,shell的expr语法是$(()),此处表达式中的不需要反斜杠来转意。
关系运算符
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
运算符 | 说明 | 举例 |
---|---|---|
-eq | 检测两个数是否相等,相等返回true | [ $a -eq $b ] |
-ne | 检测两个数是否相等,不相等返回true | [ $a -ne $b ] |
-gt | 检测左边的数是否大于右边的数,大于返回true | [ $a -gt $b ] |
-lt | 检测左边的数是否小于右边的数,小于返回true | [ $a -lt $b ] |
-ge | 检测左边的数是否大于等于右边的数,大于等于则返回true | [ $a -ge $b ] |
-le | 检测左边的数是否小于等于右边的数,小于等于则返回true | [ $a -le $b ] |
布尔运算符
运算符 | 说明 | 举例 | ||
---|---|---|---|---|
! | 非运算 | [ !false ] 返回true | ||
-o | 或运算 | [ $a -lt 20 -o $b -gt 30 ] | ||
-a | 与运算 | [ $a -lt 20 -a $b -gt 30 ] | ||
&& | 逻辑与运算符 | [[ $a -lt 100 && $b -lt 20 ]] | ||
逻辑或运算符 | [[ $a -lt 100 | $b -lt 20 ]] |
字符串运算符
运算符 | 说明 | 举例 |
---|---|---|
= | 检测两个字符串是否相等,相等返回true | [ $a = $b ] |
!= | 检测两个字符串是否相等,不相等返回true | [ $a != $b ] |
-z | 检测字符串长度是否为0,为0返回true | [ -z $a ] |
-n | 检测字符串长度是否为0,不为0返回true | [ -n $a ] |
str | 检测字符串是否为空,不为空返回true | [ $a ] |
文件测试运算符
操作符 | 说明 | 举例 |
---|---|---|
-b file | 检测文件是否是二进制文件,是则返回true | [ -b $file ] |
-c file | 检测文件是否是字符文件,是则返回ture | [ -b $file ] |
-d file | 检测文件是否是目录,是则返回true | [ -d $file ] |
-f file | 检测文件是否是普通文件(既不是目录,也不是设备文件) | [ -f $file ] |
-g file | 检测文件是否设置了SGID位,是则返回true | [ -g $file ] |
-k file | 检测文件是否设置了粘着位(Sticky Bit),是则返回true | [ -k $file ] |
-p file | 检测文件是否是有名管道,是则返回true | [ -p $file ] |
-u file | 检测文件是否设置了 SUID 位,是则返回true | [ -u $file ] |
-r file | 检测文件是否可读,是则返回true | [ -r $file ] |
-w file | 检测文件是否可写,是则返回true | [ -w $file ] |
-x file | 检测文件是否可执行,是则返回true | [ -x $file ] |
-s file | 检测文件是否为空,不为空则返回true | [ -s $file ] |
-e file | 检测文件是否存在,存在则返回true | [ -e $file ] |
Shell echo命令
Shell的echo命令用于字符串的输出。命令格式:
可以使用echo实现更加复杂的输出格式控制。
显示普通字符串
显示普通的字符串,如:
这里的双引号完全可以省略:
显示变量
|
|
显示换行
|
|
显示结果定向到文件
|
|
原样输出字符串,不进行转义或取变量(用单引号)
|
|
显示命令执行结果
|
|
Shell printf命令
printf是标准所定义,因此使用它比使用echo移植性好。printf使用引用文本或空格分隔的参数,外面可以在printf中使用格式化字符串,还可以定制字符串的宽度、左右对齐方式等。默认printf不会像echo那样自动添加换行,需要手动添加\n。
printf语法:
其中 format-string为格式控制字符串 ;arguments为参数列表。
示例:
%s %c %d %f都是格式替代符。%-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
%-4.2f 指格式化为小数,其中.2指保留2位小数。
printf的转义序列
序列 | 说明 |
---|---|
\a | 警告字符,通常为ASCII的BEL字符 |
\b | 后退 |
\c | 抑制(不显示)输出结果中任何结尾的换行符(只在%b格式指示符控制下的参数字符串有效),而且任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符都会被忽略 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ | 转义的饭斜杠 |
\ddd | 表示1到3位八进制的字符。仅在格式字符串中有效 |
\0ddd | 表示1到3位的八进制字符 |
Shell test命令
test命令用于检测某个条件是否成立,他可以进行数值、字符和文件三个方面的测试:
数值测试
参数 | 说明 |
---|---|
-eq | 是否等于 |
-ne | 是否不等于 |
-gt | 是否大于 |
-ge | 是否大于等于 |
-lt | 是否小于 |
-le | 是否小于等于 |
示例:
字符串测试
字符串测试可用的操作符,详见”Shell基本运算符”章节。
示例:
文件测试
文件测试可用的操作符,详见”Shell基本运算符”章节。
示例:
Shell 流控制
和其他语言的流控制不同,shell的流控制语句不能为空,如果为空就不要写。
if else
if语句的语法格式
|
|
如果写成一行:
if else语句的语法格式
|
|
if else-if else语句的语法格式
|
|
if else语句经常与test命令结合使用
for循环
for循环与其他编程语言类似,一般格式为:
如果写成一行:
循环体中的命令可以是任何有效的shell和语句。in列表是可选的,如果不使用它,for循环使用命令行的位置参数。
无限循环:
while循环
while循环用于不断的执行一系列命令,也用于从文件中读取数据。其格式为:
无限循环:
until循环
until循环执行一系列命令,直到条件为true时停止。until循环与while循环在处理方式上刚好相反。一般while循环由于until循环,但在某些情况下,until循环更加有用。
until语法格式:
条件可以为任意测试条件,测试发生在循环末尾,因此循环至少执行一次。
case语句
case语句为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行匹配的命令。case语句格式如下:
case工作方式如上所示。取值后面必须为单词in,每一种模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行至;;。
取值将检测匹配的每一个模式。一旦模式匹配,则执行完全匹配模式响应命令后不再继续匹配其他模式。如果无一匹配模式,使用星号捕获,再执行后面的命令。
case的语法和传统的编程语言不太一样,它需要一个esac来结束case语句,每个case分值用右括号来表示,并用两个分号表示break。
跳出循环
在执行循环的过程中,有时候需要在某种特定情况下终止循环,shell中使用两个命令来实现:continue和break。
break语句
break语句用来跳出并结束循环(后面的循环都执行了)。
continue语句
continue语句也是用来跳出循环,但是只是跳出当前循环,只是continue语句后面的不执行了,但是下一个循环继续。
Shell文件的引入
shell脚本也可以引入其他的shell脚本,这样可以很方便利用已有代码来提高效率。
shell文件引入的语法如下:
注意,被引入的文件不需要可执行权限。
高级使用
Shell的函数
shell可以允许用户定义函数,然后在shell中随便调用。
shell中函数的定义格式如下:
说明:
定义函数时,关键字function可以省略;而且可以不带任何参数。
返回参数,可以显示的使用return语句,如果没有return语句,则最后一条命令的运行结果作为返回值。return后跟数值n(0-255)
示例:
函数的返回值在调用函数后,通过$?来获取。
另外,函数在使用前,必须定义。这表示,函数的调用位置必须在函数的定义位置之后。而且函数的调用,只使用函数名即可。
函数的参数
调用函数时可以向其传递参数。在函数内部,通过$n的形式来获取参数的值,例如$1表示第一个参数,$2表示第二个参数。当参数超过10个时,从第10个开始,需要使用${N}的方式来获取。
示例:
另外,还有几个特殊的字符来表示参数
| 参数 | 说明 |
|:————–|:———————|
| $# | 传递到函数中的参数的个数 |
| $ | 以一个字符串来显示所有传入到函数中的参数 |
| $$ | 脚本运行的当前进程ID |
| $! | 后台运行的最后一个进程ID |
| $@ | 与$相同,只是每个参数以带引号的方式返回 |
| $- | 显示shell使用的当前选项,与set命令功能相同 |
| $? | 表示函数的返回值 |
时间的使用
在执行shell调度的时候,难免需要操作时间,来生成特定的时间格式。
date命令
date命令的功能是显示和设置系统的日期和时间。date命令中的各个选项的含义分别为:
-d, –date 提供日期字符串作为输入
-s, –set 设置日期格式
-u, –universal 显示或设置通用时间
日期格式字符串列表
注意使用这些符号时,加号和格式符号之间不能有空格,如:date +”%H”。
格式字符串 | 表示含义 |
---|---|
%H | 小时,24小时制(00 - 23) |
%I | 小时,12小时制(01 - 12) |
%k | 小时,24小时制(1 - 24) |
%l | 小时,12小时制(1 - 12) |
%M | 分钟(01 - 59) |
%p | 显示AM或PM |
%r | 显示时间,以12小时制显示(hh:mm:ss %p) |
%s | 从1970年1月1日 00:00:00到目前经历的秒数 |
%S | 显示秒(00-59) |
%T | 显示时间,以24小时制显示(hh:mm:ss) |
%Z | 显示时区,日期域(CST) |
%X | 显示时间的格式(%H:%M:%S),如:14时37分21秒 |
%x | 显示日期的格式(%Y/%m/%d) |
%a | 星期的简称(Sun - Sat) |
%A | 星期的全称(Sunday - Staturday) |
%h, %b | 月的简称(Jan - Dec) |
%B | 月的全称(January - December) |
%c | 日期和时间(Tue Nov 20 14:12:48 2017) |
%d | 当日在当月中的天数(01 - 31) |
%D | 日期(mm/dd/yy) |
%x, %D | 日期(mm/dd/yy) |
%j | 一年的第几天(001 - 366) |
%m | 月份(01 - 12) |
%w | 当天是本周的第几天(0代表星期天) |
%W | 本周是一年的第几个星期(00 - 53, 星期一为第一天) |
%y | 年的最后两位数字(17) |
%Y | 年的完整表示(2017) |
用例
以24小时制输出当前时间
显示当日的月份,是今年第几周的星期几
时间的加减
时间的加减使用-d参数,配合描述操作来完成的,例如:
其中”+1 day”就是描述操作,类似的还有”+1month”、”+1year”、”+1week”、”+1minute”和”+1second”等,其中减操作,使用”-“。
除了上面的计算,如果两个时间需要计算时间差,那么需要使用以一种变通的方式来解决,将时间转换为秒,然后在计算对应的时间差:
休眠sleep
在写一些以循环方式运行某些监控脚本时,设置间隔时间是必不可少的,那么就会用到休眠功能:
命令的连续执行
某些时候,我们可能需要执行某些服务的某些脚本,而这些脚本需要再次输入命令。比如ZooKeeper的zkCli.sh脚本,当执行了这个脚本后,会打开Jconsole,需要再次输入要执行的命令。如果在这种情况下,我们如何通过shell连接服务并执行命令呢?操作如下:
对于连续的shell,我们还可以使用&&进行连接,比如 ls /home && rm -f xxx.log