购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.6 shell 编程入门

从用户的角度来看,shell是用户与Linux系统沟通的桥梁,或者说是一个翻译,用户可以通过输入命令执行程序,也可以通过shell脚本执行更加复杂的操作。在系统管理领域,shell编程起着非常重要的作用。深入的熟悉shell编程是每一个Linux工程师的必修课。

要创建一个shell脚本,我们要使用任何编辑器,如vim在文本文件中编写shell脚本,保存的文件最好是.sh后缀的,表明这是一个shell脚本文件。

shell脚本格式固定如下。

第一行中的#!用来告诉系统其后路径指定的程序就是用来解释、执行该脚本的shell程序。除第一行外,所有以#开始的行都是注释行,不会被执行。shell编程也像高级编程语言一样,有变量定义、流程控制语句等。

编辑完毕后,将脚本保存为filename.sh文件。执行脚本程序前,先将脚本文件的访问权限改为可执行的。

执行脚本的方法如下。

1 shell变量定义

shell变量没有数据类型,都是字符串,即使数值也是字符串。

(1)定义变量:变量名称=值,如a=“hello”(等号两边不能有空格)。如果值有空格,则必须用""号或者“’’号引用起来。

示例如下。

(2)引用变量:echo $a 或 echo ${a} 或 echo“${a}”。注意‘’和“”的区别:单引号消除所有字符的特殊意义;双引号消除除$、””、’’三种以外其他字符的特殊意义。下面举例说明。

① 命令执行结果如下。

等同于#echo ${a} #echo“${a}”。

② 命令执行结果如下。

因为此时把aa作为一个整体变量,而且没有定义,所以输出前面的字符串。

③ 命令执行结果如下。

④ 命令执行结果如下。

双引号不会消除特殊字符的意义

⑤ 命令执行结果如下。

单引号会消除特殊字符的意义。

⑥ 命令执行结果如下。

(3)删除变量:unset 变量名,示例如下。

(4)设置变量为只读变量。

只读变量不能被修改。

(5)使用read命令允许用户从键盘输入,赋值给一个变量,实现程序交互。

这里,read命令从键盘读取了hello赋值给变量b。

(6)$? 表示上一条命令的执行结果(0表示成功,非0表示失败),或者函数返回值。示例如下。

(7)转义符用于转义特殊的字符,示例如下。

(8)命令替换符号(Tab键上边的波浪号键)的使用如下。

即先执行命令引用符号内的命令,用命令执行的结果替换命令引用符处的内容。

(9)表达式计算如下。

举例,写1.sh要求读入1个目录名,在当前目录下创建该目录,并复制etc下的conf文件到该目录,统计etc下所有目录的数目到etcdir.txt中。

2 标准变量或环境变量

标准变量或环境变量:系统预定义的变量,一般在/etc/profile中进行定义。

HOME 用户主目录;PATH文件搜索路径;PWD用户当前工作目录;PS1、PS2提示符,还有UNAME、HOSTNAME、LOGNAME等。如#echo $PWD。用echo $PATH显示,用env看环境所有变量,用env | grep "name"查找。

用"export"进行设定或更改为全局变量,用“unset 变量名”来取消全局变量的定义。

例如,定义本地变量 name="Red Hat Linux",export name把name 变为全局变量;sh进入子shell,echo ${name}全局变量可以作用于子进程,而本地变量不可以。或直接输出export name="Red Hat Linux",bash 退出子shell,进入父shell。

设置环境变量,如把/etc/apache/bin目录添加到PATH中。

(1)#PATH=$PATH:/etc/apache/bin。

(2)vim /etc/profile 在里面添加PATH=$PATH:/etc/apache/bin。

(3)vim~/.bash_profile 在里面修改PATH行,把/etc/apache/bin加进去,此种方法针对当前用户有效。

3 自动变量

(1)$1,$2…$n表示传入的参数,$0表示shell程序名称,每一项相当于main函数中的argv[i]。

(2)$#表示传递到脚本的参数列表,或表示参数个数,等价于main函数中的argc-1。

(3)$@表示传入脚本的全部参数,等价于argv[1]——argv[n-1]。

(4)$*表示显示脚本全部参数。

(5)$?表示前个命令执行情况。0:成功;非0:失败。

(6)$$表示脚本运行的当前进程号。

(7)$!表示运行脚本最后一个命令。

举例如下。

新建文件,写入以下内容。

执行以下命令。

可以通过程序的输出,对比验证各个变量的值。

1.6.1 shell里的流程控制

1 if语句

if 语句格式如下。

只有当 condition为真时,action语句才执行操作,否则不执行操作,并继续执行“if”之后的任何行。

第一行的[]表示条件测试,常用的条件测试有下面几种。

(1)[ -f "$file" ] 判断$file是否是一个文件。

(2)[ $a -lt 3 ] 判断$a的值是否小于3,同样的,-gt和-le分别表示大于或小于等于。

(3)[ -x "$file" ] 判断$file是否存在且有可执行权限,同样的,-r测试文件可读性。

(4)[ -n "$a" ] 判断变量$a是否有值,测试空串用-z。

(5)[ "$a" = "$b" ] 判断$a和$b的取值是否相等。

(6)[ cond1 -a cond2 ] 判断cond1和cond2是否同时成立,-o表示cond1和cond2有一成立。

if else语句格式如下。

在使用时,将“if”和“then”放在不同行,若同行放置,则if 语句必须要以“;”结束。

例如,用参数传1个文件名,该文件如果是文件并且可读可写就显示该文件,如果是目录就进入该目录,并判断ls.sh是否存在,如果不存在,就建立1个ls.sh的文件并运行该文件。该文件的内容是ls -li /etc > etc.list

2 case 语句

case常用的语句格式如下。

(1)例1,智能解压文件,其程序如下所示。

(2)例2,其程序如下所示。

(3)例3,编写一个加、减、乘、除、取模计算器,其程序如下所示。

3 for循环

(1)例1,其程序如下所示。

(2)例2,其程序如下所示。

(3)例3,/etc/r*中的文件和目录,其程序如下所示。

(4)例4,其程序如下所示。

4 while 语句

举例如下。

5 until 语句

1.6.2 Here Documents

1 Here Documents(嵌入文档)

Here Documents作为重定向的一种方式,指示shell从源文件的当前位置开始读取输出,直到遇到只包含一个单词的文本行时结束。在该过程中读到的所有文本行都将作为某一个命令的标准输入而使用。

Here Documents的使用形式为:command <<[-] limit_string msg_body limit_string。

如果用双引号或单引号将limit_string引用起来或用转义符\将其转义,则Here Documents中的文本将不被扩展,即参数替换被禁用。否则,Here Documents中的所有文本都将进行常规的参数扩展、命令替换、表达式计算。在后一种情况下,字符序列\<newline>将被忽略,并且必须对元字符\、$和`使用\进行转义。如果用<<而不是<<-,则后面的limit_string必须位于行首,否则,若Here Documents用在函数内部,则会报语法错误;用在函数外面,其后面的所有内容均会被当作Here Documents的内容。如果重定向操作符是<<-,则msg_body和limit_string行中的所有前缀TAB字符都将被忽略(但空格不会被忽略)。这样,源代码中的Here Documents就可以按照优雅的嵌入方式进行对齐。

2 技术的应用

下面我们考察一下在shell脚本中使用Here Documents的必要性所在。shell脚本对于文本文件的处理是非常方便的。因此,实现本文所要完成的工作还是比较简单的,只需对所有文件进行一次循环过程。但是问题在于,从①-②,②-③的每一步,实际上都是使用cut、echo、cat等进行流处理,每个处理过程的输出作为下一步骤的输出,这里面自然地要使用重定向的方法。在以前对BASH用得不熟的时候,往往笨拙地使用临时文件存储这些中间结果。例如,00000004.REG -> 00000004.cut -> 00000004.line -> all.txt。这样处理的过程中,代码显得非常杂乱,而且有时候不得不写出多个脚本来处理重定向,这种情况使得问题更为加重。

因此,这次下决心要将代码写得精简一些。在BASH内部可以使用Here Documents进行重定向,使流的输入/输出都是在同一脚本内进行的,不用拆开成多个脚本,也省略了大量的中间文件,使代码非常清晰、紧凑。 Here Documents的一般形式如下。

在上面的语法描述中,0001行的commands是任何shell命令。ID是一个字符串标符,标志着嵌入文档的开始(习惯上,常用的标识符是EOF,等等)。

从0002行可以写任何文本,这里写入的内容都将作为标准输入传送给commands。在最后一行写标识符ID,代码嵌入文本结束。

在使用Here Documents编写脚本的时候注意以下两点。

(1)在000n行上,必须只有一个ID,也就是说,ID前面不能有空格,ID后面必须是回车符。否则shell处理时会出错。这一点在实际时要尤为注意。

(2)当使用 <<- 时, 输入行和000n行上,ID前的任何TAB字符将会被忽略; 嵌入文档内可以写普通文本(比如说,写程序的使用Usage),也可以是标准的shell语句。如果是shell语句,要使用反引号(`)括起来,这时候,语句的标准输出也将作为嵌入文档送给commands。这个特性非常有用。

1.6.3 shell里的函数

函数定义格式如下。

(1)如下所示是一个定义并使用函数的简单例子脚本。

(2)用函数求两个数的和(类似于C语言的int add(int a, int b){ return a+b})。

1.6.4 命令行参数

shell里的命令行参数即位置参数:是由系统提供的参数,就是我们一般说的某个数组的第1,2,3,…个元素;可以采用$i的形式获得某个参数,显然$0就是程序本身,即脚本文件的名字,$1就是第一个参数,$2就是命令行传入的第二个参数……这里需要注意的是,即便你以sh xxx.sh 来执行脚本,$0仍然是xxx.sh,而不是sh!这一点和Perl python是一致的。

1.6.5 shell脚本示例

综合例子——制作菜单如下所示。

1.6.6 脚本调试

在编写shell脚本的时候,有时候会发现脚本所输出的结果并不是我们想要的,在调试的过程中,往往会通过在脚本特定的一些地方加入一些echo的命令,把变量回显出来看是不是跟我们想要的一致。除了这个方法之外还有一些其他的方法可以帮忙调试脚本。shell脚本的几种调试方法如下所示。

(1)-n只读取shell脚本,但不实际执行。

(2)-v 一边执行脚本,一边将执行过的脚本命令打印到标准输出端。

(3)-x 提供跟踪执行信息,将执行的每一条命令和结果一次打印出来。

(4)-c "string" 从strings中读取命令。

-n可用于测试shell脚本是否存在语法错误,但不会实际执行命令。在shell脚本编写完成之后,实际执行之前,首先使用-n选项来测试脚本是否存在语法错误是一个很好的习惯。因为某些shell脚本在执行时会对系统环境产生影响,如生成或移动文件等,如果在实际执行中才发现语法错误,用户就不得不手工做一些系统环境的恢复工作才能继续测试这个脚本。

-c选项使shell解释器从一个字符串中(而不是从一个文件中)读取并执行shell命令。当需要临时测试一小段脚本的执行结果时,可以使用这个选项,如下所示。

-x选项可用来跟踪脚本的执行,是调试shell脚本的强有力工具。-x选项使shell在执行脚本的过程中把它实际执行的每一个命令行显示出来,并且在行首显示一个+号。+号后面显示的是经过了变量替换之后的命令行的内容,有助于分析实际执行的是什么命令。-x选项使用起来简单方便,可以轻松对付大多数的shell调试任务,应把其当作首选的调试手段。这就像C语言的gdb一样有用,有助于检查错误。

使用这些选项有三种方法。

(1)在命令行提供参数:$sh -x script.sh。

(2)脚本开头提供参数:#!/bin/sh -x。

(3)在脚本中用set命令启用或禁用参数:其中,set -x表示启用,set +x表示禁用。

另外,在写脚本的时候,需要特别注意的是添加空格或换行或分号。

例如,text命令: $[ 5 -lt 10 ]左方括号后必须有空格,右方括号前也必须有空格;

shell脚本中函数定义: foo() { 后必须有空格或换行。

shell脚本中,若同一行内写有两条命令,则必须加分号。

与C语言不同的是,shell脚本中,返回值0表示真,1表示假。 d+rKxCex2OuZPrRviB5gFaMwJk9sDn6+tUYubnK83syvSvuoxxf9etTbYvPo2Sbl

点击中间区域
呼出菜单
上一章
目录
下一章
×