shell脚本编程
什么是 Shell
shell 是英语“壳,外壳”的意思。像是嵌入在 Linux 这样的操作系统中的一个“微型编程语言”。不需要安装,不需要编译。可以帮我们完成很多自动化任务,例如:保存数据,监测系统的负载等等。
创建脚本文件
vim test.sh
#!/bin/bash # #! 开头的一行会设置解释器运行环境
#!/bin/sh:使用 sh,即 Bourne shell 或其它兼容 shell 执行脚本 #!/bin/csh:使用 csh,即 C shell 执行 #!/usr/bin/perl -w:使用带警告的 Perl 执行 #!/usr/bin/python -O:使用具有代码优化的 Python 执行 #!/usr/bin/php:使用 PHP 的命令行解释器执行
#! 必须连接在一起
#! 一句必须在文件的最开始,第一行
这一行( #!/bin/bash )其实并不是必不可少的,但它可以保证此脚本会被我们指定的 Shell 执行。
#!/bin/bash
# 列出目录的文件 Shell 脚本的注释以 #(井号)开头
ls
esc :wq 保存,退出
运行 Shell 脚本
./test.sh # 直接运行正常会报 -bash: ./test.sh: 权限不够
ls -l # 查看test.sh的权限,-rw-r--r-- 没有 x 执行权限
chmod +x test.sh # 为所有用户添加test.sh运行权限
Linux权限
在 Linux 系统里,每个文件和目录都有一列权限属性。这一列访问权限指明了谁有读的权利,谁有修改的权利,谁有运行的权利。
ls -l 显示的每个文件或目录访问权限
文件信息的第一列可以看到不少 d、r、w、l、x 等字母,这些字母为文件访问权限符
d:英语 directory 的缩写,表示“目录”。就是说这是一个目录; l:英语 link 的缩写,表示“链接”。就是说这是一个链接; r:英语 read 的缩写,表示“读”。就是说可以读这个文件; w:英语 write 的缩写,表示“写”。就是说可以写这个文件,也就是可以修改; x:英语 execute 的缩写,表示“执行,运行”。就是说可以运行这个文件。
为什么我们看到这一排有好多个重复出现的 r、w 和 x ?
那是因为访问权限是按照用户来划分的:
如果相应位置有字母,表示有相应权限;如果相应位置是一个短横 - ,则表示没有相应权限。
除开第一个表示文件或目录属性的符号(此处是 d,表示目录;如果是 l,则是链接;还有其它字母,我们暂时不深究;如果是短横 - ,那么是普通文件。),其它的 9 个符号被划分为三组,从左到右分别表示:
- 第一组 rwx 表示文件的所有者对于此文件的访问权限;
- 第二组 rwx 表示文件所属群组的其他用户对于此文件的访问权限;
- 第三组 rwx 表示除前两组之外的其他用户对于此文件的访问权限。
以tesh.sh为例
-rw-r--r--
从左到右这些符号都表示什么
- - 第一个短横表示这是一个普通文件。如果此处是 d,那么表示目录;如果是 l,那么表示链接等等;
- rw- 表明文件的所有者(此处是 root)对文件有读、写的权限,但是没有运行的权限。也很好理解,因为这是一个普通文件,默认没有可执行的属性。记住:如果有 w 权限(写的权限),那么表明也有删除此文件的权限;
- r-- 表明文件所在的群组(此处是 root)的其他用户(除了 root 之外)只可以读此文件,但不能写也不能执行
- r-- 表示其他用户(除去 root 这个群组的用户)只可以读此文件,但不能写也不能执行。
chmod 命令:修改文件的访问权限
Linux 系统为每种权限(r、w 和 x)分配了对应的数字:
| 权限 | 数字 |
|---|---|
| r | 4 |
| w | 2 |
| x | 1 |
所以,如果我们要合并这些权限,需要做简单的加法:将对应的数字相加。
假如我们要分配读、写权限,那么我们就要用 4+2,就等于 6。数字 6 表示具有读和写权限。
以下是可能的组合形式:
| 权限 | 数字 | 计算 |
|---|---|---|
| — | 0 | 0 + 0 + 0 |
| r– | 4 | 4 + 0 + 0 |
| -w- | 2 | 0 + 2 + 0 |
| –x | 1 | 0 + 0 + 1 |
| rw- | 6 | 4 + 2 + 0 |
| -wx | 3 | 0 + 2 + 1 |
| r-x | 5 | 4 + 0 + 1 |
| rwx | 7 | 4 + 2 + 1 |
所以,对于访问权限的三组(所有者的权限、群组用户的权限、其他用户的权限),我们只要分别做加法就可以了,然后把三个和连起来。
例如,640 分别表示:
- 文件的所有者有读和写的权限;
- 文件所在群组的其他用户具有读的权限;
- 除此之外的其他用户没有任何权限。
因此,我们可以给的最宽泛的权限就是 777:所有者,群组用户,其他用户都有读、写和运行的权限。这样,所有人就都可以对此文件“为所欲为”。
用字母来分配权限:chmod 的相对用法
除了用数字,我们也可以用另一种方式来分配文件的访问权限:用字母。
原理是类似的,但是有时用字母的方式更加精巧,因为不需要一次性把三组权限都写出来。
我们需要知道不同的字母代表什么:
- u:user 的缩写,是英语“用户”的意思。表示所有者;
- g:group 的缩写,是英语“群组”的意思。表示群组用户;
- o:other 的缩写,是英语“其他”的意思。表示其他用户;
- a:all 的缩写,是英语“所有”的意思。表示所有用户。
和这些字母配合的还有几个符号:
- +:加号,表示添加权限;
- -:减号,表示去除权限;
- =:等号,表示分配权限。
接下来,我们举例说明如何使用:
#文件 file.txt 的所有者增加读和运行的权限。
chmod u+rx file.txt
#文件 file.txt 的群组其他用户增加读的权限。
chmod g+r file.txt
#文件 file.txt 的其他用户移除读的权限。
chmod o-r file.txt
#文件 file.txt 的群组其他用户增加读的权限,其他用户移除读的权限。
chmod g+r o-r file.txt
#文件 file.txt 的群组其他用户和其他用户均移除读的权限。
chmod go-r file.txt
#文件 file.txt 的所有用户增加运行的权限。
chmod +x file.txt
#文件 file.txt 的所有者分配读,写和执行的权限;
#群组其他用户分配读的权限,不能写或执行;
#其他用户没有任何权限。
chmod u=rwx,g=r,o=- file.txt
回到shell脚本运行,添加执行权限之后脚本就可以正常运行了
以调试模式运行
bash -x test.sh # 调试模式,脚本内部执行的命令也会显示出来 + 后面的
-e # 使shell立即退出,某些东西会返回一个错误(这通常在shell脚本中用作故障保护机制),Jenkins的脚本默认执行模式就是-xe模式,会打印所有命令执行过程,一旦脚本执行出现错误就会退出执行
# 脚本内部可以使用 set -x 显示 shell 执行过程
# 如果想隐藏某一行的内容:
set -x # 开启调试模式,后面脚本命令执行过程都会输出显示出来
xxxx # xxx命令会显示出来
set +x # 关闭调试模式了
yyyy # yyyy命令执行过程不会输出显示出来,命令执行结果如果有输出会输出显示出来
set -x # 再次开启调试模式
# 如果以bash -x 执行脚本,添加set -x,该命令会输出显示,运行模式不会改变,反之亦然
Linux PATH
先看看有什么
echo $PATH # 输出显示$PATH变量
PATH 是 Linux 的一个系统变量。这个变量包含了系统里所有可以被直接执行的程序的路径。 这也是date,pwd,ls,cat,cp 等命令为什么可以直接在任意目录执行(不需要在前面加上 ./ 这样的路径)的原因。
那我们自己写的脚本程序能不能也这样运行,答案是可以的,有两种方式
- 将test.sh复制到/usr/local/sbin、/usr/local/bin、/usr/sbin:/usr/bin、/root/bin 其中的任意文件夹中,不过不推荐,最好不要乱拷贝文件到系统路径里,就可以在任意目录
test.sh运行了 - 将我们自己的路径添加到PATH中
export PATH=$PATH:/root/liwei/bin # export会覆盖,所以加上$PATH:的目的就是保留所有的path变量,操作系统通过:切割找到匹配路径
echo $PATH # 检查自己添加的路径是否存在
test.sh # 在任意目录就可以执行该脚本了
像我们装一些安卓sdk、mysql等需要配置路径都是一样的原理
定义变量
创建脚本
vim var.sh
编写脚本,定义变量
#!/bin/bash
# 定义变量
message='hello,world'
# 输出变量
echo $message
echo 'message is $message'
echo "message is $message"
msg=`pwd`
echo "所在目录:$msg"
执行
chmod +x var.sh # 添加执行权限
bash -x var.sh # 调试模式执行脚本,方便观察
注意4次echo输出,注意第二次$message是直接显示的,第4个输出msg变量是pwd执行后的输出
shell脚本中引号来界定包含空格的字符串,引号有三种类型:单引号''、双引号""、反引号``。引号类型不同,Bash 的处理方式也会不同
- 单引号:如果变量被包含在单引号里面,那么变量不会被解析,美元符号( $ )保持原样输出。单引号忽略被它括起来的所有特殊字符
- 双引号:双引号忽略大多数特殊字符,但不包括:美元符号( $ )、反引号( ` )、反斜杠( \ ),这 3 种特殊字符将不被忽略。 不忽略美元符号意味着 Shell 在双引号内部可进行变量名替换
- 反引号:反引号要求 Shell 执行被它括起来的内容。如上面脚本内容 msg=`pwd`,pwd 命令被执行了,执行的结果(显示当前所在目录)被赋值给 msg 变量
如果变量的值中想要加入一个单引号( ' ),需要在前面加上反斜杠( \ )进行转义
read : 请求输入
read 命令读取到的文本会立即被储存在一个变量里,read后接一个变量名,这样用户输入的文本就会被储存在这个变量中
vim read.sh
#!/bin/bash
read name
echo "my name is $name"
chmod +x read.sh
./read.sh
read还可以一直读取多个参数
read 命令一个单词一个单词(单词是用空格分开的)地读取你输入的参数,并且把每个参数赋值给对应变量。
# 脚本内容
read lastName firstName
echo "my fullName is $lastName-$firstName"
如果输入了比预期更多的参数,比如三个,四个,甚至更多,那么最后一个变量就会把多出来的参数全部
-p :显示提示信息
修改脚本内容
#!/bin/bash
read -p 'please input your name:' name
echo "my name is $name"
read -p '输入姓与名,中间空格隔开:' lastName firstName
echo "my fullName is $lastName-$firstName
-n :限制字符数目
-n 参数,可以限制用户输入的字符串的最大长度(字符数)
#!/bin/bash
read -p 'please input your name(5 char max):' -n 5 name
echo "my name is $name"
read -p '输入姓与名,中间空格隔开:' lastName firstName
echo "my fullName is $lastName-$firstName"
-t :限制输入时间
-t 参数,可以限定用户的输入时间(以秒为单位),超过这个时间,就不读取输入了
-s :隐藏输入内容
-s 参数,可以隐藏输入内容。一般用不到,但如果想要用户输入的是一个密码,那 -s 参数还是有用的
数学运算
在 Bash 中,所有的变量都是字符串!
Bash 本身不会操纵数字,因此它也不会做运算。不过可以用命令来达到目的。需要用到 let 命令。let 命令可以用于赋值
例如
let "a = 5"
let "b = 2"
let "c = a + b"
echo $c
可用的运算符
| 运算 | 符号 |
|---|---|
| 加法 | + |
| 减法 | - |
| 乘法 | * |
| 除法 | / |
| 幂(乘方) | ** |
| 余(整数除法的余数) | % |
let "a = 5 * 3" # $a = 15
let "a = 4 ** 2" # $a = 16 (4 的平方)
let "a = 8 / 2" # $a = 4
let "a = 10 / 3" # $a = 3
let "a = 10 % 3" # $a = 1
环境变量
env
env为全局变量,可以在任意脚本程序使用
其中几个比较重要的几个环境变量
-
SHELL:指明目前你使用的是哪种 Shell。我目前用的是 Bash(因为 SHELL=/bin/bash)。
-
PATH:是一系列路径的集合。只要有可执行程序位于任意一个存在于 PATH 中的路径,那我们就可以直接输入可执行程序的名字来执行,而不需要加上所在路径前缀或进入到可执行程序所在目录去执行。
-
HOME:家目录所在的路径。
-
PWD:目前所在的目录。
这些环境变量和平时使用变量一样
#!/bin/bash
echo "default Shell is $SHELL"
如果需要自己定义环境变量。可以用 export 命令来完成
export X_ENV=beta
echo "X_ENV is $X_ENV"
export命令直接在终端中也是可以直接使用的,如果是修改,注意覆盖的问题,比如PATH
参数变量
Shell 脚本也可以接收参数的
./test.sh 参数1 参数2 参数3 ...
这些个 参数1,参数2,参数3 … 被称为“参数变量”。
如何接收这些参数到我们的脚本中
- $# :包含参数的数目。
- $0 :包含被运行的脚本的名称 (示例中就是 test.sh )。
- $1:包含第一个参数。
- $2:包含第二个参数。 …
- $8 :包含第八个参数。 … 以此类推。
| 特殊符号 | 含义 |
|---|---|
| $# | 传给脚本的参数个数 |
| $0 | shell脚本文件的名字 |
| $1 | 传递给该shell脚本的第一个参数 |
| $2 | 传递给该shell脚本的第二个参数 |
| $@ | 传给脚本的所有参数的列表 |
| $* | 以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个 |
| $$ | 脚本运行的当前进程ID号,Shell本身的PID |
| $? | 显示最后命令的退出状态,0表示没有错误,其他表示有错误 |
| $! | Shell最后运行的后台Process的PID |
| $- | 使用Set命令设定的Flag一览 |
| $() | 相当于``,括号中放的是命令 |
| ${} | 括号中放的是变量。例如echo P A T H 取 P A T H 变 量 的 值 并 打 印 , 也 可 以 不 加 括 号 比 如 {PATH}取PATH变量的值并打印,也可以不加括号比如PATH取PATH变量的值并打印,也可以不加括号比如PATH |
| $1~n | 添加到Shell的各参数值。2是第2参数…。以此类推 |
条件
if : 最简单的条件
if [ 条件测试 ]
then
做这个
fi
fi 是 if 的反转写法,表示“if 语句结束”。then 是英语“那么”的意思。
“做这个”只有在“条件测试”为真时,才会被执行。
注意:方括号
[]中的条件测试两边必须要空一格。不能写成[test],而要写成[ test ]。 Shell 就是这么规定的
当然了,if 语句的基本写法还有一种,那就是把 then 写在 if [ 条件测试 ] 后面,如下:
if [ 条件测试 ]; then
做这个
fi
用这种写法时,在 if 条件判断和 then 之间要加一个分号。
例子
#!/bin/bash
read -p '输入用户名:' name
if [ $name = "liwei" ]
then
echo "Hello $name !"
fi
在 Shell 语言中,“等于”是用一个等号( = )来表示的,这和大多数编程语言不同。C 语言中“等于”是用两个等号( == )来表示的。但 Shell 中用两个等号来表示“等于”的判断也是可以的。
else : 否则
if [ 条件测试 ]
then
做这个
else
做那个
fi
例子
#!/bin/bash
read -p '输入用户名:' name
if [ $name = "liwei" ]
then
echo "Hello $name !"
else
echo "$name 你被拦截了"
fi
elif : 否则,如果
if [ 条件测试 1 ]
then
做事情 1
elif [ 条件测试 2 ]
then
做事情 2
elif [ 条件测试 3 ]
then
做事情 3
else
做其他事情
fi
例子
#!/bin/bash
read -p '输入用户名:' name
if [ $name = "liwei" ]
then
echo "Hello $name !"
elif [ $name = "xiaoming" ]
then
echo "Hello $name"
elif [ $name = "zhangsan" ]
then
echo "Hello $name"
else
echo "$name who are you 你被拦截了"
fi
条件测试
Bash 中可以做三种测试:
- 测试字符串
- 测试数字
- 测试文件
- 测试字符串
| 条件 | 意义 |
|---|---|
$string1 = $string2 | 两个字符串是否相等。Shell 大小写敏感,因此 A 和 a 是不一样的。 |
$string1 != $string2 | 两个字符串是否不同。 |
| -z $string | 字符串 string 是否为空。z 是 zero 的首字母,是英语“零”的意思。 |
| -n $string | 字符串 string 是否不为空。n 是英语 not 的首字母,是英语“不”的意思。 |
- 数字测试
| 条件 | 意义 |
|---|---|
$num1 -eq $num2 | 两个数字是否相等。和判断字符串所用的符号( = )不一样。eq 是 equal 的缩写,是英语“等于”的意思。 |
$num1 -ne $num2 | 两个数字是否不同。ne 是 not equal 的缩写,是英语“不等于”的意思。 |
$num1 -lt $num2 | 数字 num1 是否小于 num2。lt 是 lower than 的缩写,是英语“小于”的意思。 |
$num1 -le $num2 | 数字 num1 是否小于或等于 num2。le 是 lower or equal 的缩写,是英语“小于或等于”的意思。 |
$num1 -gt $num2 | 数字 num1 是否大于 num2。gt 是 greater than 的缩写,是英语“大于”的意思。 |
$num1 -ge $num2 | 数字 num1 是否大于或等于 num2。ge 是 greater or equal 的缩写,是英语“大于或等于”的意思。 |
- 条件测试
| 条件 | 意义 |
|---|---|
| -e $file | 文件是否存在。e 是 exist 的首字母,表示“存在”。 |
| -d $file | 文件是否是一个目录。因为 Linux 中一切都是文件,目录也是文件的一种。d 是 directory 的首字母,表示“目录”。 |
| -f $file | 文件是否是一个文件。f 是 file 的首字母,表示“文件”。 |
| -L $file | 文件是否是一个符号链接文件。L 是 link 的首字母,表示“链接”。 |
| -r $file | 文件是否可读。r 是 readable 的首字母,表示“可读的”。 |
| -w $file | 文件是否可写。w 是 writable 的首字母,表示“可写的”。 |
| -x $file | 文件是否可执行。x 是 executable 的首字母,表示“可执行的”。 |
$file1 -nt $file2 | 文件 file1 是否比 file2 更新。nt 是 newer than 的缩写,表示“更新的”。 |
$file1 -ot $file2 | 文件 file1 是否比 file2 更旧。ot 是 older than 的缩写,表示“更旧的”。 |
- 一次测试多个条件
| 符号 | 意义 |
|---|---|
| && | 两个 &。表示“逻辑与”。此符号两端的条件必须全为真,整个条件测试才为真;只要有一个不为真,整个条件测试为假。 |
| II | 两个竖线。表示“逻辑或”。此符号两端的条件只要有一个为真,整个条件测试就为真;只有两个都为假,整个条件测试才为假。 |
- 反转测试(非测试)
可以用“否定”来反转测试条件,用到感叹号( ! )
#!/bin/bash
read -p 'Enter a file path : ' file
if [ ! -e $file ]
then
echo "$file does not exist"
else
echo "$file exists"
fi
case : 测试多个条件
跟正常编程语言switch case语句类似
例子:
这里用的脚本参数输入
#!/bin/bash
case $1 in
"liwei")
echo "Hello liwei !"
;;
"xiaoming")
echo "Hello xiaoming !"
;;
"zhangsan")
echo "Hello zhangsan !"
;;
"lisi")
echo "Hello lisi !"
;;
*)
echo "Sorry, I do not know you."
;;
esac
分析一下上面的脚本内容,因为有很多新的内容:
case $1 in:$1表示我们要测试的变量是输入的第一个参数。in 是英语“在…之中”的意思。"liwei"):测试其中一个 case,也就是$1是否等于"liwei"。当然,这里也可以用星号来做通配符来匹配多个字符,例如"li*")可以匹配所有以 li 开头的字符串。;;:类似于主流编程语言中的break;,表示结束 case 的读取,程序跳转到 esac 后面执行。*):相当于 if 条件语句的 else,表示“否则”,就是“假如不等于上面任何一种情况”。esac:是 case 的反写,表示 case 语句的结束。
至于使用if还是case语句,合理选择,跟正常编程语言一致
shell 循环
-
while 循环
while [ 条件测试 ] do 做某些事 done -
until 循环
与 while 这个关键字相反的有一个 until 关键字,until 在英语中是“到…为止,直到…时”的意思。 它也可以实现循环,只不过逻辑和 while 循环正好相反。 -
for 循环
for 变量 in '值1' '值2' '值3' ... '值n' do 做某些事 done
shell 函数
函数可以把一块代码包裹起来,使之成为一个整体,完成某些任务。方便复用,使程序就会变得有条理。
定义函数
#!/bin/bash
print_something () {
echo "Hello, I am a function"
}
print_something
print_something
Shell 中的函数的圆括号里不能放置参数
函数的完整定义必须置于函数的调用之前。
传递参数
Shell 函数中,给它传递参数的方式很像给 Shell 脚本传递命令行参数。把参数直接置于函数名字后面,然后就像我们之前 Shell 脚本的参数那样:$1,$2,$3等等
#!/bin/bash
print_something () {
echo Hello $1
}
print_something liwei
print_something xiaoming
print_something zhangsan
print_something lisi
返回值
变量作用范围