目录
一、shell脚本的详细介绍
1.1 基础概念
Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
它虽然不是Linux系统核心的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Linux系统的关键。
可以说,shell使用的熟练程度反映了用户对Linux使用的熟练程度。
Shell有两种执行命令的方式:
•交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。
•批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。
Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。
Shell初学者请注意,在平常应用中,建议您不要用 root 帐号运行 Shell 。作为普通用户,不管您有意还是无意,都无法破坏系统;但如果是 root,那就不同了,只要敲几个字母,就可能导致灾难性后果。
1.2 几种常见的Shell
上面提到过,Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本。
Linux上常见的Shell脚本解释器有bash、sh、ash、csh、ksh,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器。
bash
bash是Linux系统默认使用的shell。bash由Brian Fox和Chet Ramey共同完成,是BourneAgain Shell的缩写,内部命令一共有40个。
Linux使用它作为默认的shell是因为它有诸如以下的特色:
•可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。
•自动通过查找匹配的方式给出以某字符串开头的命令。
•包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。
sh
sh 由Steve Bourne开发,是Bourne Shell的缩写,各种UNIX系统都配有sh。
ash
ash shell 是由Kenneth Almquist编写的,Linux中占用系统资源最少的一个小shell,它只包含24个内部命令,因而使用起来很不方便。
csh
csh 是Linux比较大的内核,它由以William Joy为代表的共计47位作者编成,共有52个内部命令。该shell其实是指向/bin/tcsh这样的一个shell,也就是说,csh其实就是tcsh。
ksh
ksh 是Korn shell的缩写,由Eric Gisin编写,共有42条内部命令。该shell最大的优点是几乎和商业发行版的ksh完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。
1.3 Shell与编译型语言的差异
大体上,可以将程序设计语言可以分为两类:编译型语言和解释型语言。
编译型语言
很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将我们写好的
源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。
运行程序时,直接读取目标代码(object code)。由于编译后的目标代码(object code)非常接近计算机底层,因此执行效率很高,这是编译型语言的优点。
但是,由于编译型语言多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象,往往实现一个简单的功能需要大量复杂的代码。例如,在C++里,就很难进行“将一个目录里所有的文件复制到另一个目录中”之类的简单操作。
解释型语言
解释型语言也被称作“脚本语言”。执行这类程序时,解释器(interpreter)需要读取我们编写的源代码(source code),并将其转换成目标代码(object code),再由计算机运行。因为每次执行程序都多了编译的过程,因此效率有所下降。
使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。不过权衡之下,通常使用脚本编程还是值得的:花一个小时写成的简单脚本,同样的功能用C或C++来编写实现,可能需要两天,而且一般来说,脚本执行的速度已经够快了,快到足以让人忽略它性能上的问题。脚本编程语言的例子有PHP、awk、Perl、Python、Ruby与Shell。
1.4 什么时候使用Shell
因为Shell似乎是各UNIX系统之间通用的功能,并且经过了POSIX的标准化。因此,Shell脚本只要“用心写”一次,即可应用到很多系统上。因此,之所以要使用Shell脚本是基于:
•简单性:Shell是一个高级语言;通过它,你可以简洁地表达复杂的操作。
•可移植性:使用POSIX所定义的功能,可以做到脚本无须修改就可在不同的系统上执行。
•开发容易:可以在短时间内完成一个功能强大又妤用的脚本。
但是,考虑到Shell脚本的命令限制和效率问题,下列情况一般不使用Shell:
1.资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)。
2.需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算(这种情况一般使用C++或FORTRAN 来处理)。
3.有跨平台(操作系统)移植需求(一般使用C 或Java)。
4.复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)。
5.对于影响系统全局性的关键任务应用。
6.对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵、破解、恶意破坏等等。
7.项目由连串的依赖的各个部分组成。
8.需要大规模的文件操作。
9.需要多维数组的支持。
10.需要数据结构的支持,比如链表或数等数据结构。
11.需要产生或操作图形化界面 GUI。
12.需要直接操作系统硬件。
13.需要 I/O 或socket 接口。
14.需要使用库或者遗留下来的老代码的接口。
15.私人的、闭源的应用(shell 脚本把代码就放在文本文件中,全世界都能看到)。
如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧——或许是PHP、Perl、Tcl、Python、Ruby——或者是更高层次的编译语言比如C/C++,或者是Java。即使如此,你会发现,使用shell来原型开发你的应用,在开发步骤中也是非常有用的。
二、shell脚本的使用
2.1 建立和运行shell程序
shell脚本程序:
按照一定的语法结构把若干linux命令组织在一起,是这些命令按照我们的要求完成一定的功能。它可以进行类似程序的编写,并且不需要编译就能执行。(只需修改其权限)
像编写高级语言的程序一样,编写shell程序需要一个文本编辑器,如VI和VIM,通常使用VIM文本编辑器,支持更便利的插入模式。
首先使用VIM编辑器编辑一个hello.sh文件:
#!/bin/bash
#hello world example
echo "hello world"
这样,一个最简单的shell程序就编写完了。
第一行:
#!说明hello.sh这个文件的类型的,这有点类似于Windows系统下的用不同的文件后缀来表示不同的文件类型,但又不完全相同。Linux系统根据#!及该字符串后面的信息确定该文件的类型。在#!之后是一个路径名,这个路径名指定了一个解释脚本中命令的程序。
第二行:
“#hello world example”就是shell程序的注释,(但后面紧接着“!”号的除外)
第三行:
echo(回显)语句的功能是把echo后面的字符串输出到标准输出中。现在,建立和写好之后,那么该程序如何运行呢?
由于该脚本没有执行的权限,只需要修改其执行的权限即可,执行:
chmod a+x hello.sh
然后输入命令:
./hello.sh
即可看到效果。
2.2 shell变量
变量是一个存储值的实体,可以是一个名字,或者一个特殊的值。shell可以创建,分配和删除变量。
(1)变量设置规则:
a.变量与变量内容以等号(=)来连接
b.等号两边不能直接接xx
c.变量名称只能是英文字母与数字或下划线,但是数字不能是开头符d.引用变量名用“$变量名”
#!/bin/bash
#hello world example
a="hello world"
echo $a
以上例子一样可以输出hello world。
e.若有空格符可以使用双引号或单引号将变量内容结合起来,但必须注意,双引号的特殊字符可以保有变量的特性,但是单引号内的特殊字符则仅为一般字符。
#name="$LOGNAME is hh" //root is hh
#name='$LOGNAME is hh' //$LOGNAME is hh
f.通常大写字符为系统预设变量,自行设定的变量可以使用小写字符
(2)取消变量:
用unset变量名
#!/bin/bash
#hello world例子
a="hello world"
unset a
echo $a
以上例子将不会打印出任何xx。
2.3 shell特殊变量
$0:执行的脚本的文件名
$1:脚本文件的第一个参数名
$#:传递到脚本的参数个数
$*:以一个单字符串显示所有向脚本传递的参数
$$:脚本运行的当前进程ID号
$?:显示最后命令的退出状态,0表示没有错误,其它值表示有错误例子a.sh
#!/bin/bash
echo "the file is $0"
echo "the parameter is $1 $2"
echo "number of parameter is $#"
echo "all parameter is $*"
echo $?
加权限后执行./a.sh hh mm gg
则打印
the file is ./a.sh
the parameter is hh mm
number of parameter is 3
all parameter is hh mm gg0
2.4 常用shell内嵌命令
(1)echo:显示变量内容
(2)env:显示目前系统中主要的预设变量内容
(3)set:显示目前系统中全部的变量内容
语法:set [-options] [var1 var2 …]
-a:标示已修改的变量,以供输出至环境变量
-b:使被中止的后台程序立刻回报执行状态
-d:Shell预设会用杂凑表记忆使用过的指令,以加速指令的执行。使用-d参数可取消
-e:若指令传回值不等于0,则立即退出shell
-f:取消使用通配符
-h:自动记录函数的所在位置
-k:指令所给的参数都会被视为此指令的环境变量
-l:记录for循环的变量名称
-m:使用监视模式
-n:测试模式,只读取指令,而不实际执行
-p:启动优先顺序模式
-P:启动-P参数后,执行指令时,会以实际的文件或目录来取代符号连接
-t:执行完随后的指令,即退出shell
-u:当执行时使用到未定义过的变量,则显示错误信息
-v:显示shell所读取的输入值
-H shell:可利用"!"加<指令编号>的方式来执行 history 中记录的指令
-x:执行指令后,会先显示该指令及所下的参数
+<参数>:取消某个set曾启动的参数。与-<参数>相反-o option:特殊属性还有很多,大部分与上面的可选参数功能相同
调用set设置一个或多个参数时,set会赋值位置参数的例子:
#!/bin/bash
set first second third
echo $3 $2 $1
(4)read:从键盘读入变量内容
-a:把读取的数据赋值给数组array,从下标0开始
-d:用字符串delimiter指定读取结束的位置
-e:在获取用户输入的时候,对功能键进行编码转换
-p:显示提示信息,提示内容为prompt
-r: 原样读取(Raw mode),不把反斜杠字符解释为转义字符
-n:读取num个字符,而不是整行字符
-s:静默模式(Slient mode),不会在屏幕上显示输入的字符,输入密码或其它确认信息时常用
-t:设置超时时间
-u:使用文件描述符fd作为输入源,而不是标准输入,类似于重定向
语法:read [-options] [var1 var2 …]
options和var都是可选的,如果没有提供变量名,那么读取的数据将存放在环境变量REPLY
变量中;
在交互式shell中的例子:
#!/bin/bash
read
echo $REPLY
执行后先输入什么后输出什么
同时给多个变量赋值的例子:
#!/bin/bash
read -p "请输入姓名:" name
read -p "请输入性别:" sex
read -p "请输入年龄:" age
#打印上面输入的变量内容
echo "姓名:${name}"
echo "性别:${sex}"
echo "年龄:${age}"
执行后先输入什么后根据变量输出什么
时间限制和静默模式的例子:
#!/bin/bash
read -t 10 -sp "请输入密码(10s内):" pwd1
echo # 这是换行(使用了-s静默模式才需要这个换行符)
read -t 10 -sp "请再次输入密码(10s内):" pwd2
printf "\n"
# 校验密码两次是否一致
if [ $pwd1 == $pwd2 ] # 这里变量名前后一定都要有空格
then
echo "两次密码一致,认证通过~"
else
echo "密码不一致,验证失败!"
fi
执行后根据判断输出echo的结果
(5)declare:声明变量内容
语法:declare [x/-][aArxif] [变量名称=具体值]
+/-
:-
用来指定变量有该属性,+
是取消该属性;-a:定义为数组array,格式为 数组名=(值1 值2 ...) 或 数组名=([0]=值1 [1]=值2 ...)
-
A
:Array,设置为key-value关联数组(索引是字符串),格式 数组名=([字符串key1]=值1 [字符串key2]=值2 ...)
-f:定义为函数function
-i:定义为整数integer
-r:定义为“只读”
-x:定义为透过环境输出变量
一般格式例子:
#!/bin/bash
a=1+1
declare -i b=1+1
echo $a
echo $b
数组格式例子:
#!/bin/bash
# 普通数组
declare -a arr1=("李明" 33 "hobbit1")
echo ${arr1[*]} # 千万别忘了这里的花括号
echo ${arr1[1]}
declare -a arr2=([0]="王强" 55 "hobbit2")
echo ${arr2[@]}
# 关联数组
declare -A arr3=(["name"]="王武" [age]=34 [hobbit]="hobbit3")
echo ${arr3[@]}
echo ${arr3["age"]}
执行后输出结果为
李明 33 hobbit1
33
王强 55 hobbit2
王武 34 hobbit3
34
(6)exit 用于退出当前shell环境进程结束运行,并且可以返回一个状态码.一般使用$?可以获取状态码.
使用场景:
1.结束当前shell进程
2.当shell进程执行出错退出时,可以返回不同的状态值代表不同的错误.
比如执行一个脚本文件里面操作一个文件时,可以返回1表示文件不存在,2表示文件没有读取权限,3表示文件类型不对.
例子:
vim exit.sh
#!/bin/bash
echo 'hello'
exit 2
echo 'word'
执行后输出结果为
hello
2.5 条件测试
在写shell脚本时,经常遇到的问题就是判断字符串是否相等,可能还要检查文件状态或进行数字测试,只有这些测试完成才能做下一步动作。
test命令就是用于测试字符串,文件状态和数字的.test命令两种格式:
test condition
或[ condition ]
使用方括号时,要注意在条件两边加上空格
三、shell脚本编程基础知识
3.1 shell基本运算符
(1)算术运算符
+:对两个变量做加法。
-:对两个变量做减法。
\*:对两个变量做乘法。
/:对两个变量做除法。
**:对两个变量做幂运算。
%:取模运算,第一个变量除以第二个变量求余数。
+=:加等于,在自身基础上加第二个变量。
-=:减等于,在第一个变量的基础上减去第二个变量。
*=:乘等于,在第一个变量的基础上乘以第二个变量。
/=:除等于,在第一个变量的基础上除以第二个变量。
%=:取模赋值,第一个变量对第二个变量取模运算,再赋值给第一个变量在使用这些运算符时,
需要注意到运算顺序的问题:
例如输入下面的命令,输出1+2的结果。
echo 1+2
shell并没有输出结果3,而是输出了1+2。
在shell中有三种方法可以更改运算顺序:
a、用expr改变运算顺序。可以用echo `expr 1 + 2`来输出1+2的结果,用expr表示后面的表达式为一个数学运算。需要注意的是,`并不是一个单引号,而是“Tab”键上面的那个符号。
b、用let指示数学运算。可以先将运算的结果赋值给变量b,运算命令是b=let1 + 2。然后用echo $b来输出b的值。如果没有let,则会输出1+2。
c、用$[]表示数学运算。将一个数学运算写到$[]符号的中括号中,中括号中的内容将先进行数学运算(中括号中可以包含空格)。例如命令echo $[1+2],将输出结果3。
(2)关系运算符
-eq:数值相等
-ne:数值不相等
-ge:数1大于等于数2
-lt:数1小于数2
-gt:数1大于数2
-le:数1小于等于数2
输入test 1 -lt 2 && echo "yes"则打印yes
(3)xx
-a:(and)两状况同时成立!test -r file -a -x file,则file同时具有r和x权限时,才为true。
-o:(or)两状况任何一个成立!test -r file -o -x file,则file具有r或x权限时,就为true。
!:相反状态,test!-r file,当file不具有r权限时,就为true。
(4)字符串运算符
=:两个字符串相等!
=:两个字符串不相等
-z:空串
-n:非空串
输入test "a" = "a" && echo "yes"则打印yes
(5)测试文件状态的条件表达式:
-e:是否存在-d:
是目录
-f:是文件
-L:符号连接-s文件非空
-r:可读
-w:可写
-x:可执行
3.2 shell条件语句
(1)if...fi语句结构
if [条件1];then
执行程序
fi
例子:
#!/bin/bash
declare -i a=10
declare -i b=20
if [ $a == $b ];then
echo "a is equal to b"
fi
if [ $a != $b ];then
echo "a is not equal to b"
fi
输出结果为:
a is not equal to b
(2)if...else...fi语句结构
if [条件1];then
执行程序1
else
执行程序2
fi
例子:
#!/bin/bash
declare -i a
echo "input 1 or 2"
read a
if [ $a -eq 1 ];then
echo "1"
else
echo "2"
fi
打印为input1 or 211
(3)if...elif...else...fi语句结构
if [条件1];then
执行程序1
elif[条件2];then
执行程序2
else
执行程序3
fi
例子:
#!/bin/bash
declare -i a=10
declare -i b=20
if [ $a == $b ];then
echo "a is equal to b"
elif [ $a -gt $b ];then
echo "a is greater than b"
else
echo "a is less than b"
fi
输出结果为:
a is less than b
(4)case...esac语句结构
case $变量名称in
“第一个变量内容”)
程序1
;;
“第二个变量内容”)
程序2
;;
*)
其它程序
exit 1
esac
例子:
#!/bin/bash
echo "you like"
echo "1 is apple"
echo "2 is orange"
echo "input your choice"
read a
case $a in
1)
echo "you like apple"
;;
2)
echo "you like orange"
;;
*)
echo "you like nothing"
exit 1
esac
执行后结果为:
you like
1 is apple
2 is orange
input your choice1you like apple
3.3 shell循环类型
(1)for循环
循环操作项目清单。重复一组命令列表中的每个项目。
语法一:
for ((初始值;限制值;执行步阶))
do
程序
done
初始值:
变量在循环中的起始值
限制值:
当变量值在这个限制范围内时,就继续进行循环
执行步阶:
每作一次循环时,变量的变化量
例子:
#!/bin/bash
declare -i s
s=0
for (( i=1; i<=100; i++ ))
do
s=s+i
done
echo "the sum is $s"
输出结果为:
the sum is 50
语法二:
for var in con1 con2 con3 ...//var是一个变量
do
程序
done
第一次循环时,$var的内容为con1
第二次循环时,$var的内容为con2
第三次循环时,$var的内容为con3
例子:
#!/bin/bash
declare -a A
A=(1 2 3 4 5 6 7 8 9 10)
declare -i c
for i in 1 3 5 7 9
do
c=c+A[$i];
done
echo "the count is $c"
输出结果为:
the count is 30
(2)while循环
while循环,使您能够重复执行一组命令,直到某些条件发生。它通常用于当你需要反复操纵的变量值。
语法如下:
while [条件]
do
程序
done
当条件成立的时候进入while循环,直到条件不成立时才退出循环例子:
#!/bin/bash
declare -i a=0
while [ $a -lt 10 ]
do
echo $a
a=a+1
done
这将产生以下结果:
0~9
(3)until循环a进行检查,看该值是否小于10。如果a的值小于完美的情况下,你需要执行的一组命令某个条件为真时,while循环执行。有时候,你需要执行一组命令,直到某个条件为真。
语法如下:
until命令
do
程序
done
这种方式与while循环恰恰相反,当命令成立的时候退出循环,否则继续循环例子:
#!/bin/bash
declare -i a=0
until [ $a -gt 10 ]
do
echo $a
a=a+1
done
这将产生以下结果:
0~10
(4)select循环
语法如下:
select var in word1 word2 ...
do
程序
done
例子:
#!/bin/bash
echo "what is this?"
select i in tea cofee water apple orange none
do
case $i in
tea|cofee|water)
echo "drink";;
apple|orange)
echo "fruit";;
none)
break;;
*) echo "ERROR:
Invalid selection";;
esac
done
打印信息为:
what is this?
1) tea
2) cofee
3) water
4) apple
5) orange
6) none
#? 1
drink
#? 6
四、shell数组
shell数组的使用
$ a=(123 34 3 5)
$ echo $a //默认获取第一个元素
123
$ echo ${a[1]}//通过下标访问
34
$ echo ${a[@]}//访问整个数组,@或者*获取整个数组
123 34 3 5
$ echo ${#a[@]}//获取数组的长度4$ echo ${#a[3]}//获取字符串长度1$ echo ${a[@]:1:2}//切片方式获取一部分数组内容
34 3
$ echo ${a[@]:2}//从第二个元素开始
3 5
$ echo ${a[@]:
:2}//到第二个元素
123 34
应用实例:
#!/bin/bash
declare -a a=(1 2 3 4 5 )
for (( i=0; i<=4; i++ ))
do
echo ${a[i]}
done
这将产生以下结果:
12345
五、shell函数
使用函数来执行重复性的任务,是一个很好的方式来创建代码的重用。
代码重用是现代面向对象编程的原则的重要组成部分。
5.1 创建函数:
声明一个函数语法:
function_name () {
例子:
#!/bin/bash
# Define your function here
Hello () {
echo "Hello World"
}
# Invoke your function
Hello
当你想执行上面的脚本,它会产生以下结果:
Hello World
5.2 参数传递给函数:
你可以定义一个函数,它接受参数,而调用这些函数。将这些参数代表$1,$2,依此类推。例子:
#!/bin/bash
# Define your function here
Hello () {
echo "Hello World $1 $2"
}
# Invoke your function
Hello Zhang lisi
这将产生以下结果:
Hello World Zhang lisi
5.3 从函数的返回值:
例子:
#!/bin/bash
# Define your function here
Hello () {
echo "Hello World $1 $2"
return 1
}
# Invoke your function
Hello Zhangsan lisi
r=$?
echo "Return value is $r"
这将产生以下结果:
Hello World Zhangsan lisi
Return value is 1
5.4 嵌套函数:
函数更有趣的功能之一是,他们可以调用本身以及调用其他函数。被称为递归函数调用自身的函数。
例子:
#!/bin/bash
# Calling one function from another
one () {
echo "This is the first function"
two
}
two () {
echo "This is the second function"
}
# Calling function one.
one
这将产生以下结果:
This is the first function
This is the second function