shell学习(不错的简单学习教程)

           shell脚本 

 Shell Script,Shell脚本Windows/Dos下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比Windows下的批处理更强大,比用其他编程程序编辑的程序效率更高,毕竟它使用了Linux/Unix下的命令。

S hell Script,Shell脚本与Windows/Dos下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比Windows下的批处理更强大,比用其他编程程序编辑的程序效率更高,毕竟它使用了Linux/Unix下的命令。

 换一种说法也就是,shell script是利用shell的功能所写的一个程序,这个程序是使用纯文本文件,将一些shell的语法与指令写在里面,然后用正规表示法,管线命令以及数据流重导向等功能,以达到我想要的处理目的。

更明白的来说,shell script就像早期dos年代的.bat,最简单的功能就是将许多指令汇整写一起,让使用者很容易的就能够一个操作执行多个命令,而shell script更是提供了数组,循环,条件以及逻辑判断等重要功能,让使用者可以直接以shell来写程序,而不必使用类似C程序语言等传统程序编写的语法。

shell是什么呢?确切一点说,Shell就是一个命令行解释器,它的作用就是遵循一定的语法将输入的命令加以解释并传给系统。它为用户提供了一个向Linux发送请求以便运行程序的接口系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。 Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言(就是你所说的shell脚本)。作为命令语言,它互动式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高阶语言中才具有的控制结构,包括循环和分支。它虽然不是 Linux系统内核的一部分,但它调用了系统内核的大部分功能来执行程序、创建文档并以并行的方式协调各个程序的运行。

 

 

 

 

 

 

 

 

 

 

 

 

Shell教程一

目录;

Shell简介

几种常见的Shell

Shell与编译型语言的差异

什么时候使用Shell

第一个Shell脚本

Shell变量

Shell特殊变量

Shell替换

Shell运算符

Shell注释

Shell字符串

Shell数组

Shell echo命令

shell printf命令

Shell if else语句

Shell case esac语句

Shell for循环

Shell while循环

Shell until循环

Shell跳出循环

Shell函数

Shell函数参数

Shell输入输出重定向

Shell文件包含

 

Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

它虽然不是Unix/Linux系统内核的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Unix/Linux系统的关键。

可以说,shell使用的熟练程度反映了用户对Unix/Linux使用的熟练程度。

注意:单独地学习 Shell 是没有意义的,请先参考Unix/Linux入门教程,了解 Unix/Linux 基础。

Shell有两种执行命令的方式:

· 交互式(Interactive):解释执行用户的命令,用户输入一条命令,Shell就解释执行一条。

· 批处理(Batch):用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。


Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。

Shell初学者请注意,在平常应用中,建议不要用 root 帐号运行 Shell 。作为普通用户,不管您有意还是无意,都无法破坏系统;但如果是 root,那就不同了,只要敲几个字母,就可能导致灾难性后果。

2

 

上面提到过,Shell是一种脚本语言,那么,就必须有解释器来执行这些脚本。

Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell。我们常说有多少种Shell,其实说的是Shell脚本解释器。

bash

bash是Linux标准默认的shell,本教程也基于bash讲解。bash由Brian Fox和Chet Ramey共同完成,是BourneAgain Shell的缩写,内部命令一共有40个。

Linux使用它作为默认的shell是因为它有诸如以下的特色:

· 可以使用类似DOS下面的doskey的功能,用方向键查阅和快速输入并修改命令。

· 自动通过查找匹配的方式给出以某字符串开头的命令。

· 包含了自身的帮助功能,你只要在提示符下面键入help就可以得到相关的帮助。

sh

sh 由Steve Bourne开发,是Bourne Shell的缩写,sh 是Unix 标准默认的shell。

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完全兼容,这样就可以在不用花钱购买商业版本的情况下尝试商业版本的性能了。

注意:bash是 Bourne Again Shell 的缩写,是linux标准的默认shell ,它基于Bourne shell,吸收了C shell和Korn shell的一些特性。bash完全兼容sh,也就是说,用sh写的脚本可以不加修改的在bash中执行。

3

大体上,可以将程序设计语言可以分为两类:编译型语言和解释型语言。

编译型语言

很多传统的程序设计语言,例如Fortran、Ada、Pascal、C、C++和Java,都是编译型语言。这类语言需要预先将我们写好的源代码(source code)转换成目标代码(object code),这个过程被称作“编译”。

运行程序时,直接读取目标代码(object code)。由于编译后的目标代码(object code)非常接近计算机底层,因此执行效率很高,这是编译型语言的优点。

但是,由于编译型语言多半运作于底层,所处理的是字节、整数、浮点数或是其他机器层级的对象,往往实现一个简单的功能需要大量复杂的代码。例如,在C++里,就很难进行“将一个目录里所有的文件复制到另一个目录中”之类的简单操作。

解释型语言

解释型语言也被称作“脚本语言”。执行这类程序时,解释器(interpreter)需要读取我们编写的源代码(source code),并将其转换成目标代码(object code),再由计算机运行。因为每次执行程序都多了编译的过程,因此效率有所下降。

使用脚本编程语言的好处是,它们多半运行在比编译型语言还高的层级,能够轻易处理文件与目录之类的对象;缺点是它们的效率通常不如编译型语言。不过权衡之下,通常使用脚本编程还是值得的:花一个小时写成的简单脚本,同样的功能用C或C++来编写实现,可能需要两天,而且一般来说,脚本执行的速度已经够快了,快到足以让人忽略它性能上的问题。脚本编程语言的例子有awk、Perl、Python、Ruby与Shell。

4

因为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 脚本把代码就放在文本文件中,全世界都能看到)。


如果你的应用符合上边的任意一条,那么就考虑一下更强大的语言吧——或许是Perl、Tcl、Python、Ruby——或者是更高层次的编译语言比如C/C++,或者是Java。即使如此,你会发现,使用shell来原型开发你的应用,在开发步骤中也是非常有用的。

5

打开文本编辑器,新建一个文件,扩展名为sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好,如果你用php写shell 脚本,扩展名就用php好了。

输入一些代码:

1. #!/bin/bash

2. echo "Hello World !"

“#!” 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种Shell。echo命令用于向窗口输出文本。

运行Shell脚本有两种方法。

作为可执行程序

将上面的代码保存为test.sh,并 cd 到相应目录:

chmod +x ./test.sh  #使脚本具有执行权限./test.sh  #执行脚本

注意,一定要写成./test.sh,而不是test.sh。运行其它二进制的程序也一样,直接写test.sh,linux系统会去PATH里寻找有没有叫test.sh的,而只有/bin, /sbin, /usr/bin,/usr/sbin等在PATH里,你的当前目录通常不在PATH里,所以写成test.sh是会找不到命令的,要用./test.sh告诉系统说,就在当前目录找。

通过这种方式运行bash脚本,第一行一定要写对,好让系统查找到正确的解释器。

这里的"系统",其实就是shell这个应用程序(想象一下Windows Explorer),但我故意写成系统,是方便理解,既然这个系统就是指shell,那么一个使用/bin/sh作为解释器的脚本是不是可以省去第一行呢?是的。

作为解释器参数

这种运行方式是,直接运行解释器,其参数就是shell脚本的文件名,如:

/bin/sh test.sh/bin/php test.php

这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。

再看一个例子。下面的脚本使用 read 命令从 stdin 获取输入并赋值给 PERSON 变量,最后在 stdout 上输出:

1. #!/bin/bash

2. 

3. # Author : mozhiyan

4. # Copyright (c) http://see.xidian.edu.cn/cpp/linux/

5. # Script follows here:

6. 

7. echo "What is your name?"

8. read PERSON

9. echo "Hello, $PERSON"

运行脚本:

chmod +x ./test.sh$./test.shWhat is your name?mozhiyanHello, mozhiyan$

6

Shell支持自定义变量。

定义变量

定义变量时,变量名不加美元符号($),如:

1. variableName="value"

注意,变量名和等号之间不能有空格,这可能和你熟悉的所有编程语言都不一样。同时,变量名的命名须遵循如下规则:

· 首个字符必须为字母(a-z,A-Z)。

· 中间不能有空格,可以使用下划线(_)。

· 不能使用标点符号。

· 不能使用bash里的关键字(可用help命令查看保留关键字)。


变量定义举例:

1. myUrl="http://see.xidian.edu.cn/cpp/linux/"

2. myNum=100

使用变量

使用一个定义过的变量,只要在变量名前面加美元符号($)即可,如:

1. your_name="mozhiyan"

2. echo $your_name

3. echo ${your_name}

变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:

1. for skill in Ada Coffe Action Java 

2. do

3.     echo "I am good at ${skill}Script"

4. done

如果不给skill变量加花括号,写成echo "I am good at $skillScript",解释器就会把$skillScript当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。

推荐给所有变量加上花括号,这是个好的编程习惯。

重新定义变量

已定义的变量,可以被重新定义,如:

1. myUrl="http://see.xidian.edu.cn/cpp/linux/"

2. echo ${myUrl}

3. 

4. myUrl="http://see.xidian.edu.cn/cpp/shell/"

5. echo ${myUrl}

这样写是合法的,但注意,第二次赋值的时候不能写 $myUrl="http://see.xidian.edu.cn/cpp/shell/",使用变量的时候才加美元符($)。

只读变量

使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。

下面的例子尝试更改只读变量,结果报错:

1. #!/bin/bash

2. 

3. myUrl="http://see.xidian.edu.cn/cpp/shell/"

4. readonly myUrl

5. myUrl="http://see.xidian.edu.cn/cpp/danpianji/"

运行脚本,结果如下:

/bin/sh: NAME: This variable is read only.

删除变量

使用 unset 命令可以删除变量。语法:

1. unset variable_name

变量被删除后不能再次使用;unset 命令不能删除只读变量。

举个例子:

1. #!/bin/sh

2. 

3. myUrl="http://see.xidian.edu.cn/cpp/u/xitong/"

4. unset myUrl

5. echo $myUrl

上面的脚本没有任何输出。

变量类型

运行shell时,会同时存在三种变量:

1) 局部变量

局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。

2) 环境变量

所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。

3) shell变量

shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行

7

前面已经讲到,变量名只能包含数字、字母和下划线,因为某些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。

例如,$ 表示当前Shell进程的ID,即pid,看下面的代码:

1. $echo $$

运行结果

29949

 

特殊变量列表

变量

含义

$0

当前脚本的文件名

$n

传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。

$#

传递给脚本或函数的参数个数。

$*

传递给脚本或函数的所有参数。

$@

传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。

$?

上个命令的退出状态,或函数的返回值。

$$

当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。

命令行参数

运行脚本时传递给脚本的参数称为命令行参数。命令行参数用 $n 表示,例如,$1 表示第一个参数,$2 表示第二个参数,依次类推。

请看下面的脚本:

1. #!/bin/bash

2. 

3. echo "File Name: $0"

4. echo "First Parameter : $1"

5. echo "First Parameter : $2"

6. echo "Quoted Values: $@"

7. echo "Quoted Values: $*"

8. echo "Total Number of Parameters : $#"

运行结果:

$./test.sh Zara AliFile Name : ./test.shFirst Parameter : ZaraSecond Parameter : AliQuoted Values: Zara AliQuoted Values: Zara AliTotal Number of Parameters : 2

$* 和 $@ 的区别

$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。

但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数;"$@" 会将各个参数分开,以"$1" "$2" … "$n" 的形式输出所有参数。

下面的例子可以清楚的看到 $* 和 $@ 的区别:

1. #!/bin/bash

2. echo "\$*=" $*

3. echo "\"\$*\"=" "$*"

4. 

5. echo "\$@=" $@

6. echo "\"\$@\"=" "$@"

7. 

8. echo "print each param from \$*"

9. for var in $*

10. do

11.     echo "$var"

12. done

13. 

14. echo "print each param from \$@"

15. for var in $@

16. do

17.     echo "$var"

18. done

19. 

20. echo "print each param from \"\$*\""

21. for var in "$*"

22. do

23.     echo "$var"

24. done

25. 

26. echo "print each param from \"\$@\""

27. for var in "$@"

28. do

29.     echo "$var"

30. done

执行 ./test.sh "a" "b" "c" "d",看到下面的结果:

$*=  a b c d"$*"= a b c d$@=  a b c d"$@"= a b c dprint each param from $*abcdprint each param from $@abcdprint each param from "$*"a b c dprint each param from "$@"abcd

退出状态

$? 可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。

退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败返回 1。

不过,也有一些命令返回其他值,表示不同类型的错误。

下面例子中,命令成功执行:

$./test.sh Zara AliFile Name : ./test.shFirst Parameter : ZaraSecond Parameter : AliQuoted Values: Zara AliQuoted Values: Zara AliTotal Number of Parameters : 2$echo $?0$


$? 也可以表示函数的返回值,后续将会讲解。

8

如果表达式中包含特殊字符,Shell 将会进行替换。例如,在双引号中使用变量就是一种替换,转义字符也是一种替换。

举个例子:

1. #!/bin/bash

2. 

3. a=10

4. echo -e "Value of a is $a \n"

运行结果:

Value of a is 10

这里 -e 表示对转义字符进行替换。如果不使用 -e 选项,将会原样输出:

Value of a is 10\n


下面的转义字符都可以用在 echo 中:

转义字符

含义

\\

反斜杠

\a

警报,响铃

\b

退格(删除键)

\f

换页(FF),将当前位置移到下页开头

\n

换行

\r

回车

\t

水平制表符(tab键) 

\v

垂直制表符

可以使用 echo 命令的 -E 选项禁止转义,默认也是不转义的;使用 -n 选项可以禁止插入换行符。

命令替换

命令替换是指Shell可以先执行命令,将输出结果暂时保存,在适当的地方输出。

命令替换的语法:

1. `command`

注意是反引号,不是单引号,这个键位于 Esc 键下方。

下面的例子中,将命令执行结果保存在变量中:

1. #!/bin/bash

2. 

3. DATE=`date`

4. echo "Date is $DATE"

5. 

6. USERS=`who | wc -l`

7. echo "Logged in user are $USERS"

8. 

9. UP=`date ; uptime`

10. echo "Uptime is $UP"

运行结果:

Date is Thu Jul  2 03:59:57 MST 2009Logged in user are 1Uptime is Thu Jul  2 03:59:57 MST 200903:59:57 up 20 days, 14:03,  1 user,  load avg: 0.13, 0.07, 0.15

变量替换

变量替换可以根据变量的状态(是否为空、是否定义等)来改变它的值

可以使用的变量替换形式:

形式

说明

${var}

变量本来的值

${var:-word}

如果变量 var 为空或已被删除(unset),那么返回 word,但不改变 var 的值。

${var:=word}

如果变量 var 为空或已被删除(unset),那么返回 word,并将 var 的值设置为 word。

${var:?message}

如果变量 var 为空或已被删除(unset),那么将消息 message 送到标准错误输出,可以用来检测变量 var 是否可以被正常赋值。
若此替换出现在Shell脚本中,那么脚本将停止运行。

${var:+word}

如果变量 var 被定义,那么返回 word,但不改变 var 的值。


请看下面的例子:

#!/bin/bash echo ${var:-"Variable is not set"}echo "1 - Value of var is ${var}" echo ${var:="Variable is not set"}echo "2 - Value of var is ${var}" unset varecho ${var:+"This is default value"}echo "3 - Value of var is $var" var="Prefix"echo ${var:+"This is default value"}echo "4 - Value of var is $var" echo ${var:?"Print this message"}echo "5 - Value of var is ${var}"

运行结果:

复制纯文本新窗口

1. Variable is not set

2. 1 - Value of var is

3. Variable is not set

4. 2 - Value of var is Variable is not set

5. 

6. 3 - Value of var is

7. This is default value

8. 4 - Value of var is Prefix

9. Prefix

10. 5 - Value of var is Prefix

9

Bash 支持很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试运算符。

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。

expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

例如,两个数相加:

1. #!/bin/bash

2. 

3. val=`expr 2 + 2`

4. echo "Total value : $val"

运行脚本输出:

Total value : 4

两点注意:

· 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。

· 完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边。

算术运算符

先来看一个使用算术运算符的例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. val=`expr $a + $b`

6. echo "a + b : $val"

7. 

8. val=`expr $a - $b`

9. echo "a - b : $val"

10. 

11. val=`expr $a \* $b`

12. echo "a * b : $val"

13. 

14. val=`expr $b / $a`

15. echo "b / a : $val"

16. 

17. val=`expr $b % $a`

18. echo "b % a : $val"

19. 

20. if [ $a == $b ]

21. then

22.    echo "a is equal to b"

23. fi

24. 

25. if [ $a != $b ]

26. then

27.    echo "a is not equal to b"

28. fi

运行结果:

a + b : 30a - b : -10a * b : 200b / a : 2b % a : 0a is not equal to b

注意:

· 乘号(*)前边必须加反斜杠(\)才能实现乘法运算;

· if...then...fi 是条件语句,后续将会讲解。

 

算术运算符列表

运算符

说明

举例

+

加法

`expr $a + $b` 结果为 30。

-

减法

`expr $a - $b` 结果为 10。

*

乘法

`expr $a \* $b` 结果为  200。

/

除法

`expr $b / $a` 结果为 2。

%

取余

`expr $b % $a` 结果为 0。

=

赋值

a=$b 将把变量 b 的值赋给 a。

==

相等。用于比较两个数字,相同则返回 true。

[ $a == $b ] 返回 false。

!=

不相等。用于比较两个数字,不相同则返回 true。

[ $a != $b ] 返回 true。


注意:条件表达式要放在方括号之间,并且要有空格,例如 [$a==$b] 是错误的,必须写成 [ $a == $b ]。

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

先来看一个关系运算符的例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. if [ $a -eq $b ]

6. then

7.    echo "$a -eq $b : a is equal to b"

8. else

9.    echo "$a -eq $b: a is not equal to b"

10. fi

11. 

12. if [ $a -ne $b ]

13. then

14.    echo "$a -ne $b: a is not equal to b"

15. else

16.    echo "$a -ne $b : a is equal to b"

17. fi

18. 

19. if [ $a -gt $b ]

20. then

21.    echo "$a -gt $b: a is greater than b"

22. else

23.    echo "$a -gt $b: a is not greater than b"

24. fi

25. 

26. if [ $a -lt $b ]

27. then

28.    echo "$a -lt $b: a is less than b"

29. else

30.    echo "$a -lt $b: a is not less than b"

31. fi

32. 

33. if [ $a -ge $b ]

34. then

35.    echo "$a -ge $b: a is greater or  equal to b"

36. else

37.    echo "$a -ge $b: a is not greater or equal to b"

38. fi

39. 

40. if [ $a -le $b ]

41. then

42.    echo "$a -le $b: a is less or  equal to b"

43. else

44.    echo "$a -le $b: a is not less or equal to b"

45. fi

运行结果:

10 -eq 20: a is not equal to b10 -ne 20: a is not equal to b10 -gt 20: a is not greater than b10 -lt 20: a is less than b10 -ge 20: a is not greater or equal to b10 -le 20: a is less or  equal to b

 

关系运算符列表

运算符

说明

举例

-eq

检测两个数是否相等,相等返回 true。

[ $a -eq $b ] 返回 true。

-ne

检测两个数是否相等,不相等返回 true。

[ $a -ne $b ] 返回 true。

-gt

检测左边的数是否大于右边的,如果是,则返回 true。

[ $a -gt $b ] 返回 false。

-lt

检测左边的数是否小于右边的,如果是,则返回 true。

[ $a -lt $b ] 返回 true。

-ge

检测左边的数是否大等于右边的,如果是,则返回 true。

[ $a -ge $b ] 返回 false。

-le

检测左边的数是否小于等于右边的,如果是,则返回 true。

[ $a -le $b ] 返回 true。

布尔运算符

先来看一个布尔运算符的例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. 

6. if [ $a != $b ]

7. then

8.    echo "$a != $b : a is not equal to b"

9. else

10.    echo "$a != $b: a is equal to b"

11. fi

12. 

13. if [ $a -lt 100 -a $b -gt 15 ]

14. then

15.    echo "$a -lt 100 -a $b -gt 15 : returns true"

16. else

17.    echo "$a -lt 100 -a $b -gt 15 : returns false"

18. fi

19. 

20. if [ $a -lt 100 -o $b -gt 100 ]

21. then

22.    echo "$a -lt 100 -o $b -gt 100 : returns true"

23. else

24.    echo "$a -lt 100 -o $b -gt 100 : returns false"

25. fi

26. 

27. if [ $a -lt 5 -o $b -gt 100 ]

28. then

29.    echo "$a -lt 100 -o $b -gt 100 : returns true"

30. else

31.    echo "$a -lt 100 -o $b -gt 100 : returns false"

32. fi

运行结果:

10 != 20 : a is not equal to b10 -lt 100 -a 20 -gt 15 : returns true10 -lt 100 -o 20 -gt 100 : returns true10 -lt 5 -o 20 -gt 100 : returns false

 

布尔运算符列表

运算符

说明

举例

!

非运算,表达式为 true 则返回 false,否则返回 true。

[ ! false ] 返回 true。

-o

或运算,有一个表达式为 true 则返回 true。

[ $a -lt 20 -o $b -gt 100 ] 返回 true。

-a

与运算,两个表达式都为 true 才返回 true。

[ $a -lt 20 -a $b -gt 100 ] 返回 false。

字符串运算符

先来看一个例子:

1. #!/bin/sh

2. 

3. a="abc"

4. b="efg"

5. 

6. if [ $a = $b ]

7. then

8.    echo "$a = $b : a is equal to b"

9. else

10.    echo "$a = $b: a is not equal to b"

11. fi

12. 

13. if [ $a != $b ]

14. then

15.    echo "$a != $b : a is not equal to b"

16. else

17.    echo "$a != $b: a is equal to b"

18. fi

19. 

20. if [ -z $a ]

21. then

22.    echo "-z $a : string length is zero"

23. else

24.    echo "-z $a : string length is not zero"

25. fi

26. 

27. if [ -n $a ]

28. then

29.    echo "-n $a : string length is not zero"

30. else

31.    echo "-n $a : string length is zero"

32. fi

33. 

34. if [ $a ]

35. then

36.    echo "$a : string is not empty"

37. else

38.    echo "$a : string is empty"

39. fi

运行结果:

abc = efg: a is not equal to babc != efg : a is not equal to b-z abc : string length is not zero-n abc : string length is not zeroabc : string is not empty

 

字符串运算符列表

运算符

说明

举例

=

检测两个字符串是否相等,相等返回 true。

[ $a = $b ] 返回 false。

!=

检测两个字符串是否相等,不相等返回 true。

[ $a != $b ] 返回 true。

-z

检测字符串长度是否为0,为0返回 true。

[ -z $a ] 返回 false。

-n

检测字符串长度是否为0,不为0返回 true。

[ -z $a ] 返回 true。

str

检测字符串是否为空,不为空返回 true。

[ $a ] 返回 true。

文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

例如,变量 file 表示文件“/var/www/tutorialspoint/unix/test.sh”,它的大小为100字节,具有 rwx 权限。下面的代码,将检测该文件的各种属性:

1. #!/bin/sh

2. 

3. file="/var/www/tutorialspoint/unix/test.sh"

4. 

5. if [ -r $file ]

6. then

7.    echo "File has read access"

8. else

9.    echo "File does not have read access"

10. fi

11. 

12. if [ -w $file ]

13. then

14.    echo "File has write permission"

15. else

16.    echo "File does not have write permission"

17. fi

18. 

19. if [ -x $file ]

20. then

21.    echo "File has execute permission"

22. else

23.    echo "File does not have execute permission"

24. fi

25. 

26. if [ -f $file ]

27. then

28.    echo "File is an ordinary file"

29. else

30.    echo "This is sepcial file"

31. fi

32. 

33. if [ -d $file ]

34. then

35.    echo "File is a directory"

36. else

37.    echo "This is not a directory"

38. fi

39. 

40. if [ -s $file ]

41. then

42.    echo "File size is zero"

43. else

44.    echo "File size is not zero"

45. fi

46. 

47. if [ -e $file ]

48. then

49.    echo "File exists"

50. else

51.    echo "File does not exist"

52. fi

运行结果:

File has read accessFile has write permissionFile has execute permissionFile is an ordinary fileThis is not a directoryFile size is zeroFile exists

 

文件测试运算符列表

操作符

说明

举例

-b file

检测文件是否是块设备文件,如果是,则返回 true。

[ -b $file ] 返回 false。

-c file

检测文件是否是字符设备文件,如果是,则返回 true。

[ -b $file ] 返回 false。

-d file

检测文件是否是目录,如果是,则返回 true。

[ -d $file ] 返回 false。

-f file

检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。

[ -f $file ] 返回 true。

-g file

检测文件是否设置了 SGID 位,如果是,则返回 true。

[ -g $file ] 返回 false。

-k file

检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。

[ -k $file ] 返回 false。

-p file

检测文件是否是具名管道,如果是,则返回 true。

[ -p $file ] 返回 false。

-u file

检测文件是否设置了 SUID 位,如果是,则返回 true。

[ -u $file ] 返回 false。

-r file

检测文件是否可读,如果是,则返回 true。

[ -r $file ] 返回 true。

-w file

检测文件是否可写,如果是,则返回 true。

[ -w $file ] 返回 true。

-x file

检测文件是否可执行,如果是,则返回 true。

[ -x $file ] 返回 true。

-s file

检测文件是否为空(文件大小是否大于0),不为空返回 true。

[ -s $file ] 返回 true。

-e file

检测文件(包括目录)是否存在,如果是,则返回 true。

[ -e $file ] 返回 true。

 

10

以“#”开头的行就是注释,会被解释器忽略。

sh里没有多行注释,只能每一行加一个#号。只能像这样:

1. #--------------------------------------------

2. 这是一个自动打ipa的脚本,基于webfrogsipa-build书写:

3. # https://github.com/webfrogs/xcode_shell/blob/master/ipa-build

4. 

5. 功能:自动为etao ios app打包,产出物为14个渠道的ipa

6. 特色:全自动打包,不需要输入任何参数

7. #--------------------------------------------

8. 

9. ##### 用户配置区 开始 #####

10. #

11. #

12. 项目根目录,推荐将此脚本放在项目的根目录,这里就不用改了

13. 应用名,确保和XcodeProduct下的target_name.app名字一致

14. #

15. ##### 用户配置区 结束  #####

如果在开发过程中,遇到大段的代码需要临时注释起来,过一会儿又取消注释,怎么办呢?每一行加个#符号太费力了,可以把这一段要注释的代码用一对花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。

11

字符串是shell编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了),字符串可以用单引号,也可以用双引号,也可以不用引号。单双引号的区别跟PHP类似。

单引号

1. str='this is a string'

单引号字符串的限制:

· 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;

· 单引号字串中不能出现单引号(对单引号使用转义符后也不行)。

双引号

1. your_name='qinjx'

2. str="Hello, I know your are \"$your_name\"! \n"

双引号的优点:

· 双引号里可以有变量

· 双引号里可以出现转义字符

拼接字符串

1. your_name="qinjx"

2. greeting="hello, "$your_name" !"

3. greeting_1="hello, ${your_name} !"

4. 

5. echo $greeting $greeting_1

获取字符串长度

1. string="abcd"

2. echo ${#string} #输出 4

提取子字符串

1. string="alibaba is a great company"

2. echo ${string:1:4} #输出liba

查找子字符串

1. string="alibaba is a great company"

2. echo `expr index "$string" is`

12

Shell在编程方面比Windows批处理强大很多,无论是在循环、运算。

bash支持一维数组(不支持多维数组),并且没有限定数组的大小。类似与C语言,数组元素的下标由0开始编号。获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于0。

定义数组

在Shell中,用括号来表示数组,数组元素用“空格”符号分割开。定义数组的一般形式为:
    array_name=(value1 ... valuen)
例如:

1. array_name=(value0 value1 value2 value3)

或者

1. array_name=(

2. value0

3. value1

4. value2

5. value3

6. )


还可以单独定义数组的各个分量:

1. array_name[0]=value0

2. array_name[1]=value1

3. array_name[2]=value2

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

读取数组元素值的一般格式是:
    ${array_name[index]}
例如:

1. valuen=${array_name[2]}

举个例子:

1. #!/bin/sh

2. 

3. NAME[0]="Zara"

4. NAME[1]="Qadir"

5. NAME[2]="Mahnaz"

6. NAME[3]="Ayan"

7. NAME[4]="Daisy"

8. echo "First Index: ${NAME[0]}"

9. echo "Second Index: ${NAME[1]}"

运行脚本,输出:

$./test.shFirst Index: ZaraSecond Index: Qadir

使用@ 或 * 可以获取数组中的所有元素,例如:

1. ${array_name[*]}

2. ${array_name[@]}

举个例子:

1. #!/bin/sh

2. 

3. NAME[0]="Zara"

4. NAME[1]="Qadir"

5. NAME[2]="Mahnaz"

6. NAME[3]="Ayan"

7. NAME[4]="Daisy"

8. echo "First Method: ${NAME[*]}"

9. echo "Second Method: ${NAME[@]}"

运行脚本,输出:

$./test.shFirst Method: Zara Qadir Mahnaz Ayan DaisySecond Method: Zara Qadir Mahnaz Ayan Daisy

获取数组的长度

获取数组长度的方法与获取字符串长度的方法相同,例如:

复制纯文本新窗口

1. 取得数组元素的个数

2. length=${#array_name[@]}

3. 或者

4. length=${#array_name[*]}

5. 取得数组单个元素的长度

6. lengthn=${#array_name[n]}

12

echo是Shell的一个内部指令,用于在屏幕上打印出指定的字符串。命令格式:

1. echo arg

您可以使用echo实现更复杂的输出格式控制。

显示转义字符

1. echo "\"It is a test\""

结果将是:
"It is a test"

双引号也可以省略。

显示变量

1. name="OK"

2. echo "$name It is a test"

结果将是:
OK It is a test

同样双引号也可以省略。

如果变量与其它字符相连的话,需要使用大括号({ }):

1. mouth=8

2. echo "${mouth}-1-2009"

结果将是:
8-1-2009

显示换行

1. echo "OK!\n"

2. echo "It is a test"

输出:
OK!
It is a test

显示不换行

1. echo "OK!\c"

2. echo "It is a test"

输出:
OK!It si a test

显示结果重定向至文件

1. echo "It is a test" > myfile

原样输出字符串

若需要原样输出字符串(不进行转义),请使用单引号。例如:

1. echo '$name\"'

显示命令执行结果

1. echo `date`

结果将显示当前日期

从上面可看出,双引号可有可无,单引号主要用在原样输出中。

13

printf 命令用于格式化输出, 是echo命令的增强版。它是C语言printf()库函数的一个有限的变形,并且在语法上有些不同。

注意:printf 由 POSIX 标准所定义,移植性要比 echo 好。

如同 echo 命令,printf 命令也可以输出简单的字符串:

1. $printf "Hello, Shell\n"

2. Hello, Shell

3. $

printf 不像 echo 那样会自动换行,必须显式添加换行符(\n)。

printf 命令的语法:

printf  format-string  [arguments...]

format-string 为格式控制字符串,arguments 为参数列表。

printf()在C语言入门教程中已经讲到,功能和用法与 printf 命令类似,请查看:C语言格式输出函数printf()详解

这里仅说明与C语言printf()函数的不同:

· printf 命令不用加括号

· format-string 可以没有引号,但最好加上,单引号双引号均可。

· 参数多于格式控制符(%)时,format-string 可以重用,可以将所有参数都转换。

· arguments 使用空格分隔,不用逗号。


请看下面的例子:

1. # format-string为双引号

2. printf "%d %s\n" 1 "abc"

3. 1 abc

4. 单引号与双引号效果一样 

5. printf '%d %s\n' 1 "abc" 

6. 1 abc

7. 没有引号也可以输出

8. printf %s abcdef

9. abcdef

10. 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,format-string 被重用

11. printf %s abc def

12. abcdef

13. printf "%s\n" abc def

14. abc

15. def

16. printf "%s %s %s\n" a b c d e f g h i j

17. a b c

18. d e f

19. g h i

20. j

21. 如果没有 arguments,那么 %s NULL代替,%d 用 代替

22. printf "%s and %d \n" 

23. and 0

24. 如果以 %d 的格式来显示字符串,那么会有警告,提示无效的数字,此时默认置为 0

25. printf "The first program always prints'%s,%d\n'" Hello Shell

26. -bash: printf: Shell: invalid number

27. The first program always prints 'Hello,0'

28. $


注意,根据POSIX标准,浮点格式%e、%E、%f、%g与%G是“不需要被支持”。这是因为awk支持浮点预算,且有它自己的printf语句。这样Shell程序中需要将浮点数值进行格式化的打印时,可使用小型的awk程序实现。然而,内建于bash、ksh93和zsh中的printf命令都支持浮点格式。

14

if 语句通过关系运算符判断表达式的真假来决定执行哪个分支。Shell 有三种 if ... else 语句:

· if ... fi 语句;

· if ... else ... fi 语句;

· if ... elif ... else ... fi 语句。

1) if ... else 语句

if ... else 语句的语法:

if [ expression ]then   Statement(s) to be executed if expression is truefi

如果 expression 返回 true,then 后边的语句将会被执行;如果返回 false,不会执行任何语句。

最后必须以 fi 来结尾闭合 if,fi 就是 if 倒过来拼写,后面也会遇见。

注意:expression 和方括号([ ])之间必须有空格,否则会有语法错误。

举个例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. 

6. if [ $a == $b ]

7. then

8.    echo "a is equal to b"

9. fi

10. 

11. if [ $a != $b ]

12. then

13.    echo "a is not equal to b"

14. fi

运行结果:

a is not equal to b

2) if ... else ... fi 语句

if ... else ... fi 语句的语法:

if [ expression ]then   Statement(s) to be executed if expression is trueelse   Statement(s) to be executed if expression is not truefi

如果 expression 返回 true,那么 then 后边的语句将会被执行;否则,执行 else 后边的语句。

举个例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. 

6. if [ $a == $b ]

7. then

8.    echo "a is equal to b"

9. else

10.    echo "a is not equal to b"

11. fi

执行结果:

a is not equal to b

3) if ... elif ... fi 语句

if ... elif ... fi 语句可以对多个条件进行判断,语法为:

if [ expression 1 ]then   Statement(s) to be executed if expression 1 is trueelif [ expression 2 ]then   Statement(s) to be executed if expression 2 is trueelif [ expression 3 ]then   Statement(s) to be executed if expression 3 is trueelse   Statement(s) to be executed if no expression is truefi

哪一个 expression 的值为 true,就执行哪个 expression 后面的语句;如果都为 false,那么不执行任何语句。

举个例子:

1. #!/bin/sh

2. 

3. a=10

4. b=20

5. 

6. if [ $a == $b ]

7. then

8.    echo "a is equal to b"

9. elif [ $a -gt $b ]

10. then

11.    echo "a is greater than b"

12. elif [ $a -lt $b ]

13. then

14.    echo "a is less than b"

15. else

16.    echo "None of the condition met"

17. fi

运行结果:

a is less than b


if ... else 语句也可以写成一行,以命令的方式来运行,像这样:

1. if test $[2*3] -eq $[1+5]; then echo 'The two numbers are equal!'; fi;


if ... else 语句也经常与 test 命令结合使用,如下所示:

1. num1=$[2*3]

2. num2=$[1+5]

3. if test $[num1] -eq $[num2]

4. then

5.     echo 'The two numbers are equal!'

6. else

7.     echo 'The two numbers are not equal!'

8. fi

输出:

The two numbers are equal!

test 命令用于检查某个条件是否成立,与方括号([ ])类似。

15

case ... esac 与其他语言中的 switch ... case 语句类似,是一种多分枝选择结构。

case 语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。case语句格式如下:

case 值 in模式1)    command1    command2    command3    ;;模式2)    command1    command2    command3    ;;*)    command1    command2    command3    ;;esac

case工作方式如上所示。取值后面必须为关键字 in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;。;; 与其他语言中的 break 类似,意思是跳到整个 case 语句的最后。

取值将检测匹配的每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令。

下面的脚本提示输入1到4,与每一种模式进行匹配:

1. echo 'Input a number between 1 to 4'

2. echo 'Your number is:\c'

3. read aNum

4. case $aNum in

5.     1)  echo 'You select 1'

6.     ;;

7.     2)  echo 'You select 2'

8.     ;;

9.     3)  echo 'You select 3'

10.     ;;

11.     4)  echo 'You select 4'

12.     ;;

13.     *)  echo 'You do not select a number between 1 to 4'

14.     ;;

15. esac

输入不同的内容,会有不同的结果,例如:

Input a number between 1 to 4Your number is:3You select 3


再举一个例子:

1. #!/bin/bash

2. 

3. option="${1}"

4. case ${option} in

5.    -f) FILE="${2}"

6.       echo "File name is $FILE"

7.       ;;

8.    -d) DIR="${2}"

9.       echo "Dir name is $DIR"

10.       ;;

11.    *) 

12.       echo "`basename ${0}`:usage: [-f file] | [-d directory]"

13.       exit 1 # Command to come out of the program with status 1

14.       ;;

15. esac

运行结果:

$./test.shtest.sh: usage: [ -f filename ] | [ -d directory ]$ ./test.sh -f index.htm$ vi test.sh$ ./test.sh -f index.htmFile name is index.htm$ ./test.sh -d unixDir name is unix$

 

16

与其他编程语言类似,Shell支持for循环。

for循环一般格式为:

for 变量 in 列表do    command1    command2    ...    commandNdone

列表是一组值(数字、字符串等)组成的序列,每个值通过空格分隔。每循环一次,就将列表中的下一个值赋给变量。

in 列表是可选的,如果不用它,for 循环使用命令行的位置参数。

例如,顺序输出当前列表中的数字:

1. for loop in 1 2 3 4 5

2. do

3.     echo "The value is: $loop"

4. done

运行结果:

The value is: 1The value is: 2The value is: 3The value is: 4The value is: 5


顺序输出字符串中的字符:

1. for str in 'This is a string'

2. do

3.     echo $str

4. done

运行结果:

This is a string


显示主目录下以 .bash 开头的文件:

1. #!/bin/bash

2. 

3. for FILE in $HOME/.bash*

4. do

5.    echo $FILE

6. done

运行结果:

/root/.bash_history/root/.bash_logout/root/.bash_profile/root/.bashrc

 

17

while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。其格式为:

while commanddo   Statement(s) to be executed if command is truedone

命令执行完毕,控制返回循环顶部,从头开始直至测试条件为假。

以下是一个基本的while循环,测试条件是:如果COUNTER小于5,那么返回 true。COUNTER从0开始,每次循环处理时,COUNTER加1。运行上述脚本,返回数字1到5,然后终止。

1. COUNTER=0

2. while [ $COUNTER -lt 5 ]

3. do

4.     COUNTER='expr $COUNTER+1'

5.     echo $COUNTER

6. done

运行脚本,输出:

12345


while循环可用于读取键盘信息。下面的例子中,输入信息被设置为变量FILM,按结束循环。

1. echo 'type  to terminate'

2. echo -n 'enter your most liked film: '

3. while read FILM

4. do

5.     echo "Yeah! great film the $FILM"

6. done

运行脚本,输出类似下面:

type  to terminateenter your most liked film: Sound of MusicYeah! great film the Sound of Music

18

until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。一般while循环优于until循环,但在某些时候,也只是极少数情况下,until 循环更加有用。

until 循环格式为:

until commanddo   Statement(s) to be executed until command is truedone

command 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。

例如,使用 until 命令输出 0 ~ 9 的数字:

1. #!/bin/bash

2. 

3. a=0

4. 

5. until [ ! $a -lt 10 ]

6. do

7.    echo $a

8.    a=`expr $a + 1`

9. done

运行结果:

0123456789

19

在循环过程中,有时候需要在未达到循环结束条件时强制跳出循环,像大多数编程语言一样,Shell也使用 break 和 continue 来跳出循环。

break命令

break命令允许跳出所有循环(终止执行后面的所有循环)。

下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,就要使用break命令。

1. #!/bin/bash

2. while :

3. do

4.     echo -n "Input a number between 1 to 5: "

5.     read aNum

6.     case $aNum in

7.         1|2|3|4|5) echo "Your number is $aNum!"

8.         ;;

9.         *) echo "You do not select a number between 1 to 5, game is over!"

10.             break

11.         ;;

12.     esac

13. done

在嵌套循环中,break 命令后面还可以跟一个整数,表示跳出第几层循环。例如:

1. break n

表示跳出第 n 层循环。

下面是一个嵌套循环的例子,如果 var1 等于 2,并且 var2 等于 0,就跳出循环:

1. #!/bin/bash

2. 

3. for var1 in 1 2 3

4. do

5.    for var2 in 0 5

6.    do

7.       if [ $var1 -eq 2 -a $var2 -eq 0 ]

8.       then

9.          break 2

10.       else

11.          echo "$var1 $var2"

12.       fi

13.    done

14. done

如上,break 2 表示直接跳出外层循环。运行结果:

1 01 5

continue命令

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

对上面的例子进行修改:

1. #!/bin/bash

2. while :

3. do

4.     echo -n "Input a number between 1 to 5: "

5.     read aNum

6.     case $aNum in

7.         1|2|3|4|5) echo "Your number is $aNum!"

8.         ;;

9.         *) echo "You do not select a number between 1 to 5!"

10.             continue

11.             echo "Game is over!"

12.         ;;

13.     esac

14. done

运行代码发现,当输入大于5的数字时,该例中的循环不会结束,语句

1. echo "Game is over!"

永远不会被执行。

同样,continue 后面也可以跟一个数字,表示跳出第几层循环。

再看一个 continue 的例子:

1. #!/bin/bash

2. 

3. NUMS="1 2 3 4 5 6 7"

4. 

5. for NUM in $NUMS

6. do

7.    Q=`expr $NUM % 2`

8.    if [ $Q -eq 0 ]

9.    then

10.       echo "Number is an even number!!"

11.       continue

12.    fi

13.    echo "Found odd number"

14. done

运行结果:

Found odd numberNumber is an even number!!Found odd numberNumber is an even number!!Found odd numberNumber is an even number!!Found odd number

 

20

函数可以让我们将一个复杂功能划分成若干模块,让程序结构更加清晰,代码重复利用率更高。像其他编程语言一样,Shell 也支持函数。Shell 函数必须先定义后使用。

Shell 函数的定义格式如下:

function_name () {    list of commands    [ return value ]}

如果你愿意,也可以在函数名前加上关键字 function:

function function_name () {    list of commands    [ return value ]}

函数返回值,可以显式增加return语句;如果不加,会将最后一条命令运行结果作为返回值。

Shell 函数返回值只能是整数,一般用来表示函数执行成功与否,0表示成功,其他值表示失败。如果 return 其他数据,比如一个字符串,往往会得到错误提示:“numeric argument required”。

如果一定要让函数返回字符串,那么可以先定义一个变量,用来接收函数的计算结果,脚本在需要的时候访问这个变量来获得函数返回值。

先来看一个例子:

1. #!/bin/bash

2. 

3. # Define your function here

4. Hello () {

5.    echo "Url is http://see.xidian.edu.cn/cpp/shell/"

6. }

7. 

8. # Invoke your function

9. Hello

运行结果:

$./test.shHello World$

调用函数只需要给出函数名,不需要加括号。

再来看一个带有return语句的函数:

1. #!/bin/bash

2. funWithReturn(){

3.     echo "The function is to get the sum of two numbers..."

4.     echo -n "Input first number: "

5.     read aNum

6.     echo -n "Input another number: "

7.     read anotherNum

8.     echo "The two numbers are $aNum and $anotherNum !"

9.     return $(($aNum+$anotherNum))

10. }

11. funWithReturn

12. # Capture value returnd by last command

13. ret=$?

14. echo "The sum of two numbers is $ret !"

运行结果:

The function is to get the sum of two numbers...Input first number: 25Input another number: 50The two numbers are 25 and 50 !The sum of two numbers is 75 !

函数返回值在调用该函数后通过 $? 来获得。

再来看一个函数嵌套的例子:

1. #!/bin/bash

2. 

3. # Calling one function from another

4. number_one () {

5.    echo "Url_1 is http://see.xidian.edu.cn/cpp/shell/"

6.    number_two

7. }

8. 

9. number_two () {

10.    echo "Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/"

11. }

12. 

13. number_one

运行结果:

Url_1 is http://see.xidian.edu.cn/cpp/shell/Url_2 is http://see.xidian.edu.cn/cpp/u/xitong/

像删除变量一样,删除函数也可以使用 unset 命令,不过要加上 .f 选项,如下所示:

1. $unset .f function_name

如果你希望直接从终端调用函数,可以将函数定义在主目录下的 .profile 文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用。

 

21

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数...

带参数的函数示例:

1. #!/bin/bash

2. funWithParam(){

3.     echo "The value of the first parameter is $1 !"

4.     echo "The value of the second parameter is $2 !"

5.     echo "The value of the tenth parameter is $10 !"

6.     echo "The value of the tenth parameter is ${10} !"

7.     echo "The value of the eleventh parameter is ${11} !"

8.     echo "The amount of the parameters is $# !"  参数个数

9.     echo "The string of the parameters is $* !"  传递给函数的所有参数

10. }

11. funWithParam 1 2 3 4 5 6 7 8 9 34 73

运行脚本:

The value of the first parameter is 1 !The value of the second parameter is 2 !The value of the tenth parameter is 10 !The value of the tenth parameter is 34 !The value of the eleventh parameter is 73 !The amount of the parameters is 12 !The string of the parameters is 1 2 3 4 5 6 7 8 9 34 73 !"

注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

另外,还有几个特殊变量用来处理参数,前面已经提到:

特殊变量

说明

$#

传递给函数的参数个数。

$*

显示所有传递给函数的参数。

$@

与$*相同,但是略有区别,请查看Shell特殊变量

$?

函数的返回值。

 

22

Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。

输出重定向

命令的输出不仅可以是显示器,还可以很容易的转移向到文件,这被称为输出重定向。

命令输出重定向的语法为:

1. command > file

这样,输出到显示器的内容就可以被重定向到文件。

例如,下面的命令在显示器上不会看到任何输出:

1. $ who > users

打开 users 文件,可以看到下面的内容:

$ cat usersoko         tty01   Sep 12 07:30ai          tty15   Sep 12 13:32ruth        tty21   Sep 12 10:10pat         tty24   Sep 12 13:07steve       tty25   Sep 12 13:03$

输出重定向会覆盖文件内容,请看下面的例子:

$ echo line 1 > users$ cat usersline 1$

如果不希望文件内容被覆盖,可以使用 >> 追加到文件末尾,例如:

$ echo line 2 >> users$ cat usersline 1line 2$

输入重定向

和输出重定向一样,Unix 命令也可以从文件获取输入,语法为:

1. command < file

这样,本来需要从键盘获取输入的命令会转移到文件读取内容。

注意:输出重定向是大于号(>),输入重定向是小于号(<)。

例如,计算 users 文件中的行数,可以使用下面的命令:

$ wc -l users2 users$

也可以将输入重定向到 users 文件:

$ wc -l < users2$

注意:上面两个例子的结果不同:第一个例子,会输出文件名;第二个不会,因为它仅仅知道从标准输入读取内容。

重定向深入讲解

一般情况下,每个 Unix/Linux 命令运行时都会打开三个文件:

· 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。

· 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。

· 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息。


默认情况下,command > file 将 stdout 重定向到 file,command < file 将stdin 重定向到 file。

如果希望 stderr 重定向到 file,可以这样写:

1. $command 2 > file

如果希望 stderr 追加到 file 文件末尾,可以这样写:

1. $command 2 >> file

2 表示标准错误文件(stderr)。

如果希望将 stdout 和 stderr 合并后重定向到 file,可以这样写:

1. $command > file 2>&1

1. $command >> file 2>&1

如果希望对 stdin 和 stdout 都重定向,可以这样写:

1. $command < file1 >file2

command 命令将 stdin 重定向到 file1,将 stdout 重定向到 file2。 

全部可用的重定向命令列表

命令

说明

command > file

将输出重定向到 file。

command < file

将输入重定向到 file。

command >> file

将输出以追加的方式重定向到 file。

n > file

将文件描述符为 n 的文件重定向到 file。

n >> file

将文件描述符为 n 的文件以追加的方式重定向到 file。

n >& m

将输出文件 m 和 n 合并。

n <& m

将输入文件 m 和 n 合并。

<< tag

将开始标记 tag 和结束标记 tag 之间的内容作为输入。

Here Document

Here Document 目前没有统一的翻译,这里暂译为”嵌入文档“。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本的形式如下:

1. command << delimiter

2.     document

3. delimiter

它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。

注意:

· 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。

· 开始的delimiter前后的空格会被忽略掉。


下面的例子,通过 wc -l 命令计算 document 的行数:

$wc -l << EOF    This is a simple lookup program    for good (and bad) restaurants    in Cape Town.EOF3$

也可以 将 Here Document 用在脚本中,例如:

1. #!/bin/bash

2. 

3. cat << EOF

4. This is a simple lookup program

5. for good (and bad) restaurants

6. in Cape Town.

7. EOF

运行结果:

This is a simple lookup programfor good (and bad) restaurantsin Cape Town.


下面的脚本通过 vi 编辑器将 document 保存到 test.txt 文件:

1. #!/bin/sh

2. 

3. filename=test.txt

4. vi $filename <<EndOfCommands

5. i

6. This file was created automatically from

7. a shell script

8. ^[

9. ZZ

10. EndOfCommands

运行脚本:

$ sh test.shVim: Warning: Input is not from a terminal$

打开 test.txt,可以看到下面的内容:

$ cat test.txtThis file was created automatically froma shell script$

/dev/null 文件

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null:

1. command > /dev/null

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到”禁止输出“的效果。

如果希望屏蔽 stdout 和 stderr,可以这样写:

1. command > /dev/null 2>&1

23

像其他语言一样,Shell 也可以包含外部脚本,将外部脚本的内容合并到当前脚本。

Shell 中包含脚本可以使用:

1. . filename

1. source filename

两种方式的效果相同,简单起见,一般使用点号(.),但是注意点号(.)和文件名中间有一空格。

例如,创建两个脚本,一个是被调用脚本 subscript.sh,内容如下:

1. url="http://see.xidian.edu.cn/cpp/view/2738.html"

一个是主文件 main.sh,内容如下:

1. #!/bin/bash

2. . ./subscript.sh

3. echo $url

执行脚本:

$chomd +x main.sh./main.shhttp://see.xidian.edu.cn/cpp/view/2738.html$

注意:被包含脚本不需要有执行权限。

 

 

 

 

 

 

Shell教程二

 

一个很不错的bash脚本编写教程,至少没接触过BASH的也能看懂

建立一个脚本 

  Linux中有好多中不同的shell,但是通常我们使用bash (bourne again shell) 进行shell编程,因为bash是免费的并且很容易使用。所以在本文中笔者所提供的脚本都是使用bash(但是在大多数情况下,这些脚本同样可以在 bash的大姐,bourne shell中运行)。 
  如同其他语言一样,通过我们使用任意一种文字编辑器,比如nedit、kedit、emacs、vi 
  等来编写我们的shell程序。 
  程序必须以下面的行开始(必须方在文件的第一行): 
#!/bin/sh 
  符号#!用来告诉系统它后面的参数是用来执行该文件的程序。在这个例子中我们使用/bin/sh来执行程序。 
  当编辑好脚本时,如果要执行该脚本,还必须使其可执行。 
  要使脚本可执行: 
chmod +x filename 
  然后,您可以通过输入: ./filename 来执行您的脚本。 
注释 
  在进行shell编程时,以#开头的句子表示注释,直到这一行的结束。我们真诚地建议您在程序中使用注释。如果您使用了注释,那么即使相当长的时间内没有使用该脚本,您也能在很短的时间内明白该脚本的作用及工作原理。 
变量 
  在其他编程语言中您必须使用变量。在shell编程中,所有的变量都由字符串组成,并且您不需要对变量进行声明。要赋值给一个变量,您可以这样写: 
变量名=值 
  取出变量值可以加一个美元符号($)在变量前面: 

?

1

2

3

4

5

6

#!/bin/sh  

#对变量赋值:  

a="hello world" 

现在打印变量a的内容:  

echo "A is:" 

echo $a

  在您的编辑器中输入以上内容,然后将其保存为一个文件first。之后执行chmod +x first 
  使其可执行,最后输入./first执行该脚本。 
  这个脚本将会输出: 
A is: 
hello world 
  有时候变量名很容易与其他文字混淆,比如: 
num=2 
echo "this is the $numnd" 
  这并不会打印出"this is the 2nd",而仅仅打印"this is the ",因为shell会去搜索变量numnd的值,但是这个变量时没有值的。可以使用花括号来告诉shell我们要打印的是num变量: 
num=2 
echo "this is the ${num}nd" 
  这将打印: this is the 2nd 
  有许多变量是系统自动设定的,这将在后面使用这些变量时进行讨论。 
  如果您需要处理数学表达式,那么您需要使用诸如expr等程序(见下面)。 
  除了一般的仅在程序内有效的shell变量以外,还有环境变量。由export关键字处理过的变量叫做环境变量。我们不对环境变量进行讨论,因为通常情况下仅仅在登录脚本中使用环境变量。 
Shell命令和流程控制 
  在shell脚本中可以使用三类命令: 
1)Unix 命令: 
  虽然在shell脚本中可以使用任意的unix命令,但是还是由一些相对更常用的命令。这些命令通常是用来进行文件和文字操作的。 
常用命令语法及功能 
  echo "some text": 将文字内容打印在屏幕上 
  ls: 文件列表 
  wc –l filewc -w filewc -c file: 计算文件行数计算文件中的单词数计算文件中的字符数 
  cp sourcefile destfile: 文件拷贝 
  mv oldname newname : 重命名文件或移动文件 
  rm file: 删除文件 
  grep 'pattern' file: 在文件内搜索字符串比如:grep 'searchstring' file.txt 
  cut -b colnum file: 指定欲显示的文件内容范围,并将它们输出到标准输出设备比如:输出每行第5个到第9个字符cut -b5-9 file.txt千万不要和cat命令混淆,这是两个完全不同的命令 
  cat file.txt: 输出文件内容到标准输出设备(屏幕)上 
  file somefile: 得到文件类型 
  read var: 提示用户输入,并将输入赋值给变量 
  sort file.txt: 对file.txt文件中的行进行排序 
  uniq: 删除文本文件中出现的行列比如: sort file.txt | uniq 
  expr: 进行数学运算Example: add 2 and 3expr 2 "+" 3 
  find: 搜索文件比如:根据文件名搜索find . -name filename -print 
  tee: 将数据输出到标准输出设备(屏幕) 和文件比如:somecommand | tee outfile 
  basename file: 返回不包含路径的文件名比如: basename /bin/tux将返回 tux 
  dirname file: 返回文件所在路径比如:dirname /bin/tux将返回 /bin 
  head file: 打印文本文件开头几行 
  tail file : 打印文本文件末尾几行 
  sed: Sed是一个基本的查找替换程序。可以从标准输入(比如命令管道)读入文本,并将结果输出到标准输出(屏幕)。该命令采用正则表达式(见参考)进行搜索。不要和shell中的通配符相混淆。比如:将linuxfocus 替换为 LinuxFocus :cat text.file | sed 's/linuxfocus/LinuxFocus/' > newtext.file 
  awk: awk 用来从文本文件中提取字段。缺省地,字段分割符是空格,可以使用-F指定其他分割符。cat file.txt | awk -F, '{print $1 "," $3 }'这里我们使用,作为字段分割符,同时打印第一个和第三个字段。如果该文件内容如下: Adam Bor, 34, IndiaKerry Miller, 22, USA命令输出结果为:Adam Bor, IndiaKerry Miller, USA 
2) 概念: 管道, 重定向和 backtick 
  这些不是系统命令,但是他们真的很重要。 
  管道 (|) 将一个命令的输出作为另外一个命令的输入。 
grep "hello" file.txt | wc -l 
  在file.txt中搜索包含有”hello”的行并计算其行数。 
  在这里grep命令的输出作为wc命令的输入。当然您可以使用多个命令。 
  重定向:将命令的结果输出到文件,而不是标准输出(屏幕)。 
  > 写入文件并覆盖旧文件 
  >> 加到文件的尾部,保留旧文件内容。 
反短斜线 
 使用反短斜线可以将一个命令的输出作为另外一个命令的一个命令行参数。 
  命令: 
find . -mtime -1 -type f -print 
  用来查找过去24小时(-mtime –2则表示过去48小时)内修改过的文件。如果您想将所有查找到的文件打一个包,则可以使用以下脚本: 
#!/bin/sh 
# The ticks are backticks (`) not normal quotes ('): 
tar -zcvf lastmod.tar.gz `find . -mtime -1 -type f -print` 
  3) 流程控制 
  "if" 表达式 如果条件为真则执行then后面的部分: 
if ....; then 
  .... 
elif ....; then 
  .... 
else 
  .... 
fi 
  大多数情况下,可以使用测试命令来对条件进行测试。比如可以比较字符串、判断文件是否存在及是否可读等等… 
  通常用" [ ] "来表示条件测试。注意这里的空格很重要。要确保方括号的空格。 
[ -f "somefile" ] :判断是否是一个文件 
[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限 
[ -n "$var" ] :判断$var变量是否有值 
[ "$a" = "$b" ] :判断$a和$b是否相等 
  执行man test可以查看所有测试表达式可以比较和判断的类型。 
  直接执行以下脚本: 
#!/bin/sh 
if [ "$SHELL" = "/bin/bash" ]; then 
 echo "your login shell is the bash (bourne again shell)" 
else 
 echo "your login shell is not bash but $SHELL" 
fi 
  变量$SHELL包含了登录shell的名称,我们和/bin/bash进行了比较。 
快捷操作符 
  熟悉C语言的朋友可能会很喜欢下面的表达式: 
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors" 
  这里 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句。您也可以认为是逻辑运算中的与操作。上例中表示如果/etc/shadow文件存在则打印” This computer uses shadow passwors”。同样或操作(||)在shell编程中也是可用的。这里有个例子: 
#!/bin/sh 
mailfolder=/var/spool/mail/james 
[ -r "$mailfolder" ]' '{ echo "Can not read $mailfolder" ; exit 1; } 
echo "$mailfolder has mail from:" 
grep "^From " $mailfolder 
  该脚本首先判断mailfolder是否可读。如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。这里有个问题,那就是我们必须有两个命令: 
  -打印错误信息 
  -退出程序 
  我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用。一般函数将在下文提及。 
  不用与和或操作符,我们也可以用if表达式作任何事情,但是使用与或操作符会更便利很多。 
  case表达式可以用来匹配一个给定的字符串,而不是数字。 
case ... in 
...) do something here ;; 
esac 
  让我们看一个例子。 file命令可以辨别出一个给定文件的文件类型,比如: 
file lf.gz 
  这将返回: 
lf.gz: gzip compressed data, deflated, original filename, 
last modified: Mon Aug 27 23:09:18 2001, os: Unix 
 我们利用这一点写了一个叫做smartzip的脚本,该脚本可以自动解压bzip2, gzip 和zip 类型的压缩文件: 
#!/bin/sh 
ftype=`file "$1"` 
case "$ftype" in 
"$1: Zip archive"*) 
  unzip "$1" ;; 
"$1: gzip compressed"*) 
  gunzip "$1" ;; 
"$1: bzip2 compressed"*) 
  bunzip2 "$1" ;; 
*) error "File $1 can not be uncompressed with smartzip";; 
esac 
  您可能注意到我们在这里使用了一个特殊的变量$1。该变量包含了传递给该程序的第一个参数值。也就是说,当我们运行: 
smartzip articles.zip 
$1 就是字符串 articles.zip 
  select 表达式是一种bash的扩展应用,尤其擅长于交互式使用。用户可以从一组不同的值中进行选择。 
select var in ... ; do 
 break 
done 
.... now $var can be used .... 
下面是一个例子: 
#!/bin/sh 
echo "What is your favourite OS?" 
select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do 
    break 
done 
echo "You have selected $var" 
  下面是该脚本运行的结果: 
What is your favourite OS? 
1) Linux 
2) Gnu Hurd 
3) Free BSD 
4) Other 
#? 1 
You have selected Linux 
  您也可以在shell中使用如下的loop表达式: 
while ...; do 
.... 
done 
  while-loop 将运行直到表达式测试为真。will run while the expression that we test for is true. 关键字"break" 用来跳出循环。而关键字”continue”用来不执行余下的部分而直接跳到下一个循环。 
  for-loop表达式查看一个字符串列表 (字符串用空格分隔) 然后将其赋给一个变量: 
for var in ....; do 
 .... 
done 
  在下面的例子中,将分别打印ABC到屏幕上: 

复制代码 代码如下:


#!/bin/sh 
for var in A B C ; do 
 echo "var is $var" 
done 


  下面是一个更为有用的脚本showrpm,其功能是打印一些RPM包的统计信息: 

?

1

2

3

4

5

6

7

8

9

10

11

12

#!/bin/sh  

# list a content summary of a number of RPM packages  

# USAGE: showrpm rpmfile1 rpmfile2 ...  

# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm  

for rpmpackage in $*; do 

 if [ -r "$rpmpackage" ];then 

  echo "=============== $rpmpackage ==============" 

  rpm -qi -p $rpmpackage  

 else 

  echo "ERROR: cannot read file $rpmpackage" 

 fi 

done

  这里出现了第二个特殊的变量$*,该变量包含了所有输入的命令行参数值。如果您运行showrpm openssh.rpm w3m.rpm webgrep.rpm 
  此时 $* 包含了 3 个字符串,即openssh.rpm, w3m.rpm and webgrep.rpm. 
引号 
  在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓扩展的意思是程序会把通配符(比如*)替换成合适的文件名,它变量替换成变量值。为了防 止程序作这种替换,您可以使用引号:让我们来看一个例子,假设在当前目录下有一些文件,两个jpg文件, mail.jpg 和tux.jpg。 

#!/bin/sh 
echo *.jpg 
  这将打印出"mail.jpg tux.jpg"的结果。 
  引号 (单引号和双引号) 将防止这种通配符扩展: 
#!/bin/sh 
echo "*.jpg" 
echo '*.jpg' 
  这将打印"*.jpg" 两次。 
  单引号更严格一些。它可以防止任何变量扩展。双引号可以防止通配符扩展但允许变量扩展。 
#!/bin/sh 
echo $SHELL 
echo "$SHELL" 
echo '$SHELL' 
  运行结果为: 
/bin/bash 
/bin/bash 
$SHELL 
  最后,还有一种防止这种扩展的方法,那就是使用转义字符——反斜杆: 
echo *.jpg 
echo $SHELL 
  这将输出: 
*.jpg 
$SHELL 
Here documents 
  当要将几行文字传递给一个命令时,here documents(译者注:目前还没有见到过对该词适合的翻译)一种不错的方法。对每个脚本写一段帮助性的文字是很有用的,此时如果我们四有那个 here documents就不必用echo函数一行行输出。 一个 "Here document" 以 << 开头,后面接上一个字符串,这个字符串还必须出现在here document的末尾。下面是一个例子,在该例子中,我们对多个文件进行重命名,并且使用here documents打印帮助: 

复制代码 代码如下:


#!/bin/sh 
# we have less than 3 arguments. Print the help text: 
if [ $# -lt 3 ] ; then 
cat < 
ren -- renames a number of files using sed regular expressions 
USAGE: ren 'regexp' 'replacement' files... 
EXAMPLE: rename all *.HTM files in *.html: 
 ren 'HTM$' 'html' *.HTM 
HELP 
 exit 0 
fi 
OLD="$1" 
NEW="$2" 
# The shift command removes one argument from the list of 
# command line arguments. 
shift 
shift 
# $* contains now all the files: 
for file in $*; do 
  if [ -f "$file" ] ; then 
   newfile=`echo "$file" | sed "s/${OLD}/${NEW}/g"` 
   if [ -f "$newfile" ]; then 
    echo "ERROR: $newfile exists already" 
   else 
    echo "renaming $file to $newfile ..." 
    mv "$file" "$newfile" 
   fi 
  fi 
done 


  这是一个复杂一些的例子。让我们详细讨论一下。第一个if表达式判断输入命令行参数是否小于3个 (特殊变量$# 表示包含参数的个数) 。如果输入参数小于3个,则将帮助文字传递给cat命令,然后由cat命令将其打印在屏幕上。打印帮助文字后程序退出。如果输入参数等于或大于3个,我们就将第一个参数赋值给变量OLD,第二个参数赋值给变量NEW。下一步,我们使用shift命令将第一个和第二个参数从参数列表中删除,这样原来的第三个参数就成为参数列表$*的第一个参数。然后我们开始循环,命令行参数列表被一个接一个地被赋值给变量$file。接着我们判断该文件是否存在,如果存在则通过sed命令搜索和替换来产生新的文件名。然后将反短斜线内命令结果赋值给newfile。这样我们就达到了我们的目的:得到了旧文件名和新文件名。然后使用mv命令进行重命名。 
函数 
  如果您写了一些稍微复杂一些的程序,您就会发现在程序中可能在几个地方使用了相同的代码,并且您也会发现,如果我们使用了函数,会方便很多。一个函数是这个样子的: 

复制代码 代码如下:


functionname() 

# inside the body $1 is the first argument given to the function 
# $2 the second ... 
body 


  您需要在每个程序的开始对函数进行声明。 

  下面是一个叫做xtitlebar的脚本,使用这个脚本您可以改变终端窗口的名称。这里使用了一个叫做help的函数。正如您可以看到的那样,这个定义的函数被使用了两次。 

复制代码 代码如下:


#!/bin/sh 
# vim: set sw=4 ts=4 et: 
help() 

  cat < 
xtitlebar -- change the name of an xterm, gnome-terminal or kde konsole 
USAGE: xtitlebar [-h] "string_for_titelbar" 
OPTIONS: -h help text 
EXAMPLE: xtitlebar "cvs" 
HELP 
  exit 0 

# in case of error or if -h is given we call the function help: 
[ -z "$1" ] && help 
[ "$1" = "-h" ] && help 
# send the escape sequence to change the xterm titelbar: 
echo -e "33]0;$107" 



  在脚本中提供帮助是一种很好的编程习惯,这样方便其他用户(和您)使用和理解脚本。 
命令行参数 
  我们已经见过$* 和 $1, $2 ... $9 等特殊变量,这些特殊变量包含了用户从命令行输入的参数。迄今为止,我们仅仅了解了一些简单的命令行语法(比如一些强制性的参数和查看帮助的-h选项)。但是在编写更复杂的程序时,您可能会发现您需要更多的自定义的选项。通常的惯例是在所有可选的参数之前加一个减号,后面再加上参数值 (比如文件名)。 
  有好多方法可以实现对输入参数的分析,但是下面的使用case表达式的例子无遗是一个不错的方法。 

复制代码 代码如下:


#!/bin/sh 
help() 

 cat < 
This is a generic command line parser demo. 
USAGE EXAMPLE: cmdparser -l hello -f -- -somefile1 somefile2 
HELP 
 exit 0 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; # function help is called 
  -f) opt_f=1;shift 1;; # variable opt_f is set 
  -l) opt_l=$2;shift 2;; # -l takes an argument -> shift by 2 
  --) shift;break;; # end of options 
  -*) echo "error: no such option $1. -h for help";exit 1;; 
  *) break;; 
esac 
done 

echo "opt_f is $opt_f" 
echo "opt_l is $opt_l" 
echo "first arg is $1" 
echo "2nd arg is $2" 


  您可以这样运行该脚本: 
cmdparser -l hello -f -- -somefile1 somefile2 
  返回的结果是: 
opt_f is 1 
opt_l is hello 
first arg is -somefile1 
2nd arg is somefile2 
  这个脚本是如何工作的呢?脚本首先在所有输入命令行参数中进行循环,将输入参数与case表达式进行比较,如果匹配则设置一个变量并且移除该参数。根据unix系统的惯例,首先输入的应该是包含减号的参数。 
实例 
  一般编程步骤 
  现在我们来讨论编写一个脚本的一般步骤。任何优秀的脚本都应该具有帮助和输入参数。并且写一个伪脚本(framework.sh),该脚本包含了大多数脚本都需要的框架结构,是一个非常不错的主意。这时候,在写一个新的脚本时我们只需要执行一下copy命令: 
cp framework.sh myscript 
 然后再插入自己的函数。 
  让我们再看两个例子: 
  二进制到十进制的转换 
  脚本 b2d 将二进制数 (比如 1101) 转换为相应的十进制数。这也是一个用expr命令进行数学运算的例子: 

复制代码 代码如下:


#!/bin/sh 
# vim: set sw=4 ts=4 et: 
help() 

 cat < 
b2h -- convert binary to decimal 
USAGE: b2h [-h] binarynum 
OPTIONS: -h help text 
EXAMPLE: b2h 111010 
will return 58 
HELP 
 exit 0 

error() 

  # print an error and exit 
  echo "$1" 
  exit 1 

lastchar() 

  # return the last character of a string in $rval 
  if [ -z "$1" ]; then 
    # empty string 
    rval="" 
    return 
  fi 
  # wc puts some space behind the output this is why we need sed: 
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` 
  # now cut out the last char 
  rval=`echo -n "$1" | cut -b $numofchar` 


chop() 

  # remove the last character in string and return it in $rval 
  if [ -z "$1" ]; then 
    # empty string 
    rval="" 
    return 
  fi 
  # wc puts some space behind the output this is why we need sed: 
  numofchar=`echo -n "$1" | wc -c | sed 's/ //g' ` 
  if [ "$numofchar" = "1" ]; then 
    # only one char in string 
    rval="" 
    return 
  fi 
  numofcharminus1=`expr $numofchar "-" 1` 
  # now cut all but the last char: 
  rval=`echo -n "$1" | cut -b 0-${numofcharminus1}` 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; # function help is called 
  --) shift;break;; # end of options 
  -*) error "error: no such option $1. -h for help";; 
  *) break;; 
esac 
done 
# The main program 
sum=0 
weight=1 
# one arg must be given: 
[ -z "$1" ] && help 
binnum="$1" 
binnumorig="$1" 

while [ -n "$binnum" ]; do 
  lastchar "$binnum" 
  if [ "$rval" = "1" ]; then 
    sum=`expr "$weight" "+" "$sum"` 
  fi 
  # remove the last position in $binnum 
  chop "$binnum" 
  binnum="$rval" 
  weight=`expr "$weight" "*" 2` 
done 
echo "binary $binnumorig is decimal $sum" 


  该脚本使用的算法是利用十进制和二进制数权值 (1,2,4,8,16,..),比如二进制"10"可以这样转换成十进制: 
0 * 1 + 1 * 2 = 2 
  为了得到单个的二进制数我们是用了lastchar 函数。该函数使用wc –c计算字符个数,然后使用cut命令取出末尾一个字符。Chop函数的功能则是移除最后一个字符。 
  文件循环程序 
  或许您是想将所有发出的邮件保存到一个文件中的人们中的一员,但是在过了几个月以后,这个文件可能会变得很大以至于使对该文件的访问速度变慢。下面的脚本rotatefile 可以解决这个问题。这个脚本可以重命名邮件保存文件(假设为outmail)为outmail.1,而对于outmail.1就变成了outmail.2 等等等等... 

复制代码 代码如下:


#!/bin/sh 
# vim: set sw=4 ts=4 et: 
ver="0.1" 
help() 

  cat < 
rotatefile -- rotate the file name 

USAGE: rotatefile [-h] filename 

OPTIONS: -h help text 
EXAMPLE: rotatefile out 
This will e.g rename out.2 to out.3, out.1 to out.2, out to out.1 
and create an empty out-file 
The max number is 10 
version $ver 
HELP 
  exit 0 


error() 

  echo "$1" 
  exit 1 

while [ -n "$1" ]; do 
case $1 in 
  -h) help;shift 1;; 
  --) break;; 
  -*) echo "error: no such option $1. -h for help";exit 1;; 
  *) break;; 
esac 
done 
# input check: 
if [ -z "$1" ] ; then 
error "ERROR: you must specify a file, use -h for help" 
fi 
filen="$1" 
# rename any .1 , .2 etc file: 
for n in 9 8 7 6 5 4 3 2 1; do 
  if [ -f "$filen.$n" ]; then 
    p=`expr $n + 1` 
    echo "mv $filen.$n $filen.$p" 
    mv $filen.$n $filen.$p 
  fi 
done 
# rename the original file: 
if [ -f "$filen" ]; then 
  echo "mv $filen $filen.1" 
  mv $filen $filen.1 
fi 
echo touch $filen 
touch $filen 


  这个脚本是如何工作的呢?在检测用户提供了一个文件名以后,我们进行一个9到1的循环。文件9被命名为10,文件8重命名为9等等。循环完成之后,我们将原始文件命名为文件1同时建立一个与原始文件同名的空文件。 
调试 
  最简单的调试命令当然是使用echo命令。您可以使用echo在任何怀疑出错的地方打印任何变量值。这也是绝大多数的shell程序员要花费80%的时间来调试程序的原因。Shell程序的好处在于不需要重新编译,插入一个echo命令也不需要多少时间。 
  shell也有一个真实的调试模式。如果在脚本"strangescript" 中有错误,您可以这样来进行调试: 
sh -x strangescript 
  这将执行该脚本并显示所有变量的值。 
  shell还有一个不需要执行脚本只是检查语法的模式。可以这样使用: 
sh -n your_script 
  这将返回所有语法错误。 
  我们希望您现在可以开始写您自己的shell脚本,希望您玩得开心。

 

 

 

 

你可能感兴趣的:(shell,linux)