Chinaunix首页 | 论坛 | 博客
  • 博客访问: 712047
  • 博文数量: 139
  • 博客积分: 7607
  • 博客等级: 少将
  • 技术积分: 1964
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-11 23:43
个人简介

...

文章分类

全部博文(139)

文章存档

2012年(53)

2011年(4)

2010年(29)

2009年(10)

2008年(33)

2007年(10)

分类: LINUX

2008-03-14 16:52:55

第一部分    热身
++++++++++++++++
shell是一个命令解释器.是介于操作系统kernel与用户之间的一个绝缘层.准确地说,它也是一
一种强力的计算机语言.一个shell程序,被称为一个脚本,是一种很容易使用的工具,它可以通过
将系统调用,公共程序,工具,和编译过的二进制程序粘合在一起来建立应用.事实上,所有的UNIX
命令和工具再加上公共程序,对于shell脚本来说,都是可调用的.如果这些你还觉得不够,那么
shell内建命令,比如test与循环结构,也会给脚本添加强力的支持和增加灵活性.Shell脚本对于
管理系统任务和其它的重复工作的例程来说,表现的非常好,根本不需要那些华而不实的成熟
紧凑的程序语言.

第1章    为什么使用shell编程
===========================
没有程序语言是完美的.甚至没有一个唯一最好的语言,只有对于特定目的,比较适合和不适合
的程序语言.
                                                               Herbert Mayer

对于任何想适当精通一些系统管理知识的人来说,掌握shell脚本知识都是最基本的,即使这些
人可能并不打算真正的编写一些脚本.想一下Linux机器的启动过程,在这个过程中,必将运行
/etc/rc.d目录下的脚本来存储系统配置和建立服务.详细的理解这些启动脚本对于分析系统的
行为是非常重要的,并且有时候可能必须修改它.

学习如何编写shell脚本并不是一件很困难的事,因为脚本可以分为很小的块,并且相对于shell
特性的操作和选项[1]部分,只需要学习很小的一部分就可以了.语法是简单并且直观的,编写脚
本很像是在命令行上把一些相关命令和工具连接起来,并且只有很少的一部分规则需要学习.
绝大部分脚本第一次就可以正常的工作,而且即使调试一个长一些的脚本也是很直观的.

一个shell脚本是一个类似于小吃店的(quick and dirty)方法,在你使用原型设计一个复杂的
应用的时候.在工程开发的第一阶段,即使从功能中取得很有限的一个子集放到shell脚本中来
完成往往都是非常有用的.使用这种方法,程序的结果可以被测试和尝试运行,并且在处理使用
诸如C/C++,Java或者Perl语言编写的最终代码前,主要的缺陷和陷阱往往就被发现了.

Shell脚本遵循典型的UNIX哲学,就是把大的复杂的工程分成小规模的子任务,并且把这些部件
和工具组合起来.许多人认为这种办法更好一些,至少这种办法比使用那种高\大\全的语言更
美,更愉悦,更适合解决问题.比如Perl就是这种能干任何事能适合任何人的语言,但是代价就是
你需要强迫自己使用这种语言来思考解决问题的办法.

什么时候不使用Shell脚本
   
   资源密集型的任务,尤其在需要考虑效率时(比如,排序,hash等等)

   需要处理大任务的数学操作,尤其是浮点运算,精确运算,或者复杂的算术运算
   (这种情况一般使用C++或FORTRAN来处理)

   有跨平台移植需求(一般使用C或Java)

   复杂的应用,在必须使用结构化编程的时候(需要变量的类型检查,函数原型,等等)

   对于影响系统全局性的关键任务应用。

   对于安全有很高要求的任务,比如你需要一个健壮的系统来防止入侵,破解,恶意破坏等等.

   项目由连串的依赖的各个部分组成。

   需要大规模的文件操作

   需要多维数组的支持

   需要数据结构的支持,比如链表或数等数据结构

   需要产生或操作图形化界面GUI

   需要直接操作系统硬件

   需要I/O或socket接口

   需要使用库或者遗留下来的老代码的接口

   私人的,闭源的应用(shell脚本把代码就放在文本文件中,全世界都能看到)

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

我们将开始使用Bash,Bash是"Bourne-Again shell"首字母的缩写,也是Stephen Bourne的经典
的Bourne shell的一个双关语,(译者:说实话,我一直搞不清这个双关语是什么意思,为什么叫
"Bourn-Again shell",这其中应该有个什么典故吧,哪位好心,告诉我一下^^).Bash已经成为了
所有UNIX中shell脚本的事实上的标准了.同时这本书也覆盖了绝大部分的其他一些shell的原
则,比如Korn Shell,Bash从ksh中继承了一部分特性,[2]C Shell和它的变种.(注意:C Shell
编程是不被推荐的,因为一些特定的内在问题,Tom Christiansen在1993年10月指出了这个问题
请在中查看具体内容.)

接下来是脚本的一些说明.在展示shell不同的特征之前,它可以减轻一些阅读书中例子
的负担.本书中的例子脚本,都在尽可能的范围内进行了测试,并且其中的一些将使用在真
实的生活中.读者可以运行这些例子脚本(使用scriptname.sh或者scriptname.bash的形式),
[3]并给这些脚本执行权限(chmod u+rx scriptname),然后执行它们,看看发生了什么.如果存
档的脚本不可用,那么就从本书的HTML,pdf或者text的发行版本中把它们拷贝粘贴出来.考虑到
这些脚本中的内容在我们还没解释它之前就被列在这里,可能会影响读者的理解,这就需要读者
暂时忽略这些内容.

除非特别注明,本书作者编写了本书中的绝大部分例子脚本.

注意事项:
[1]        这些在builtins章节被引用,这些是shell的内部特征.
[2]        ksh88的许多特性,甚至是一些ksh93的特性都被合并到Bash中了.
[3]        根据惯例,用户编写的Bourne shell脚本应该在脚本的名字后边加上.sh扩展名.
       一些系统脚本,比如那些在/etc/rc.d中的脚本,则不遵循这种命名习惯.



第2章    带着一个Sha-Bang出发(Sha-Bang指的是#!)
==============================================
在一个最简单的例子中,一个shell脚本其实就是将一堆系统命令列在一个文件中.它的最基本的
用处就是,在你每次输入这些特定顺序的命令时可以少敲一些字.

Example 2-1 清除:清除/var/log下的log文件
################################Start Script#######################################
1 # Cleanup
2 # 当然要使用root身份来运行这个脚本
3
4 cd /var/log
5 cat /dev/null > messages
6 cat /dev/null > wtmp
7 echo "Logs cleaned up."
################################End Script#########################################
这根本就没什么稀奇的, 只不过是命令的堆积, 来让从console或者xterm中一个一个的输入命
令更方便一些.好处就是把所有命令都放在一个脚本中,不用每次都敲它们.这样的话,对于特定
的应用来说,这个脚本就很容易被修改或定制.

Example 2-2 清除:一个改良的清除脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 一个Bash脚本的正确的开头部分.
3
4 # Cleanup, 版本 2
5
6 # 当然要使用root身份来运行.
7 # 在此处插入代码,来打印错误消息,并且在不是root身份的时候退出.
8
9 LOG_DIR=/var/log
10 # 如果使用变量,当然比把代码写死的好.
11 cd $LOG_DIR
12
13 cat /dev/null > messages
14 cat /dev/null > wtmp
15
16
17 echo "Logs cleaned up."
18
19 exit # 这个命令是一种正确并且合适的退出脚本的方法.
################################End Script#########################################

现在,让我们看一下一个真正意义的脚本.而且我们可以走得更远...
Example 2-3. cleanup:一个增强的和广义的删除logfile的脚本
################################Start Script#######################################
1 #!/bin/bash
2 # 清除, 版本 3
3
4 #  Warning:
5 #  -------
6 #  这个脚本有好多特征,这些特征是在后边章节进行解释的,大概是进行到本书的一半的
7 #  时候,
8 #  你就会觉得它没有什么神秘的了.
9 #
10
11
12
13 LOG_DIR=/var/log
14 ROOT_UID=0     # $UID为0的时候,用户才具有根用户的权限
15 LINES=50       # 默认的保存行数
16 E_XCD=66       # 不能修改目录?
17 E_NOTROOT=67   # 非根用户将以error退出
18
19
20 # 当然要使用根用户来运行
21 if [ "$UID" -ne "$ROOT_UID" ]
22 then
23   echo "Must be root to run this script."
24   exit $E_NOTROOT
25 fi  
26
27 if [ -n "$1" ]
28 # 测试是否有命令行参数(非空).
29 then
30   lines=$1
31 else  
32   lines=$LINES # 默认,如果不在命令行中指定
33 fi  
34
35
36 #  Stephane Chazelas 建议使用下边
37 #+ 的更好方法来检测命令行参数.
38 #+ 但对于这章来说还是有点超前.
39 #
40 #    E_WRONGARGS=65  # 非数值参数(错误的参数格式)
41 #
42 #    case "$1" in
43 #    ""      ) lines=50;;
44 #    *[!0-9]*) echo "Usage: `basename $0` file-to-cleanup"; exit $E_WRONGARGS;;
45 #    *       ) lines=$1;;
46 #    esac
47 #
48 #* 直到"Loops"的章节才会对上边的内容进行详细的描述.
49
50
51 cd $LOG_DIR
52
53 if [ `pwd` != "$LOG_DIR" ]  # 或者    if[ "$PWD" != "$LOG_DIR" ]
54                             # 不在 /var/log中?
55 then
56   echo "Can't change to $LOG_DIR."
57   exit $E_XCD
58 fi  # 在处理log file之前,再确认一遍当前目录是否正确.
59
60 # 更有效率的做法是
61 #
62 # cd /var/log || {
63 #   echo "Cannot change to necessary directory." >&2
64 #   exit $E_XCD;
65 # }
66
67
68
69
70 tail -$lines messages > mesg.temp # 保存log file消息的最后部分.
71 mv mesg.temp messages             # 变为新的log目录.
72
73
74 # cat /dev/null > messages
75 #* 不再需要了,使用上边的方法更安全.
76
77 cat /dev/null > wtmp  #  ': > wtmp' 和 '> wtmp'具有相同的作用
78 echo "Logs cleaned up."
79
80 exit 0
81 #  退出之前返回0,返回0表示成功.
82 #
################################End Script#########################################

因为你可能希望将系统log全部消灭,这个版本留下了log消息最后的部分.你将不断地找到新
的方法来完善这个脚本,并提高效率.

要注意,在每个脚本的开头都使用"#!",这意味着告诉你的系统这个文件的执行需要指定一个解
释器.#!实际上是一个2字节[1]的魔法数字,这是指定一个文件类型的特殊标记, 换句话说, 在
这种情况下,指的就是一个可执行的脚本(键入man magic来获得关于这个迷人话题的更多详细
信息).在#!之后接着是一个路径名.这个路径名指定了一个解释脚本中命令的程序,这个程序可
以是shell,程序语言或者是任意一个通用程序.这个指定的程序从头开始解释并且执行脚本中
的命令(从#!行下边的一行开始),忽略注释.[2]
如:
1 #!/bin/sh
2 #!/bin/bash
3 #!/usr/bin/perl
4 #!/usr/bin/tcl
5 #!/bin/sed -f
6 #!/usr/awk -f

上边每一个脚本头的行都指定了一个不同的命令解释器,如果是/bin/sh,那么就是默认shell
(在Linux系统中默认是Bash).[3]使用#!/bin/sh,在大多数商业发行的UNIX上,默认是Bourne
shell,这将让你的脚本可以正常的运行在非Linux机器上,虽然这将会牺牲Bash一些独特的特征.
脚本将与POSIX[4] 的sh标准相一致.

注意: #! 后边给出的路径名必须是正确的,否则将会出现一个错误消息,通常是
"Command not found",这将是你运行这个脚本时所得到的唯一结果.

当然"#!"也可以被忽略,不过这样你的脚本文件就只能是一些命令的集合,不能够使用shell内建
的指令了,如果不能使用变量的话,当然这也就失去了脚本编程的意义了.

   注意:这个例子鼓励你使用模块化的方式来编写脚本,平时也要注意收集一些零碎的代码,
       这些零碎的代码可能用在你将来编写的脚本中.这样你就可以通过这些代码片段来构
       造一个较大的工程用例. 以下边脚本作为序,来测试脚本被调用的参数是否正确.
################################Start Script#######################################
1 E_WRONG_ARGS=65
2 script_parameters="-a -h -m -z"
3 #                  -a = all, -h = help, 等等.
4
5 if [ $# -ne $Number_of_expected_args ]
6 then
7   echo "Usage: `basename $0` $script_parameters"
8   # `basename $0`是这个脚本的文件名
9   exit $E_WRONG_ARGS
10 fi
################################End Script#########################################
大多数情况下,你需要编写一个脚本来执行一个特定的任务,在本章中第一个脚本就是一个这样
的例子, 然后你会修改它来完成一个不同的,但比较相似的任务.用变量来代替写死的常量,就是
一个好方法,将重复的代码放到一个函数中,也是一种好习惯.


2.1 调用一个脚本
----------------
编写完脚本之后,你可以使用sh scriptname,[5]或者bash scriptname来调用它.
(不推荐使用sh 更方便的方法是让脚本本身就具有可执行权限,通过chmod命令可以修改.

比如:
   chmod 555 scriptname (允许任何人都具有 可读和执行权限) [6]  
或:
   chmod +rx scriptname (允许任何人都具有 可读和执行权限)
   chmod u+rx scriptname (只给脚本的所有者 可读和执行权限)

既然脚本已经具有了可执行权限,现在你可以使用./scriptname.[7]来测试它了.如果这个脚本
以一个"#!"行开头,那么脚本将会调用合适的命令解释器来运行.

最后一步,在脚本被测试和debug之后,你可能想把它移动到/usr/local/bin(当然是以root身份)
,来让你的脚本对所有用户都有用.这样用户就可以直接敲脚本名字来运行了.

注意事项:
[1]        那些具有UNIX味道的脚本(基于4.2BSD)需要一个4字节的魔法数字,在#!后边需要一个
       空格#! /bin/sh.
[2]        脚本中的#!行的最重要的任务就是命令解释器(sh或者bash).因为这行是以#开始的,
       当命令解释器执行这个脚本的时候,会把它作为一个注释行.当然,在这之前,这行语句
       已经完成了它的任务,就是调用命令解释器.

       如果在脚本的里边还有一个#!行,那么bash将把它认为是一个一般的注释行.
        1 #!/bin/bash
        2
        3 echo "Part 1 of script."
        4 a=1
        5
        6 #!/bin/bash
        7 # 这将不会开始一个新脚本.
        8
        9 echo "Part 2 of script."
       10 echo $a  # Value of $a stays at 1.
[3]        这里可以玩一些小技巧.
        1 #!/bin/rm
        2 # 自删除脚本.
        3
        4 # 当你运行这个脚本时,基本上什么都不会发生...除非这个文件消失不见.
        5
        6 WHATEVER=65
        7
        8 echo "This line will never print (betcha!)."
        9
       10 exit $WHATEVER  # 没关系,脚本是不会在这退出的.
       当然,你还可以试试在一个README文件的开头加上#!/bin/more,并让它具有执行权限.
       结果将是文档自动列出自己的内容.(一个使用cat命令的here document可能是一个
       更好的选则,--见Example 17-3).
[4]        可移植的操作系统接口,标准化类UNIX操作系统的一种尝试.POSIX规范可以在
       中查阅.
[5]        小心:使用sh scriptname来调用脚本的时候将会关闭一些Bash特定的扩展,脚本可能
       因此而调用失败.
[6]        脚本需要读和执行权限,因为shell需要读这个脚本.
[7]        为什么不直接使用scriptname来调用脚本?如果你当前的目录下($PWD)正好有你想要
       执行的脚本,为什么它运行不了呢?失败的原因是,出于安全考虑,当前目录并没有被
       加在用户的$PATH变量中.因此,在当前目录下调用脚本必须使用./scriptname这种
       形式.


2.2 初步的练习
--------------
1. 系统管理员经常会为了自动化一些常用的任务而编写脚本.举出几个这种有用的脚本的实例.
2. 编写一个脚本,显示时间和日期,列出所有的登录用户,显示系统的更新时间.然后这个脚本
  将会把这些内容保存到一个log file中.


第二部分    基本
++++++++++++++++

第3章    特殊字符
================

#        注释,行首以#开头为注释(#!是个例外).

       1 # This line is a comment.

       注释也可以存在于本行命令的后边.

       1 echo "A comment will follow." # 注释在这里
       2 #                            ^ 注意#前边的空白

       注释也可以在本行空白的后边.

       1     # A tab precedes this comment.

       注意:命令是不能跟在同一行上注释的后边的,没有办法,在同一行上,注释的后边想
       要再使用命令,只能另起一行.
       当然,在echo命令中被转义的#是不能作为注释的.
       同样的,#也可以出现在特定的参数替换结构中或者是数字常量表达式中.

       1 echo "The # here does not begin a comment."
       2 echo 'The # here does not begin a comment.'
       3 echo The \# here does not begin a comment.
       4 echo The # 这里开始一个注释
       5
       6 echo ${PATH#*:}       # 参数替换,不是一个注释
       7 echo $(( 2#101011 ))  # 数制转换,不是一个注释
       8
       9 # Thanks, S.C.

       标准的引用和转义字符("'\)可以用来转义#

;        命令分隔符,可以用来在一行中来写多个命令.

       1 echo hello; echo there
       2
       3
       4 if [ -x "$filename" ]; then    # 注意:"if"和"then"需要分隔
       5                                # 为啥?
       6   echo "File $filename exists."; cp $filename $filename.bak
       7 else
       8   echo "File $filename not found."; touch $filename
       9 fi; echo "File test complete."

       有时候需要转义

;;        终止"case"选项.

       1 case "$variable" in
       2 abc)  echo "\$variable = abc" ;;
       3 xyz)  echo "\$variable = xyz" ;;
       4 esac

.        .命令等价于source命令(见Example 11-20).这是一个bash的内建命令.

.        .作为文件名的一部分.如果作为文件名的前缀的话,那么这个文件将成为隐藏文件.
       将不被ls命令列出.

       bash$ touch .hidden-file
       bash$ ls -l          
       total 10
       -rw-r--r--    1 bozo      4034 Jul 18 22:04 data1.addressbook
       -rw-r--r--    1 bozo      4602 May 25 13:58 data1.addressbook.bak
       -rw-r--r--    1 bozo       877 Dec 17  2000 employment.addressbook
       
       
       bash$ ls -al          
       total 14
       drwxrwxr-x    2 bozo  bozo      1024 Aug 29 20:54 ./
       drwx------   52 bozo  bozo      3072 Aug 29 20:51 ../
       -rw-r--r--    1 bozo  bozo      4034 Jul 18 22:04 data1.addressbook
       -rw-r--r--    1 bozo  bozo      4602 May 25 13:58 data1.addressbook.bak
       -rw-r--r--    1 bozo  bozo       877 Dec 17  2000 employment.addressbook
       -rw-rw-r--    1 bozo  bozo         0 Aug 29 20:54 .hidden-file

       .命令如果作为目录名的一部分的话,那么.表达的是当前目录.".."表示上一级目录.

       bash$ pwd
       /home/bozo/projects

       bash$ cd .
       bash$ pwd
       /home/bozo/projects

       bash$ cd ..
       bash$ pwd
       /home/bozo/

       .命令经常作为一个文件移动命令的目的地.

       bash$ cp /home/bozo/current_work/junk/* .

.        .字符匹配,这是作为正则表达是的一部分,用来匹配任何的单个字符.

"        部分引用."STRING"阻止了一部分特殊字符,具体见第5章.

'        全引用. 'STRING' 阻止了全部特殊字符,具体见第5章.

,        逗号链接了一系列的算术操作,虽然里边所有的内容都被运行了,但只有最后一项被
       返回.

       如:
       1 let "t2 = ((a = 9, 15 / 3))"  # Set "a = 9" and "t2 = 15 / 3"

\        转义字符,如\X等价于"X"或'X',具体见第5章.

/        文件名路径分隔符.或用来做除法操作.

`        后置引用,命令替换,具体见第14章

:        空命令,等价于"NOP"(no op,一个什么也不干的命令).也可以被认为与shell的内建命令(true)作用相同.":"命令是一
       个bash的内建命令,它的返回值为0,就是shell返回的true.

       如:
       1 :
       2 echo $?   # 0

       死循环,如:

        1 while :
        2 do
        3    operation-1
        4    operation-2
        5    ...
        6    operation-n
        7 done
        8
        9 # 与下边相同:
       10 #    while true
       11 #    do
       12 #      ...
       13 #    done

       在if/then中的占位符,如:
       1 if condition
       2 then :   # 什么都不做,引出分支.
       3 else
       4    take-some-action
       5 fi

       在一个2元命令中提供一个占位符,具体见Example 8-2,和"默认参数".如:
       1 : ${username=`whoami`}
       2 # ${username=`whoami`}   如果没有":"的话,将给出一个错误,除非"username"是
       3 #                        个命令
       在here document中提供一个占位符,见Example 17-10.

       使用"参数替换"来评估字符串变量(见Example 9-14).如:
       1 : ${HOSTNAME?} ${USER?} ${MAIL?}
       2 #     如果一个或多个必要的环境变量没被设置的话,
       3 #+ 就打印错误信息.

       "变量扩展/子串替换"
       在和 > (重定向操作符)结合使用时,把一个文件截断到0长度,没有修改它的权限.
       如果文件在之前并不存在,那么就创建它.如:
       1 : > data.xxx        #文件"data.xxx"现在被清空了.
       2
       3 #与 cat /dev/null >data.xxx 的作用相同
       4 #然而,这不会产生一个新的进程,因为":"是一个内建命令.
       具体参见Example 12-14.

       在和>>重定向操作符结合使用时,将不会对想要附加的文件产生任何影响.
       如果文件不存在,将创建.
       注意: 这只适用于正规文件,而不是管道,符号连接,和某些特殊文件.

       也可能用来作为注释行,虽然我们不推荐这么做.使用#来注释的话,将关闭剩余行的
       错误检查,所以可以在注释行中写任何东西.然而,使用:的话将不会这样.如:
       1 : This is a comment thar generates an error,(if [ $x -eq 3] ).

       ":"还用来在/etc/passwd和$PATH变量中用来做分隔符.
       bash$    echo $PATH
       /usr/local/bin:/bin:/usr/X11R6/bin:/sbin:/usr/sbin:/usr/games
!        取反操作符,将反转"退出状态"结果,(见Example 6-2).也会反转test操作符的意义.比
       如修改=为!=.!操作是Bash的一个关键字.

       在一个不同的上下文中,!也会出现在"间接变量引用"见Example 9-22.

       在另一种上下文中,!还能反转bash的"history mechanism"(见附录J 历史命令)
       需要注意的是,在一个脚本中,"history mechanism"是被禁用的.

*        万能匹配字符,用于文件名匹配(这个东西有个专有名词叫file globbing),或者是正则
       表达式中.注意:在正则表达式匹配中的作用和在文件名匹配中的作用是不同的.
       bash$ echo *
       abs-book.sgml add-drive.sh agram.sh alias.sh
*        数学乘法.
       **是幂运算.
?        测试操作.在一个确定的表达式中,用?来测试结果.
       (())结构可以用来做数学计算或者是写c代码,那?就是c语言的3元操作符的
       一个.
       在"参数替换"中,?测试一个变量是否被set了.
?        在file globbing中和在正则表达式中一样匹配任意的单个字符.

$        变量替换
       1 var1=5
       2 var2=23skidoo
       3
       4 echo $var1     # 5
       5 echo $var2     # 23skidoo
$        在正则表达式中作为行结束符.
${}        参数替换,见9.3节.
$*,$@    位置参数
$?        退出状态变量.$?保存一个命令/一个函数或者脚本本身的退出状态.
$$        进程ID变量.这个$$变量保存运行脚本进程ID
()        命令组.如:
       1 (a=hello;echo $a)
       注意:在()中的命令列表,将作为一个子shell来运行.
       在()中的变量,由于是在子shell中,所以对于脚本剩下的部分是不可用的.
       如:
       1 a=123
       2 ( a=321; )          
       3
       4 echo "a = $a"   # a = 123
       5 # 在圆括号中a变量,更像是一个局部变量.

       用在数组初始化,如:
       1 Array=(element1,element2,element3)

{xxx,yyy,zzz...}
       大括号扩展,如:
       1 cat {file1,file2,file3} > combined_file
       2 # 把file1,file2,file3连接在一起,并且重定向到combined_file中.
       3
       4
       5 cp file22.{txt,backup}
       6 # 拷贝"file22.txt" 到"file22.backup"中

       一个命令可能会对大括号中的以逗号分割的文件列表起作用[1]. file globbing将对
       大括号中的文件名作扩展.
       注意: 在大括号中,不允许有空白,除非这个空白是有意义的.
       echo {file1,file2}\ :{\ A," B",' C'}
       file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C
{}        代码块.又被称为内部组.事实上,这个结构创建了一个匿名的函数.但是与函数不同的
       是,在其中声明的变量,对于脚本其他部分的代码来说还是可见的.如:
       bash$
       {
           local a;
           a= 123;
       }
       bash中的local申请的变量只能够用在函数中.

       1 a=123
       2 { a=321; }
       3 echo "a = $a"   # a = 321   (说明在代码块中对变量a所作的修改,影响了外边的变量a)
       4
       5 # Thanks, S.C.

       下边的代码展示了在{}结构中代码的I/O重定向.

Example 3-1. 代码块和I/O重定向
################################Start Script#######################################
1 #!/bin/bash
2 # 从 /etc/fstab中读行
3
4 File=/etc/fstab
5
6 {
7 read line1
8 read line2
9 } < $File
10
11 echo "First line in $File is:"
12 echo "$line1"
13 echo
14 echo "Second line in $File is:"
15 echo "$line2"
16
17 exit 0
18
19 # 现在,你怎么分析每行的分割域
20 # 暗示: 使用 awk.
################################End Script#########################################

Example 3-2. 将一个代码块的结果保存到文件
################################Start Script#######################################
1 #!/bin/bash
2 # rpm-check.sh
3
4 # 这个脚本的目的是为了描述,列表,和确定是否可以安装一个rpm包.
5 # 在一个文件中保存输出.
6 #
7 # 这个脚本使用一个代码块来展示
8
9 SUCCESS=0
10 E_NOARGS=65
11
12 if [ -z "$1" ]
13 then
14   echo "Usage: `basename $0` rpm-file"
15   exit $E_NOARGS
16 fi  
17
18 {
19   echo
20   echo "Archive Description:"
21   rpm -qpi $1       # 查询说明
22   echo
23   echo "Archive Listing:"
24   rpm -qpl $1       # 查询列表
25   echo
26   rpm -i --test $1  # 查询rpm包是否可以被安装
27   if [ "$?" -eq $SUCCESS ]
28   then
29     echo "$1 can be installed."
30   else
31     echo "$1 cannot be installed."
32   fi  
33   echo
34 } > "$1.test"       # 把代码块中的所有输出都重定向到文件中
35
36 echo "Results of rpm test in file $1.test"
37
38 # 查看rpm的man页来查看rpm的选项
39
40 exit 0
################################End Script#########################################
       注意: 与()中的命令不同的是,{}中的代码块将不能正常地开启一个新shell.[2]

{} \;    路径名.一般都在find命令中使用.这不是一个shell内建命令.
       注意: ";"用来结束find命令序列的-exec选项.

[]        test.
       test的表达式将在[]中.
       值得注意的是[是shell内建test命令的一部分,并不是/usr/bin/test中的扩展命令
       的一个连接.

[[]]    test.
       test表达式放在[[]]中.(shell关键字)
       具体查看[[]]结构的讨论.

[]        数组元素
       Array[1]=slot_1
       echo ${Array[1]}

[]        字符范围
       在正则表达式中使用,作为字符匹配的一个范围

(())    数学计算的扩展
       在(())结构中可以使用一些数字计算.
       具体参阅((...))结构.

>&>>&>><
       重定向.
       scriptname >filename 重定向脚本的输出到文件中.覆盖文件原有内容.
       command &>filename 重定向stdout和stderr到文件中
       command >&2    重定向command的stdout到stderr
       scriptname >>filename 重定向脚本的输出到文件中.添加到文件尾端,如果没有文件,
       则创建这个文件.

       进程替换,具体见"进程替换部分",跟命令替换极其类似.
       (command)>
       <(command)

       <和> 可用来做字符串比较
       <和> 可用在数学计算比较

<<        重定向,用在"here document"

<<<        重定向,用在"here string"

<,>        ASCII比较
        1 veg1=carrots
        2 veg2=tomatoes
        3
        4 if [[ "$veg1" < "$veg2" ]]
        5 then
        6   echo "Although $veg1 precede $veg2 in the dictionary,"
        7   echo "this implies nothing about my culinary preferences."
        8 else
        9   echo "What kind of dictionary are you using, anyhow?"
       10 fi

\<,\>    正则表达式中的单词边界.如:
       bash$grep '\' textfile
   
|        管道.分析前边命令的输出,并将输出作为后边命令的输入.这是一种产生命令链的
       好方法.
       1 echo ls -l | sh
       2 #  传递"echo ls -l"的输出到shell中,
       3 #+ 与一个简单的"ls -l"结果相同.
       4
       5
       6 cat *.lst | sort | uniq
       7 # 合并和排序所有的".lst"文件,然后删除所有重复的行.
       
       管道是进程间通讯的一个典型办法,将一个进程的stdout放到另一个进程的stdin中.
       标准的方法是将一个一般命令的输出,比如cat或echo,传递到一个过滤命令中(在这个
       过滤命令中将处理输入),得到结果,如:
       cat $filename1 | $filename2 | grep $search_word

       当然输出的命令也可以传递到脚本中.如:
################################Start Script#######################################
1 #!/bin/bash
2 # uppercase.sh : 修改输出,全部转换为大写
3
4 tr 'a-z' 'A-Z'
5 #  字符范围必须被""引用起来
6 #+ 来阻止产生单字符的文件名.
7
8 exit 0
################################End Script#########################################
       
       现在让我们输送ls -l的输出到一个脚本中.
       bash$ ls -l | ./uppercase.sh
       -RW-RW-R--    1 BOZO  BOZO       109 APR  7 19:49 1.TXT
       -RW-RW-R--    1 BOZO  BOZO       109 APR 14 16:48 2.TXT
       -RW-R--R--    1 BOZO  BOZO       725 APR 20 20:56 DATA-FILE

       注意:管道中的一个进程的stdout必须被下一个进程作为stdin读入.否则,数据流会阻
       塞,并且管道将产生非预期的行为.
       如:
       1 cat file1 file2 | ls -l | sort
       2 #从"cat file1 file2"中的输出并没出现

       作为子进程的运行的管道,不能够改变脚本的变量.
       1 variable="initial_value"
       2 echo "new_value" | read variable
       3 echo "variable = $variable"            #variable = initial_value
       如果管道中的某个命令产生了一个异常,并中途失败,那么这个管道将过早的终止.
       这种行为被叫做a broken pipe,并且这种状态下将发送一个SIGPIPE信号.

>|        强制重定向(即使设置了noclobber选项--就是-C选项).这将强制的覆盖一个现存文件.

||        或-逻辑操作.

&        后台运行命令.一个命令后边跟一个&,将表示在后台运行.
       bash$sleep 10 &
       [1] 850
       [1]+    Done            sleep 10
       在一个脚本中,命令和循环都可能运行在后台.

Example 3-3. 在后台运行一个循环
################################Start Script#######################################
1 #!/bin/bash
2 #background-loop.sh
3
4 for i in 1 2 3 4 5 6 7 8 9 10                #第一个循环
5 do
6    echo -n "$i"
7 done&                        #在后台运行这个循环
8                                #在第2个循环之后,将在某些时候执行.
9
10 echo    #这个'echo'某些时候将不会显示.
11
12 for i in 11 12 13 14 15 16 17 18 19 20        #第二个循环
13 do
14     echo -n "$i"
15 done
16
17 echo    #这个'echo'某些时候将不会显示.
18
19 #--------------------------------------------------------
20
21 #期望的输出应该是
22 #1 2 3 4 5 6 7 8 9 10
23 #11 12 13 14 15 16 17 18 19 20
24
25 #然而实际的结果有可能是
26 #11 12 13 14 15 16 17 18 19 20
27 #1 2 3 4 5 6 7 8 9 10 bozo $
28 #(第2个'echo'没执行,为什么?)
29
30 #也可能是
31 #1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
32 #(第1个'echo'没执行,为什么?)
33
34 #非常少见的执行结果,也有可能是:
35 #11 12 13 1 2 3 4 5 6 7 8 9 10 14 15 16 17 18 19 20
36 #前台的循环先于后台的执行
37
38 exit 0
39
40 #    Nasimuddin Ansari 建议加一句 sleep 1
41 #+    在 6行和14行的 echo -n "$i"之后加
42 #+    将看到一些乐趣
################################End Script#########################################
       注意:在一个脚本内后台运行一个命令,有可能造成这个脚本的挂起,等待一个按键
           响应.幸运的是,我们可以在Example 11-24附近,看到这个问题的解决办法.

&&        与-逻辑操作.

-        选项,前缀.在所有的命令内如果想使用选项参数的话,前边都要加上"-".

       COMMAND -[Option1][Option2][...]
       ls -al
       sort -dfu $filename
       set -- $variable

        1 if [ $file1 -ot $file2 ]
        2 then
        3   echo "File $file1 is older than $file2."
        4 fi
        5
        6 if [ "$a" -eq "$b" ]
        7 then
        8   echo "$a is equal to $b."
        9 fi
       10
       11 if [ "$c" -eq 24 -a "$d" -eq 47 ]
       12 then
       13   echo "$c equals 24 and $d equals 47."
       14 fi

-        用于重定向 stdin 或 stdout.

################################Start Script#######################################
1 (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)
2 # 从一个目录移动整个目录树到另一个目录
3 # [courtesy Alan Cox , with a minor change]
4
5 # 1) cd /source/directory    源目录
6 # 2) &&                      与操作,如果cd命令成功了,那么就执行下边的命令
7 # 3) tar cf - .              'c'创建一个新文档,'f'后边跟'-'指定目标文件作为stdout
8 #                            '-'后边的'f'(file)选项,指明作为stdout的目标文件.
9 #                            并且在当前目录('.')执行.
10 # 4) |                       管道...
11 # 5) ( ... )                 一个子shell
12 # 6) cd /dest/directory      改变当前目录到目标目录.
13 # 7) &&                      与操作,同上.
14 # 8) tar xpvf -              'x'解档,'p'保证所有权和文件属性,
15 #                            'v'发完整消息到stdout
16 #                            'f'后边跟'-',从stdin读取数据
17 #
18 #                            注意:'x' 是一个命令, 'p', 'v', 'f' 是选项.
19 # Whew!
20
21
22
23 # 更优雅的写法应该是
24 #   cd source/directory
25 #   tar cf - . | (cd ../dest/directory; tar xpvf -)
26 #
27 #     当然也可以这么写:
28 # cp -a /source/directory/* /dest/directory
29 #     或者:
30 # cp -a /source/directory/* /source/directory/.[^.]* /dest/directory
31 #     如果在/source/directory中有隐藏文件的话.
################################End Script#########################################

################################Start Script#######################################
1 bunzip2 linux-2.6.13.tar.bz2 | tar xvf -
2 # --未解压的tar文件--    | --然后把它传递到"tar"中--
3 # 如果 "tar" 没能够正常的处理"bunzip2",
4 # 这就需要使用管道来执行2个单独的步骤来完成它.
5 # 这个练习的目的是解档"bzipped"的kernel源文件.
################################End Script#########################################
       注意:在上边这个例子中'-'不太象是bash的操作符,而更像是tar的参数.
       bash$echo "whatever" | cat -
       whatever

       在需要一个文件名的地方,-重定向输出到stdout(如在tar和cf命令中),或者从
       stdin中接受输入,而不是从一个文件中接受输入.这是在管道中作为一个过滤
       器,来使用文件定位工具的一种办法.
       bash$file
       用法: file [-bciknvzl] [-f namefile] [-m magicfiles] file...
       上边这个例子file将会出错,提示你如何使用file命令.

       添加一个"-"将得到一个更有用的结果.这将使得shell等待用户输入.
       bash$file -
       abc
       standard input:                    ASCII text

       bash$file -
       #!/bin/bash
       standard input:                    Bourn-Again shell script tesxt executable

       现在命令从stdin中接受了输入,并分析它.

       "-"常用于管道后边的命令,具体参看33.7节,来看使用技巧.
       使用diff命令来和另一个文件的一部分进行比较.
       grep Linux file1 | diff file2 -

       最后,一个真实世界的使用tar命令的例子.

Example 3-4. 备份最后一天所有修改的文件.
################################Start Script#######################################
1 #!/bin/bash
2
3 #  在一个"tarball"中(经过tar和gzip处理过的文件)
4 #+ 备份最后24小时当前目录下d所有修改的文件.
5
6 BACKUPFILE=backup-$(date +%m-%d-%Y)
7 #                 在备份文件中嵌入时间.
8 #                 Thanks, Joshua Tschida, for the idea.
9 archive=${1:-$BACKUPFILE}
10 #  如果在命令行中没有指定备份文件的文件名,
11 #+ 那么将默认使用"backup-MM-DD-YYYY.tar.gz".
12
13 tar cvf - `find . -mtime -1 -type f -print` > $archive.tar
14 gzip $archive.tar
15 echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."
16
17
18 #  Stephane Chazelas指出上边代码,
19 #+ 如果在发现太多的文件的时候,或者是如果文件
20 #+ 名包括空格的时候,将执行失败.
21
22 # Stephane Chazelas建议使用下边的两种代码之一
23 # -------------------------------------------------------------------
24 #   find . -mtime -1 -type f -print0 | xargs -0 tar rvf "$archive.tar"
25 #      使用gnu版本的find.
26
27
28 #   find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;
29 #         对于其他风格的UNIX便于移植,但是比较慢.
30 # -------------------------------------------------------------------
31
32
33 exit 0
################################End Script#########################################

       注意:以"-"开头的文件名在使用"-"作为重定向操作符的时候,可能会产生问题.
       应该写一个脚本来检查这个问题,并给这个文件加上合适的前缀.如:
       ./-FILENAME, $PWD/-FILENAME,或$PATHNAME/-FILENAME.

       如果变量的值以"-"开头,可能也会引起问题.
       1 var="-n"
       2 echo $var
       3 #具有"echo -n"的效果了,这样什么都不会输出的.

-        之前工作的目录."cd -"将回到之前的工作目录,具体请参考"$OLDPWD"环境变量.
       注意:一定要和之前讨论的重定向功能分开,但是只能依赖上下文区分.

-        算术减号.

=        算术等号,有时也用来比较字符串.
       1 a=28
       2 echo $a   # 28

+        算术加号,也用在正则表达式中.
+        选项,对于特定的命令来说使用"+"来打开特定的选项,用"-"来关闭特定的选项.

%        算术取模运算.也用在正则表达式中.

~        home目录.相当于$HOME变量.~bozo是bozo的home目录,并且ls ~bozo将列出其中的
       内容. ~/就是当前用户的home目录,并且ls ~/将列出其中的内容,如:
       bash$ echo ~bozo
       /home/bozo

       bash$ echo ~
       /home/bozo

       bash$ echo ~/
       /home/bozo/

       bash$ echo ~:
       /home/bozo:

       bash$ echo ~nonexistent-user
       ~nonexistent-user

~+        当前工作目录,相当于$PWD变量.

~-        之前的工作目录,相当于$OLDPWD内部变量.

=~        用于正则表达式,这个操作将在正则表达式匹配部分讲解,只有version3才支持.

^        行首,正则表达式中表示行首."^"定位到行首.


控制字符
       修改终端或文本显示的行为.控制字符以CONTROL + key组合.
       控制字符在脚本中不能正常使用.
       Ctl-B        光标后退,这应该依赖于bash输入的风格,默认是emacs风格的.
       Ctl-C        Break,终止前台工作.
       Ctl-D        从当前shell登出(和exit很像)
                   "EOF"(文件结束符).这也能从stdin中终止输入.
                   在console或者在xterm window中输入的时候,Ctl-D将删除光标下字符.
                   当没有字符时,Ctrl-D将退出当前会话.在xterm window也有关闭窗口
                   的效果.
       Ctl-G        beep.在一些老的终端,将响铃.
       Ctl-H        backspace,删除光标前边的字符.如:
                    1 #!/bin/bash
                    2 # 在一个变量中插入Ctl-H
                    3
                    4 a="^H^H"                  # 两个 Ctl-H (backspaces).
                    5 echo "abcdef"             # abcdef
                    6 echo -n "abcdef$a "       # abcd f
                    7 # 注意结尾的空格 ^              ^ 两个 twice.
                    8 echo -n "abcdef$a"        # abcdef
                    9 #  结尾没有空格             没有 backspace 的效果了(why?).
                   10                           # 结果并不像期望的那样
                   11 echo; echo
       Ctl-I        就是tab键.
       Ctl-J        新行.
       Ctl-K        垂直tab.(垂直tab?新颖,没听过)
                   作用就是删除光标到行尾的字符.
       Ctl-L        clear,清屏.
       Ctl-M        回车
################################Start Script#######################################
1 #!/bin/bash
2 # Thank you, Lee Maschmeyer, for this example.
3
4 read -n 1 -s -p $'Control-M leaves cursor at beginning of this line. Press Enter. \x0d'
5                                   #当然,'0d'就是二进制的回车.
6 echo >&2   #  '-s'参数使得任何输入都不将回显出来
7            #+ 所以,明确的重起一行是必要的.
8
9 read -n 1 -s -p $'Control-J leaves cursor on next line. \x0a'
10 echo >&2   #  Control-J 是换行.
11
12 ###
13
14 read -n 1 -s -p $'And Control-K\x0bgoes straight down.'
15 echo >&2   #  Control-K 是垂直制表符.
16
17 # 关于垂直制表符效果的一个更好的例子见下边:
18
19 var=$'\x0aThis is the bottom line\x0bThis is the top line\x0a'
20 echo "$var"
21 #  这句与上边的例子使用的是同样的办法,然而:
22 echo "$var" | col
23 #  这将造成垂直制表符右边的部分在左边部分的上边.
24 #  这也解释了为什么我们要在行首和行尾加上一个换行符--
25 #+ 来避免一个混乱的屏幕输出.
26
27 # Lee Maschmeyer的解释:
28 # ---------------------
29 #  In the [first vertical tab example] . . . the vertical tab
29 #  在这里[第一个垂直制表符的例子中] . . . 这个垂直制表符
30 #+ makes the printing go straight down without a carriage return.
31 #  This is true only on devices, such as the Linux console,
32 #+ that can't go "backward."
33 #  The real purpose of VT is to go straight UP, not down.
34 #  It can be used to print superscripts on a printer.
34 #  它可以用来在一个打印机上打印上标.
35 #  col的作用,可以用来模仿VT的合适的行为.
36
37 exit 0
################################End Script#########################################
       Ctl-Q        继续(等价于XON字符),这个继续的标准输入在一个终端里
       Ctl-S        挂起(等价于XOFF字符),这个被挂起的stdin在一个终端里,用Ctl-Q恢复
       Ctl-U        删除光标到行首的所有字符,在某些设置下,删除全行.
       Ctl-V        当输入字符时,Ctl-V允许插入控制字符.比如,下边2个例子是等价的
                   echo -e '\x0a'
                   echo
                   Ctl-V在文本编辑器中十分有用,在vim中一样.
       Ctl-W        删除当前光标到前边的最近一个空格之间的字符.
                   在某些设置下,删除到第一个非字母或数字的字符.
       Ctl-Z        终止前台工作.
       
空白部分
       分割命令或者是变量.包括空格,tab,空行,或任何它们的组合.
       在一些特殊情况下,空白是不允许的,如变量赋值时,会引起语法错误.
       空白行在脚本中没有效果.
       "$IFS",对于某些命令输入的特殊变量分割域,默认使用的是空白.
       如果想保留空白,使用引用.

注意事项:
[1]        shell做大括号的命令扩展.但是命令本身需要对扩展的结果作处理.
[2]        例外:在pipe中的一个大括号中的代码段可能运行在一个子shell中.
       1 ls | { read firstline; read secondline; }
       2 #  错误,在打括号中的代码段,将运行到子shell中.
       3 #+ 所以ls的输出将不能传递到代码块中.
       4 echo "First line is $firstline; second line is $secondline"  # 不能工作
       5
       6 # Thanks, S.C.
[3]        换行符也被认为是空白.这也解释了为什么一个空行也会被认为是空白.



第4章 变量和参数的介绍
======================

4.1 变量替换
------------
$        变量替换操作符
       只有在变量被声明,赋值,unset或exported或者是在变量代表一个signal的时候,
       变量才会是以本来的面目出现在脚本里.变量在被赋值的时候,可能需要使用"=",
       read状态或者是在循环的头部.
       在""中还是会发生变量替换,这被叫做部分引用,或叫弱引用.而在''中就不会发生变
       量替换,这叫做全引用,也叫强引用.具体见第5章的讨论.

       注意:$var与${var}的区别,不加{},在某些上下文将引起错误,为了安全,使用2.
           具体见9.3节 参数替换.

Example 4-1. 变量赋值和替换
################################Start Script#######################################
1 #!/bin/bash
2
3 # 变量赋值和替换
4
5 a=375
6 hello=$a
7
8 #-------------------------------------------------------------------------
9 # 强烈注意,在赋值的前后一定不要有空格.
10 # 如果有空格会发生什么?
11
12 #  如果"VARIABLE =value",
13 #              ^
14 #+ 脚本将尝试运行一个"VARIABLE"的命令,带着一个"=value"参数.
15
16 #  如果"VARIABLE= value",
17 #               ^
18 #+ script tries to run "value" command with
18 #+ 脚本将尝试运行一个"value"的命令,带着
19 #+ the environmental variable "VARIABLE" set to "".
19 #+ 一个被赋成""值的环境变量"VARIABLE".
20 #-------------------------------------------------------------------------
21
22
23 echo hello    # 没有变量引用,不过是个hello字符串
24
25 echo $hello
26 echo ${hello} # 同上
27
28 echo "$hello"
29 echo "${hello}"
30
31 echo
32
33 hello="A B  C   D"
34 echo $hello   # A B C D
35 echo "$hello" # A B  C   D
36 # 就象你看到的echo $hello   和    echo "$hello"   将给出不同的结果.
37 #                                      ^      ^
38 # Quoting a variable preserves whitespace.
38 # 引用一个变量将保留其中的空白,当然,如果是变量替换就不会保留了.
39
40 echo
41
42 echo '$hello'  # $hello
43 #    ^      ^
44 # 全引用的作用
45 #+ 将导致"$"变成一个单独的字符.
46
47 # 注意两种引用不同的效果
48
49
50 hello=    # 设置为空值
51 echo "\$hello (null value) = $hello"
52 #  注意设置一个变量为空,与unset它,不是一回事,虽然看起来一样
53 #
54
55 # --------------------------------------------------------------
56
57 #  可以在同一行上设置多个变量.
58 #+ 要以空白分隔
59 #  小心,这会降低可读性,和可移植性.
60
61 var1=21  var2=22  var3=$V3
62 echo
63 echo "var1=$var1   var2=$var2   var3=$var3"
64
65 # 在老版本的"sh"上,可能会有问题.
66
67 # --------------------------------------------------------------
68
69 echo; echo
70
71 numbers="one two three"
72 #           ^   ^
73 other_numbers="1 2 3"
74 #               ^ ^
75 #  如果变量中有空白,那么引用就必要了.
76 #
77 echo "numbers = $numbers"
78 echo "other_numbers = $other_numbers"   # other_numbers = 1 2 3
79 echo
80
81 echo "uninitialized_variable = $uninitialized_variable"
82 # Uninitialized变量为空值(根本就没赋值).
83 uninitialized_variable=   #  声明,但是没被初始化
84                           #+ 其实和前边设置为空值得作用是一样的.
85 echo "uninitialized_variable = $uninitialized_variable"
86                           # 还是一个空值
87
88 uninitialized_variable=23       # 赋值
89 unset uninitialized_variable    # Unset it.
90 echo "uninitialized_variable = $uninitialized_variable"
91                                 # 还是空值
92 echo
93
94 exit 0
################################End Script#########################################
注意: 一个空值变量,或者是根本就没声明的变量,在赋值之前使用它可能会引起问题.
       但是还是可以用来做算术运算
################################Start Script#######################################
1 echo "$uninitialized"                                # (blank line)
2 let "uninitialized += 5"                             # Add 5 to it.
3 echo "$uninitialized"                                # 5
4
5 #  结论:
6 #  对于一个空值变量在做算术操作的时候,就好像它的值为0一样.
8 #  This is undocumented (and probably non-portable) behavior.
7 #  这并没被文档化(可能是不可移植)的行为.
################################End Script#########################################
具体参考    Example 11-21


4.2 变量赋值
------------
=        赋值操作符(前后都不能有空白)
       不要与-eq混淆,那个是test,并不是赋值.
       注意,=也可被用来做test操作,这依赖于上下文.

Example 4-2. 一般的变量赋值
################################Start Script#######################################
1 #!/bin/bash
2 # "裸体"变量
3
4 echo
5
6 # 变量什么时候是"裸体"的,比如前边少了$的时候.
7 # 当它被赋值的时候,而不是被引用的时候.
8
9 # 赋值
10 a=879
11 echo "The value of \"a\" is $a."
12
13 # 使用let赋值
14 let a=16+5
15 echo "The value of \"a\" is now $a."
16
17 echo
18
19 # 在for循环中
20 echo -n "Values of \"a\" in the loop are: "
21 for a in 7 8 9 11
22 do
23   echo -n "$a "
24 done
25
26 echo
27 echo
28
29 # 在read命令状态中
30 echo -n "Enter \"a\" "
31 read a
32 echo "The value of \"a\" is now $a."
33
34 echo
35
36 exit 0
################################End Script#########################################

Example 4-3. 变量赋值,一般的和比较特殊的
################################Start Script#######################################
1 #!/bin/bash
2
3 a=23              # Simple case
4 echo $a
5 b=$a
6 echo $b
7
8 # 现在让我们来点小变化
9
10 a=`echo Hello!`   # 把echo命令的结果传给变量a
11 echo $a
12 #  注意,如果在命令扩展结构中使用一个(!)的话,在命令行中将不能工作
13 #+ 因为这触发了Bash的"历史机制".
14 #  但是,在校本里边使用的话,历史功能是被关闭的,所以就能够正常运行.
15
16
17 a=`ls -l`         # 把ls -l的结果给a
18 echo $a           # 别忘了,这么引用的话,ls的结果中的所有空白部分都没了(包括换行)
19 echo
20 echo "$a"         # 这么引用就正常了,保留了空白
21                   # (具体参阅章节"引用")
22
23 exit 0
################################End Script#########################################
使用$(...)机制进行的变量赋值(除去使用``来赋值的另外一种新方法).事实上这两种方法都是
命令替换的一种形式.
# 来自于/ect/rc.d/rc.local
R=$(cat /ect/redhat-release)
arch=$(uname -m)


4.3 Bash变量是不分类型的
------------------------
不像其他程序语言一样,Bash并不对变量区分"类型".本质上,Bash变量都是字符串.
但是依赖于上下文,Bash也允许比较操作和算术操作.决定这些的关键因素就是,变量中的值
是否只有数字.

Example 4-4 整型还是string?
################################Start Script#######################################
1 #!/bin/bash
2 # int-or-string.sh: 整形还是string?
3
4 a=2334                   # 整型
5 let "a += 1"
6 echo "a = $a "           # a = 2335
7 echo                     # 还是整型
8
9
10 b=${a/23/BB}             # 将23替换成BB
11                          # 这将把b变量从整型变为string
12 echo "b = $b"            # b = BB35
13 declare -i b             # 即使使用declare命令也不会对此有任何帮助,9.4节有解释
14 echo "b = $b"            # b = BB35
15
16 let "b += 1"             # BB35 + 1 =
17 echo "b = $b"            # b = 1
18 echo
19
20 c=BB34
21 echo "c = $c"            # c = BB34
22 d=${c/BB/23}             # S将BB替换成23
23                          # 这使得$d变为一个整形
24 echo "d = $d"            # d = 2334
25 let "d += 1"             # 2334 + 1 =
26 echo "d = $d"            # d = 2335
27 echo
28
29 # 关于空变量怎么样?
30 e=""
31 echo "e = $e"            # e =
32 let "e += 1"             # 算术操作允许一个空变量?
33 echo "e = $e"            # e = 1
34 echo                     # 空变量将转换成一个整型变量
35
36 # 关于未声明的变量怎么样?
37 echo "f = $f"            # f =
38 let "f += 1"             # 算术操作允许么?
39 echo "f = $f"            # f = 1
40 echo                     # 未声明的变量将转换成一个整型变量
41
42
43
44 # 所以说Bash中的变量都是无类型的.
45
46 exit 0
################################End Script#########################################


4.4 特殊的变量类型
------------------
local variables
       这种变量只有在代码块或者是函数中才可见(具体见23.2和23章)
environmental variables
       这种变量将改变用户接口和shell的行为.

       在一般的上下文中,每个进程都有自己的环境,就是一组保持进程可能引用的信息的
       变量.这种情况下,shell于一个一般进程是相同的.

       每次当shell启动时,它都将创建自己的环境变量.更新或者添加新的环境变量,将导
       致shell更新它的环境,同时也会影响所有继承自这个环境的所有子进程(由这个命令
       导致的).

       注意:分配给环境变量的空间是受限的.创建太多的环境变量将引起空间溢出,这会引
       起问题.
       关于eval命令,具体见第11章
       bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
       bash$ du
       bash: /usr/bin/du: Argument list too long

       如果一个脚本设置了环境变量,需要export它,来通知本脚本的环境,这是export
       命令的功能,关于export命令,具体见11章.
       
       脚本只能对它产生的子进程export变量.一个从命令行被调用的脚本export的变量,将
       不能影响调用这个脚本的那个命令行shell的环境.

positional parameters
       就是从命令行中传进来的参数,$0, $1, $2, $3...

       $0就是脚本文件的名字,$1是第一个参数,$2为第2个...,参见[1](有$0的说明),$9
       以后就需要打括号了,如${10},${11},${12}...
       两个值得注意的变量$*和$@(第9章有具体的描述),表示所有的位置参数.

Example 4-5 位置参数
################################Start Script#######################################
1 #!/bin/bash
2
3 # 作为用例,调用这个脚本至少需要10个参数,如
4 # ./scriptname 1 2 3 4 5 6 7 8 9 10
5 MINPARAMS=10
6
7 echo
8
9 echo "The name of this script is \"$0\"."
10 # 添加./是为了当前目录
11 echo "The name of this script is \"`basename $0`\"."
12 # 去掉目录信息,具体见'basename'命令
13
14 echo
15
16 if [ -n "$1" ]              # 测试变量被被引用
17 then
18  echo "Parameter #1 is $1"  # "#"没被转义
19 fi
20
21 if [ -n "$2" ]
22 then
23  echo "Parameter #2 is $2"
24 fi
25
26 if [ -n "$3" ]
27 then
28  echo "Parameter #3 is $3"
29 fi
30
31 # ...
32
33
34 if [ -n "${10}" ]  # 大于9的参数必须出现在{}中.
35 then
36  echo "Parameter #10 is ${10}"
37 fi
38
39 echo "-----------------------------------"
40 echo "All the command-line parameters are: "$*""
41
42 if [ $# -lt "$MINPARAMS" ]        #$#是传到脚本里的位置参数的个数
43 then
44   echo
45   echo "This script needs at least $MINPARAMS command-line arguments!"
46 fi  
47
48 echo
49
50 exit 0
################################End Script#########################################
       {}标记法是一种很好的使用位置参数的方法.这也需要间接引用(见Example 34-2)
       1 args=$#           # 位置参数的个数
       2 lastarg=${!args}
       3 # 或:       lastarg=${!#}
       4 # 注意 lastarg=${!$#} 将报错

       一些脚本可能会依赖于使用不同的调用名字,而表现出不同的行为,这样一般都需要
       判断$0,而其他的名字都是通过ln命令产生的链接.(具体参见Example 12-2)

       如果脚本需要一个命令行参数,而调用的时候,没用这个参数,这就有可能造成分配一个
       空变量,这样估计就会引起问题.一种解决办法就是在这个位置参数,和相关的变量后
       边,都添加一个额外的字符.具体见下边的例子.
################################Start Script#######################################
1 variable1_=$1_  # 而不是 variable1=$1
2 # 这将阻止一个错误,即使在调用时没使用这个位置参数.
3
4 critical_argument01=$variable1_
5
6 # 这个扩展的字符是可以被消除掉的,就像这样.
7 variable1=${variable1_/_/}
8 # 副作用就是$variable1_多了一个下划线
9 # 这里使用了一个参数替换模版(后边会有具体的讨论)
10 # (Leaving out the replacement pattern results in a deletion.)
10 # (在一个删除动作中,节省了一个替换模式)
11
12
13 # 一个解决这种问题的更简单的做法就是,判断一下这个位置参数是否传递下来了
14 if [ -z $1 ]
15 then
16   exit $E_MISSING_POS_PARAM
17 fi
18
19
20 #  但是上边的方法将可能产生一个意外的副作用
21 #  参数替换的更好的办法应该是:
22 #         ${1:-$DefaultVal}
23 #  具体察看"Parameter Substition"节
24 #+ 在第9章
################################End Script#########################################
       

Example 4-6 wh,whois节点名字查询
################################Start Script#######################################
1 #!/bin/bash
2 # ex18.sh
3
4 # Does a 'whois domain-name' lookup on any of 3 alternate servers:
5 #                    ripe.net, cw.net, radb.net
6
7 # 把这个脚本重命名为'wh',然后放到/usr/local/bin下
8
9 # 需要3个符号链接
10 # ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
11 # ln -s /usr/local/bin/wh /usr/local/bin/wh-cw
12 # ln -s /usr/local/bin/wh /usr/local/bin/wh-radb
13
14 E_NOARGS=65
15
16
17 if [ -z "$1" ]
18 then
19   echo "Usage: `basename $0` [domain-name]"
20   exit $E_NOARGS
21 fi
22
23 # Check script name and call proper server.
23 # 检查脚本名字,然后调用合适的服务器
24 case `basename $0` in    # Or:    case ${0##*/} in
25     "wh"     ) whois $1@whois.ripe.net;;
26     "wh-ripe") whois $1@whois.ripe.net;;
27     "wh-radb") whois $1@whois.radb.net;;
28     "wh-cw"  ) whois $1@whois.cw.net;;
29     *        ) echo "Usage: `basename $0` [domain-name]";;
30 esac
31
32 exit $?
################################End Script#########################################

shift        shift命令重新分配位置参数,其实就是向左移动一个位置.
   $1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.
       老的$1将消失,但是$0(脚本名)是不会改变的.如果你使用了大量的位置参数,那么
       shift命令允许你存取超过10个参数.虽然{}表示法也允许这样.


Example 4-7 使用shift
################################Start Script#######################################
1 #!/bin/bash
2 # 使用'shift'来穿过所有的位置参数.
3
4 #  把这个脚本命名为shft,
5 #+ 并且使用一些参数来调用它,如:
6 #          ./shft a b c def 23 skidoo
7
8 until [ -z "$1" ]  # 知道所有参数都用光
9 do
10   echo -n "$1 "
11   shift
12 done
13
14 echo               # 额外的换行.
15
16 exit 0
################################End Script#########################################
       在将参数传递到函数中时,shift的工作方式也基本差不多.具体见Example 33-15

注意事项:
[1]        进程调用设置$0参数的脚本.一般的,这个参数就是脚本名字.具体察看execv的man页.



第5章 引用(翻译的可能有问题,特指引号)
======================================
引号的特殊效果就是,保护字符串中的特殊字符不被shell或者是shell脚本重新解释或者扩展.
(我们这里所说的"特殊"指的是一些字符在shell中具有的特殊意义,比如*)
如:
bash$ ls -l [Vv]*
-rw-rw-r--    1 bozo  bozo       324 Apr  2 15:05 VIEWDATA.BAT
-rw-rw-r--    1 bozo  bozo       507 May  4 14:25 vartrace.sh
-rw-rw-r--    1 bozo  bozo       539 Apr 14 17:11 viewdata.sh
   
bash$ ls -l '[Vv]*'
ls: [Vv]*: No such file or directory

在我们一般的生活中,引号内的内容往往有特殊的含义,而在Bash中,当我们引用一个字符串,
我们是保护它的字面含义.

特定的程序和工具能够重新解释或扩展特殊的字符.引用的一个重要的作用就是保护命令行中
的参数,但还是允许正在调用的程序来扩展它.
bash$ grep '[Ff]irst' *.txt
file1.txt:This is the first line of file1.txt.
file2.txt:This is the First line of file2.txt.

注意 grep [Ff]irst *.txt在Bash下的行为(其实就是正则表达式么),[1]

引用还可以抑制echo命令的换行作用.

bash$ echo $(ls -l)
total 8 -rw-rw-r-- 1 bozo bozo 130 Aug 21 12:57 t222.sh -rw-rw-r-- 1 bozo bozo 78 Aug 21 12:57 t71.sh

bash$ echo "$(ls -l)"
total 8
-rw-rw-r--  1 bozo bozo 130 Aug 21 12:57 t222.sh
-rw-rw-r--  1 bozo bozo  78 Aug 21 12:57 t71.sh


5.1 引用变量
------------
在一个双引号中直接使用变量名,一般都是没有问题的.它阻止了所有在引号中的特殊字符的
重新解释--包括变量名[2]--但是$,`和\除外.[3]保留$,作为特殊字符的意义,是为了能够在双
引号中也能够正常地引用变量("$var").这样在""中可以使用变量所表达的值(Example 4-1).

使用""来防止单词分割.[4]如果在参数列表中使用双引号,将使得双引号中的参数作为一个参
数.即使双引号中的字符串包含多个单词(也就是包含空白部分),也不会变为多个参数,如:
1 variable1="a variable containing five words"
2 COMMAND This is $variable1    # COMMAND将以7个参数来执行
3 # "This" "is" "a" "variable" "containing" "five" "words"
4
5 COMMAND "This is $variable1"  # COMMAND将以1个参数来执行
6 # "This is a variable containing five words"
7
8
9 variable2=""    # 空值
10
11 COMMAND $variable2 $variable2 $variable2        # COMMAND将不带参数执行
12 COMMAND "$variable2" "$variable2" "$variable2"  # COMMAND将以3个空参数来执行
13 COMMAND "$variable2 $variable2 $variable2"      # COMMAND将以1个参数来执行(2空格)
   用双引号把参数封到echo中是很有必要的,只有在单词分隔或时保留空白时的时候可能
   有些问题.

Example 5-1 echo一些诡异的变量
################################Start Script#######################################
1 #!/bin/bash
2 # weirdvars.sh: echo诡异的变量
3
4 var="'(]\\{}\$\""
5 echo $var        # '(]\{}$"
6 echo "$var"      # '(]\{}$"    并没有什么不同
7
8 echo
9
10 IFS='\'
11 echo $var        # '(] {}$"     \ 转换成空格了?明显和IFS有关系么!又不傻!
12 echo "$var"      # '(]\{}$"
13
14 exit 0
################################End Script#########################################

单引号操作总体上和""很像,但不允许引用变量.因为$的特殊含义被关闭了.在''中除了',其他
字符都没有特殊的含义了.所以单引号比双引号严格.
   因为即使是\,在''中都被关闭了,所以你想在''中显示'的含义,将得不到预期的效果.
  1 echo "Why can't I write 's between single quotes"
  2
  3 echo
  4
  5 # 一种绕弯的方法
  6 echo 'Why can'\''t I write '"'"'s between single quotes'
  7 #    |-------|  |----------|   |-----------------------|
  8 # 包含了2个单引号字符,原书好像有错误

注意事项:
[1]        除非当前目录下,正好有个叫first的文件.
[2]        即使是变量的值也是有副作用的(见下边)
[3]        如果在""中包含"!"的话,在命令行中将会出现错误.因为这个"!"被当作历史命令来解释了.
       在一个脚本中,这种情况是不会发生的,因为在脚本中,Bash历史记录被关闭了.

       下边是一些关于"\"一些不协调的行为.
       bash$ echo hello\!
       hello!

       bash$ echo "hello\!"
       hello\!

       bash$ echo -e x\ty
       xty

       bash$ echo -e "x\ty"
       x       y

[4]        "单词分隔",在这个上下文中意味着,将一个字符串分隔为一些分离的参数.


5.2 转义(\)
-----------
转义是一种引用单个字符的方法.一个具有特殊含义的字符前边放上一个转义符(\)就告诉shell
这个字符失去了特殊的含义.
   值得注意的是,在某些特定的命令和工具中,比如echo和sed,转义符往往会起到相反的效果,
   它反倒有可能引发出这个字符特殊的含义.

对于特定的转义符的特殊的含义
在echo和sed中所使用的
\n        意味着新的一行
\r        回车
\t        tab键
\v        vertical tab(垂直tab),查前边的Ctl-K
\b        backspace,查前边的Ctl-H
\a        "alert"(如beep或flash)
\0xx    转换成8进制ASCII解码,等价于oxx

Example 5-2 转义符
################################Start Script#######################################
1 #!/bin/bash
2 # escaped.sh: 转义符
3
4 echo; echo
5
6 echo "\v\v\v\v"      # 逐字的打印\v\v\v\v .
7 # 使用-e选项的echo命令来打印转义符
8 echo "============="
9 echo "VERTICAL TABS"
10 echo -e "\v\v\v\v"   # Prints 4 vertical tabs.
11 echo "=============="
12
13 echo "QUOTATION MARK"
14 echo -e "\042"       # 打印" (引号, 8进制的ASCII 码就是42).
15 echo "=============="
16
17 # The $'\X' construct makes the -e option unnecessary.
17 # 如果使用$'\X'结构,那-e选项就不必要了
18 echo; echo "NEWLINE AND BEEP"
19 echo $'\n'           # 新行.
20 echo $'\a'           # Alert (beep).
21
22 echo "==============="
23 echo "QUOTATION MARKS"
24 # 版本2以后Bash允许使用$'\nnn'结构
25 # 注意这种情况,'\nnn\是8进制
26 echo $'\t \042 \t'   # Quote (") framed by tabs.
27
28 # 当然,也可以使用16进制的值,使用$'\xhhh' 结构
29 echo $'\t \x22 \t'  # Quote (") framed by tabs.
30
31 # 早一点的Bash版本允许'\x022'这种形式
32 echo "==============="
33 echo
34
35
36 # 分配ASCII字符到变量中
37 # ---------------------
38 quote=$'\042'        # \042是",分配到变量中
39 echo "$quote This is a quoted string, $quote and this lies outside the quotes."
40
41 echo
42
43 # Concatenating ASCII chars in a variable.
43 # 变量中的连续的ASCII char.
44 triple_underline=$'\137\137\137'  # 137 是8进制的ASCII 码'_'.
45 echo "$triple_underline UNDERLINE $triple_underline"
46
47 echo
48
49 ABC=$'\101\102\103\010'           # 101, 102, 103 是8进制的码A, B, C.
50 echo $ABC
51
52 echo; echo
53
54 escape=$'\033'                    # 033 是8进制码for escape.
55 echo "\"escape\" echoes as $escape"
56 #"escape" echoes as                  没有变量被输出
57
58 echo; echo
59
60 exit 0
################################End Script#########################################
   另一个关于$''字符串扩展结果的例子见Example 34-1

\"        表达引号本身
       1 echo "Hello"                  # Hello
       2 echo "\"Hello\", he said."    # "Hello", he said.

\$        $号本身,跟在\$后的变量名,将不能扩展
       1 echo "\$variable01"  # 结果是$variable01

\\        \号本身.
       1 echo "\\"  # 结果是\
       2
       3 # 相反的 . . .
       4
       5 echo "\"   # 这会出现第2个命令提示符,说白了就是提示你命令不全,你再补个"就
       6             # 好了.如果是在脚本里,就会给出一个错误.

   注意:\的行为依赖于它是否被转义,被"",或者是否在"命令替换"和"here document"中.
################################Start Script#######################################
1                       #  简单的转义和""
2 echo \z               #  z
3 echo \\z              # \z
4 echo '\z'             # \z
5 echo '\\z'            # \\z
6 echo "\z"             # \z
7 echo "\\z"            # \z
8
9                       #  命令替换
10 echo `echo \z`        #  z
11 echo `echo \\z`       #  z
12 echo `echo \\\z`      # \z
13 echo `echo \\\\z`     # \z
14 echo `echo \\\\\\z`   # \z
15 echo `echo \\\\\\\z`  # \\z
16 echo `echo "\z"`      # \z
17 echo `echo "\\z"`     # \z
18
19                       # Here document
20 cat <21 \z                      
22 EOF                   # \z
23
24 cat <25 \\z                    
26 EOF                   # \z
################################End Script#########################################

   分配给变量的字符串的元素也会被转义,但是只把一个转义符分配给变量将会报错.
################################Start Script#######################################
1 variable=\
2 echo "$variable"
3 # Will not work - gives an error message:
3 # 将不能正常工作- 将给出一个错误消息:
4 # test.sh: : command not found
5 # 一个"裸体的" 转义符将不能够安全的分配给变量.
6 #
7 #  What actually happens here is that the "\" escapes the newline and
7 #  这里其实真正发生的是variable=\,这句被shell认为是没有完成,\被认为是一个续行符
8 #+ 这样,下边的这句echo,也被认为是上一行的补充.所以,总的来说就是一个非法变量分配
9
10 variable=\
11 23skidoo
12 echo "$variable"        #  23skidoo
13                         #  这句就可以使用,因为这是一个合法的变量分配
14
15 variable=\
16 #        \^   转义一个空格
17 echo "$variable"        # 显示空格
18
19 variable=\\
20 echo "$variable"        # \
21
22 variable=\\\
23 echo "$variable"
24 # 不能正常工作,给出一个错误
25 # test.sh: \: command not found
26 #
27 #  第一个转义符把第2个\转义了,但是第3个又变成"裸体的"了,
28 #+ 与上边的例子的原因相同
29
30 variable=\\\\
31 echo "$variable"        # \\
32                         # 转了两个\
33                         # 没问题
################################End Script#########################################

转义一个空格,在命令行参数列表中将会阻止单词分隔问题.
################################Start Script#######################################
1 file_list="/bin/cat /bin/gzip /bin/more /usr/bin/less /usr/bin/emacs-20.7"
2 # 列出的文件都作为命令的参数.
3
4 # Add two files to the list, and list all.
4 # 加2个文件到list中,并且列出全部.
5 ls -l /usr/X11R6/bin/xsetroot /sbin/dump $file_list
6
7 echo "-------------------------------------------------------------------------"
8
9 # 如果我们转义2个空格,会发生什么?
10 ls -l /usr/X11R6/bin/xsetroot\ /sbin/dump\ $file_list
11 # 错误: 因为前3个路径名被合并成一个参数传给了'ls -l'
12 #        因为2个转义符阻止了参数(单词)分离
################################End Script#########################################

转义符也提供续行功能.一般,每一行都包含一个不同的命令,但如果在行尾加上\,那就会接受
新行的输入,作为这一行的补充.
1 (cd /source/directory && tar cf - . ) | \
2 (cd /dest/directory && tar xpvf -)
3 # 重复了 Alan Cox的目录树拷贝命令
4 # 为了增加可读性分成2行.
5
6 # 也可以使用如下方式:
7 tar cf - -C /source/directory . |
8 tar xpvf - -C /dest/directory
9 # 察看下边的注意事项

注意:如果一个脚本以|(管道字符)结束.那么一个\(转义符),就不用非加上不可了.
   但是一个好的shell脚本编写风格,还是应该在行尾加上\,以增加可读性.
################################Start Script#######################################
1 echo "foo
2 bar"
3 #foo
4 #bar
5
6 echo
7
8 echo 'foo
9 bar'    # 没区别
10 #foo
11 #bar
12
13 echo
14
15 echo foo\
16 bar     # 续行
17 #foobar
18
19 echo
20
21 echo "foo\
22 bar"     # 与上边一样,\还是作为续行符
23 #foobar
24
25 echo
26
27 echo 'foo\
28 bar'     # 由于是强引用,所以\没被解释成续行符
29 #foo\
30 #bar
################################End Script#########################################



第6章 退出和退出状态
====================
exit命令被用来结束脚本,就像C语言一样.他也会返回一个值来传给父进程,父进程会判断是否
可用.

每个命令都会返回一个exit状态(有时候也叫return状态).成功返回0,如果返回一个非0值,通
常情况下都会被认为是一个错误码.一个编写良好的UNIX命令,程序,和工具都会返回一个0作为
退出码来表示成功,虽然偶尔也会有例外.

同样的,脚本中的函数和脚本本身都会返回退出状态.在脚本或者是脚本函数中执行的最后的命
令会决定退出状态.在脚本中,exit nnn命令将会把nnn退出码传递给shell(nnn必须是10进制数
0-255).

当一个脚本以不带参数exit来结束时,脚本的退出状态就由脚本中最后执行命令来决定.
1 #!/bin/bash
2
3 COMMAND_1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST
9
10 exit $?

1 #!/bin/bash
2
3 COMMAND1
4
5 . . .
6
7 # 将以最后的命令来决定退出状态
8 COMMAND_LAST

$?读取最后执行命令的退出码.函数返回后,$?给出函数最后执行的那条命令的退出码.这种给
函数返回值的方法是Bash的方法.对于脚本来说也一样.总之,一般情况下,0为成功,非0失败W.
Example 6-1 exit/exit状态
################################Start Script#######################################
1 #!/bin/bash
2
3 echo hello
4 echo $?    # 返回0,因为执行成功
5
6 lskdf      # 不认识的命令.
7 echo $?    # 返回非0值,因为失败了.
8
9 echo
10
11 exit 113   # 将返回113给shell.
12            # To verify this, type "echo $?" after script terminates.
12            # 为了验证这个,在脚本结束的地方使用"echo $?"
################################End Script#########################################

$?对于测试脚本中的命令的结果特别有用(见Example 12-32和Example 12-17).
注意: !逻辑非操作,将会反转test命令的结果,并且这会影响exit状态.
Example 6-2 否定一个条件使用!
################################Start Script#######################################
1 true  # true是shell内建命令,什么事都不做,就是shell返回0
2 echo "exit status of \"true\" = $?"     # 0
3
4 ! true
5 echo "exit status of \"! true\" = $?"   # 1
6 # 注意:"!"需要一个空格
7 #    !true   将导致一个"command not found"错误
8 #
9 # 如果一个命令以'!'开头,那么将使用Bash的历史机制.就是显示这个命令被使用的历史.
10
11 true
12 !true
13 # 这次就没有错误了.
14 # 他不过是重复了之前的命令(true).
################################End Script#########################################

注意事项:
   特定的退出码都有预定的含义(见附录D),用户不应该在自己的脚本中指定他.



第7章 Tests
===========
每个完整的合理的编程语言都具有条件判断的功能.Bash具有test命令,不同的[]和()操作,和
if/then结构.


7.1 Test结构
------------
一个if/then结构可以测试命令的返回值是否为0(因为0表示成功),如果是的话,执行更多命令.

有一个专用命令"["(左中括号,特殊字符).这个命令与test命令等价,但是出于效率上的考虑,
它是一个内建命令.这个命令把它的参数作为比较表达式或是文件测试,并且根据比较的结果,
返回一个退出码.

在版本2.02的Bash中,推出了一个新的[[...]]扩展test命令.因为这种表现形式可能对某些语
言的程序员来说更加熟悉.注意"[["是一个关键字,并不是一个命令.

Bash把[[ $a -lt $b ]]看作一个单独的元素,并且返回一个退出码.

((...))和let...结果也能够返回一个退出码,当它们所测试的算术表达式的结果为非0的时候,
他们的退出码将返回0.这些算术扩展(见第15章)结构被用来做算术比较.
1 let "1<2" returns 0 (as "1<2" expands to "1")
2 (( 0 && 1 )) returns 1 (as "0 && 1" expands to "0")

if命令可以测试任何命令,不仅仅是括号中的条件.
1 if cmp a b &> /dev/null  # 阻止输出.
2 then echo "Files a and b are identical."
3 else echo "Files a and b differ."
4 fi
5
6 # 非常有用的"if-grep" 结构:
7 # ------------------------
8 if grep -q Bash file
9 then echo "File contains at least one occurrence of Bash."
10 fi
11
12 word=Linux
13 letter_sequence=inu
14 if echo "$word" | grep -q "$letter_sequence"
15 # "-q"选项是用来阻止输出
16 then
17   echo "$letter_sequence found in $word"
18 else
19   echo "$letter_sequence not found in $word"
20 fi
21
22
23 if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
24 then echo "Command succeeded."
25 else echo "Command failed."
26 fi

一个if/then结构可以包含多级比较和tests.

1 if echo "Next *if* is part of the comparison for the first *if*."
2
3   if [[ $comparison = "integer" ]]
4     then (( a < b ))
5   else
6     [[ $a < $b ]]
7   fi
8
9 then
10   echo '$a is less than $b'
11 fi

Example 7-1 什么情况下为真?
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 #  技巧:
 4 #  如果你不确定一个特定的条件如何判断.
 5 #+ 在一个if-test结构中测试它.
 6
 7 echo
 8
 9 echo "Testing \"0\""
10 if [ 0 ]      # zero
11 then
12   echo "0 is true."
13 else
14   echo "0 is false."
15 fi            # 0 is true.
16
17 echo
18
19 echo "Testing \"1\""
20 if [ 1 ]      # one
21 then
22   echo "1 is true."
23 else
24   echo "1 is false."
25 fi            # 1 is true.
26
27 echo
28
29 echo "Testing \"-1\""
30 if [ -1 ]     # -1
31 then
32   echo "-1 is true."
33 else
34   echo "-1 is false."
35 fi            # -1 is true.
36
37 echo
38
39 echo "Testing \"NULL\""
40 if [ ]        # NULL (控状态)
41 then
42   echo "NULL is true."
43 else
44   echo "NULL is false."
45 fi            # NULL is false.
46
47 echo
48
49 echo "Testing \"xyz\""
50 if [ xyz ]    # 字符串
51 then
52   echo "Random string is true."
53 else
54   echo "Random string is false."
55 fi            # Random string is true.
56
57 echo
58
59 echo "Testing \"\$xyz\""
60 if [ $xyz ]   # 测试$xyz是否为null,但是...(明显没人定义么!)
61               # 只不过是一个未定义的变量
62 then
63   echo "Uninitialized variable is true."
64 else
65   echo "Uninitialized variable is false."
66 fi            # Uninitialized variable is false.
67
68 echo
69
70 echo "Testing \"-n \$xyz\""
71 if [ -n "$xyz" ]            # 更学究的的检查
72 then
73   echo "Uninitialized variable is true."
74 else
75   echo "Uninitialized variable is false."
76 fi            # Uninitialized variable is false.
77
78 echo
79
80
81 xyz=          # 初始化了,但是将其设为空值
82
83 echo "Testing \"-n \$xyz\""
84 if [ -n "$xyz" ]
85 then
86   echo "Null variable is true."
87 else
88   echo "Null variable is false."
89 fi            # Null variable is false.
90
91
92 echo
93
94
95 # 什么时候"flase"为true?
96
97 echo "Testing \"false\""
98 if [ "false" ]              #  看起来"false"只不过是个字符串而已.
99 then
100   echo "\"false\" is true." #+ 并且它test的结果就是true.
101 else
102   echo "\"false\" is false."
103 fi            # "false" is true.
104
105 echo
106
107 echo "Testing \"\$false\""  # 再来一个,未声明的变量
108 if [ "$false" ]
109 then
110   echo "\"\$false\" is true."
111 else
112   echo "\"\$false\" is false."
113 fi            # "$false" is false.
114               # 现在我们终于得到了期望的结果
115
116 #  如果我们test这个变量"$true"会发生什么结果?答案是和"$flase"一样,都为空,因为我
117 #+ 们并没有定义它.
118 echo
119
120 exit 0
################################End Script#########################################
练习.解释上边例子的行为(我想我解释的已经够清楚了)

1 if [ condition-true ]
2 then
3    command 1
4    command 2
5    ...
6 else
7    # 可选的(如果不需要可以省去)
8    # 如果原始的条件测试结果是false,那么添加默认的代码来执行.
9    command 3
10    command 4
11    ...
12 fi

注意:当if和then在一个条件测试的同一行中的话,必须使用";"来终止if表达式.if和then都是
   关键字.关键字(或者命令)作为一个表达式的开头,并且在一个新的表达式开始之前,必须
   结束上一个表达式.
   1 if [ -x "$filename" ]; then

Else if和elif

elif
   elif是else if的缩减形式.
        1 if [ condition1 ]
        2 then
        3    command1
        4    command2
        5    command3
        6 elif [ condition2 ]
        7 # Same as else if
        8 then
        9    command4
       10    command5
       11 else
       12    default-command
       13 fi

使用if test condition-true这种形式和if[condition-true]这种形式是等价的.向我们前边
所说的"["是test的标记.并且以"]"结束.在if/test中并不应该这么严厉,但是新版本的Bash
需要它.

注意:test命令是Bash的内建命令,用来测试文件类型和比较字符串.因此,在Bash脚本中,test
并不调用/usr/bin/test的二进制版本(这是sh-utils工具包的一部分).同样的,[并不调用
/usr/bin/[,被连接到/usr/bin/test.
       bash$ type test
       test is a shell builtin
       bash$ type '['
       [ is a shell builtin
       bash$ type '[['
       [[ is a shell keyword
       bash$ type ']]'
       ]] is a shell keyword
       bash$ type ']'
       bash: type: ]: not found

Example 7-2 几个等效命令test,/usr/bin/test,[],和/usr/bin/[
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 echo
 4
 5 if test -z "$1"
 6 then
 7   echo "No command-line arguments."
 8 else
 9   echo "First command-line argument is $1."
10 fi
11
12 echo
13
14 if /usr/bin/test -z "$1"      # 与内建的test结果相同
15 then
16   echo "No command-line arguments."
17 else
18   echo "First command-line argument is $1."
19 fi
20
21 echo
22
23 if [ -z "$1" ]                # 与上边代码的作用相同
24 #   if [ -z "$1"                应该工作,但是...
25 #+  Bash相应一个缺少关闭中括号的错误消息.
26 then
27   echo "No command-line arguments."
28 else
29   echo "First command-line argument is $1."
30 fi
31
32 echo
33
34
35 if /usr/bin/[ -z "$1" ]       # 再来一个,与上边代码的作用相同
36 # if /usr/bin/[ -z "$1"       # 工作,但是给个错误消息
37 #                             # 注意:
38 #                               This has been fixed in Bash, version 3.x.
38 #                               在ver 3.x上,这个bug已经被Bash修正了.
39 then
40   echo "No command-line arguments."
41 else
42   echo "First command-line argument is $1."
43 fi
44
45 echo
46
47 exit 0
###############################End Script#########################################

[[]]结构比Bash的[]更加灵活,这是一个扩展的test命令,从ksh88继承过来的.
注意:在[[]]结构中,将没有文件扩展或者是单词分离,但是会发生参数扩展和命令替换.
   1 file=/etc/passwd
   2
   3 if [[ -e $file ]]
   4 then
   5   echo "Password file exists."
   6 fi
注意:使用[[]],而不是[],能够阻止脚本中的许多逻辑错误.比如,尽管在[]中将给出一个错误,
   但是&&,||,<>操作还是能够工作在一个[[]]test之中.
注意:在if后边,test命令和[]或[[]]都不是必须的.如下:
   1 dir=/home/bozo
   2
   3 if cd "$dir" 2>/dev/null; then   # "2>/dev/null" hides error message.
   4   echo "Now in $dir."
   5 else
   6   echo "Can't change to $dir."
   7 fi
if命令将返回if后边的命令的退出码.

与此相似,当在一个在使用与或列表结构的时候,test或中括号的使用,也并不一定非的有if不可
   1 var1=20
   2 var2=22
   3 [ "$var1" -ne "$var2" ] && echo "$var1 is not equal to $var2"
   4
   5 home=/home/bozo
   6 [ -d "$home" ] || echo "$home directory does not exist."

(())结构扩展并计算一个算术表达式的结果.如果表达式的结果为0,它将返回1作为退出码,或
者是"false".而一个非0表达式的结果将返回0作为退出码,或者是"true".

Example 7-3 算数测试使用(( ))
################################Start Script#######################################
1 #!/bin/bash
2 # 算数测试
3
4 # The (( ... )) construct evaluates and tests numerical expressions.
4 # (( ... ))结构计算并测试算数表达式的结果.
5 # 退出码将与[ ... ]结构相反!
6
7 (( 0 ))
8 echo "Exit status of \"(( 0 ))\" is $?."         # 1
9
10 (( 1 ))
11 echo "Exit status of \"(( 1 ))\" is $?."         # 0
12
13 (( 5 > 4 ))                                      # true
14 echo "Exit status of \"(( 5 > 4 ))\" is $?."     # 0
15
16 (( 5 > 9 ))                                      # false
17 echo "Exit status of \"(( 5 > 9 ))\" is $?."     # 1
18
19 (( 5 - 5 ))                                      # 0
20 echo "Exit status of \"(( 5 - 5 ))\" is $?."     # 1
21
22 (( 5 / 4 ))                                      # 除法也行
23 echo "Exit status of \"(( 5 / 4 ))\" is $?."     # 0
24
25 (( 1 / 2 ))                                      # 出发结果<1
26 echo "Exit status of \"(( 1 / 2 ))\" is $?."     # 结果将为0
27                                                  # 1
28
29 (( 1 / 0 )) 2>/dev/null                          # 除数为0的错误
30 #           ^^^^^^^^^^^
31 echo "Exit status of \"(( 1 / 0 ))\" is $?."     # 1
32
33 # What effect does the "2>/dev/null" have?
33 # "2>/dev/null"的作用是什么?
34 # 如果删除"2>dev/null"将会发生什么?
35 # Try removing it, then rerunning the script.
35 # 尝试删除它,然后再运行脚本.
36
37 exit 0
################################End Script#########################################


7.2 文件测试操作
----------------
返回true如果...

-e        文件存在
-a        文件存在
       这个选项的效果与-e相同.但是它已经被弃用了,并且不鼓励使用
-f        file是一个regular文件(不是目录或者设备文件)
-s        文件长度不为0
-d        文件是个目录
-b        文件是个块设备(软盘,cdrom等等)
-c        文件是个字符设备(键盘,modem,声卡等等)
-p        文件是个管道
-h        文件是个符号链接
-L        文件是个符号链接
-S        文件是个socket
-t        关联到一个终端设备的文件描述符
       这个选项一般都用来检测是否在一个给定脚本中的stdin[-t0]或[-t1]是一个终端
-r        文件具有读权限(对于用户运行这个test)
-w        文件具有写权限(对于用户运行这个test)
-x        文件具有执行权限(对于用户运行这个test)
-g        set-group-id(sgid)标志到文件或目录上
       如果一个目录具有sgid标志,那么一个被创建在这个目录里的文件,这个目录属于创建
       这个目录的用户组,并不一定与创建这个文件的用户的组相同.对于workgroup的目录
       共享来说,这非常有用.见<>第58页.
-u        set-user-id(suid)标志到文件上
       如果运行一个具有root权限的文件,那么运行进程将取得root权限,即使你是一个普通
       用户.[1]这对于需要存取系统硬件的执行操作(比如pppd和cdrecord)非常有用.如果
       没有suid标志的话,那么普通用户(没有root权限)将无法运行这种程序.
       见<>第58页.
          -rwsr-xr-t    1 root       178236 Oct  2  2000 /usr/sbin/pppd
       对于设置了suid的文件,在它的权限标志中有"s".
-k        设置粘贴位,见<>第65页.
       对于"sticky bit",save-text-mode标志是一个文件权限的特殊类型.如果设置了这
       个标志,那么这个文件将被保存在交换区,为了达到快速存取的目的.如果设置在目录
       中,它将限制写权限.对于设置了sticky bit位的文件或目录,权限标志中有"t".
          drwxrwxrwt    7 root         1024 May 19 21:26 tmp/
       如果一个用户并不时具有stick bit位的目录的拥有者,但是具有写权限,那么用户只
       能在这个目录下删除自己所拥有的文件.这将防止用户在一个公开的目录中不慎覆盖
       或者删除别人的文件,比如/tmp(当然root或者是目录的所有者可以随便删除或重命名
       其中的文件).
-O        你是文件的所有者.
-G        文件的group-id和你的相同.
-N        从文件最后被阅读到现在,是否被修改.

f1 -nt f2
       文件f1比f2新
f1 -ot f2
       f1比f2老
f1 -ef f2
       f1和f2都硬连接到同一个文件.

!        非--反转上边测试的结果(如果条件缺席,将返回true)

Example 7-4 test死的链接文件
################################Start Script#######################################
1 #!/bin/bash
2 # broken-link.sh
3 # Written by Lee bigelow
4 # Used with permission.
5
6 #一个真正有用的shell脚本来找出死链接文件并且输出它们的引用
7 #以便于它们可以被输入到xargs命令中进行处理 :)
8 #比如: broken-link.sh /somedir /someotherdir|xargs rm
9 #
10 #这里,不管怎么说,是一种更好的方法
11 #
12 #find "somedir" -type l -print0|\
13 #xargs -r0 file|\
14 #grep "broken symbolic"|
15 #sed -e 's/^\|: *broken symbolic.*$/"/g'
16 #
17 #但这不是一个纯粹的bash,最起码现在不是.
18 #小心:小心/proc文件系统和任何的循环链接文件.
19 ##############################################################
20
21
22 #如果没对这个脚本传递参数,那么就使用当前目录.
23 #否则就使用传递进来的参数作为目录来搜索.
24 #
25 ####################
26 [ $# -eq 0 ] && directorys=`pwd` || directorys=$@
27
28 #建立函数linkchk来检查传进来的目录或文件是否是链接和是否存在,
29 #并且打印出它们的引用
30 #如果传进来的目录有子目录,
31 #那么把子目录也发送到linkchk函数中处理,就是递归目录.
32 ##########
33 linkchk () {
34     for element in $1/*; do
35     [ -h "$element" -a ! -e "$element" ] && echo \"$element\"
36     [ -d "$element" ] && linkchk $element
37     # Of course, '-h' tests for symbolic link, '-d' for directory.
37     # 当然'-h'是测试链接,'-d'是测试目录.
38     done
39 }
40
41 #如果是个可用目录,那就把每个从脚本传递进来的参数都送到linkche函数中.
42 #如果不是,那就打印出错误消息和使用信息.
43 #
44 ################
45 for directory in $directorys; do
46     if [ -d $directory ]
47     then linkchk $directory
48     else
49         echo "$directory is not a directory"
50         echo "Usage: $0 dir1 dir2 ..."
51     fi
52 done
53
54 exit 0
################################End Script#########################################
Example 28-1, Example 10-7, Example 10-3, Example 28-3, 和Example A-1 也会说明文件
测试操作的使用过程.

注意事项:
[1]        小心suid,可能引起安全漏洞,但是不会影响shell脚本.
[2]        在当代UNIX系统中,已经不使用sticky bit了,只在目录中使用.


7.3 其他比较操作
----------------
二元比较操作符,比较变量或者比较数字.注意数字与字符串的区别.

整数比较

-eq        等于,如:if [ "$a" -eq "$b" ]
-ne        不等于,如:if [ "$a" -ne "$b" ]
-gt        大于,如:if [ "$a" -gt "$b" ]
-ge        大于等于,如:if [ "$a" -ge "$b" ]
-lt        小于,如:if [ "$a" -lt "$b" ]
-le        小于等于,如:if [ "$a" -le "$b" ]
<        小于(需要双括号),如:(("$a" < "$b"))
<=        小于等于(需要双括号),如:(("$a" <= "$b"))
>        大于(需要双括号),如:(("$a" > "$b"))
>=        大于等于(需要双括号),如:(("$a" >= "$b"))

字符串比较
=        等于,如:if [ "$a" = "$b" ]
==        等于,如:if [ "$a" == "$b" ],与=等价
       注意:==的功能在[[]]和[]中的行为是不同的,如下:
       1 [[ $a == z* ]]    # 如果$a以"z"开头(模式匹配)那么将为true
       2 [[ $a == "z*" ]]  # 如果$a等于z*(字符匹配),那么结果为true
       3
       4 [ $a == z* ]      # File globbing 和word splitting将会发生
       5 [ "$a" == "z*" ]  # 如果$a等于z*(字符匹配),那么结果为true
       一点解释,关于File globbing是一种关于文件的速记法,比如"*.c"就是,再如~也是.
       但是file globbing并不是严格的正则表达式,虽然绝大多数情况下结构比较像.
!=        不等于,如:if [ "$a" != "$b" ]
       这个操作符将在[[]]结构中使用模式匹配.
<        小于,在ASCII字母顺序下.如:
       if [[ "$a" < "$b" ]]
       if [ "$a" \< "$b" ]
       注意:在[]结构中"<"需要被转义.
>        大于,在ASCII字母顺序下.如:
       if [[ "$a" > "$b" ]]
       if [ "$a" \> "$b" ]
       注意:在[]结构中">"需要被转义.
       具体参考Example 26-11来查看这个操作符应用的例子.
-z        字符串为"null".就是长度为0.
-n        字符串不为"null"
       注意:
       使用-n在[]结构中测试必须要用""把变量引起来.使用一个未被""的字符串来使用! -z
       或者就是未用""引用的字符串本身,放到[]结构中(见Example 7-6)虽然一般情况下可
       以工作,但这是不安全的.习惯于使用""来测试字符串是一种好习惯.[1]

Example 7-5 数字和字符串比较
################################Start Script#######################################
1 #!/bin/bash
2
3 a=4
4 b=5
5
6 #  这里的变量a和b既可以当作整型也可以当作是字符串.
7 #  这里在算术比较和字符串比较之间有些混淆,
8 #+ 因为Bash变量并不是强类型的.
9
10 #  Bash允许对整型变量操作和比较
11 #+ 当然变量中只包含数字字符.
12 #  但是还是要考虑清楚再做.
13
14 echo
15
16 if [ "$a" -ne "$b" ]
17 then
18   echo "$a is not equal to $b"
19   echo "(arithmetic comparison)"
20 fi
21
22 echo
23
24 if [ "$a" != "$b" ]
25 then
26   echo "$a is not equal to $b."
27   echo "(string comparison)"
28   #     "4"  != "5"
29   # ASCII 52 != ASCII 53
30 fi
31
32 # 在这个特定的例子中,"-ne"和"!="都可以.
33
34 echo
35
36 exit 0
################################End Script#########################################

Example 7-6 测试字符串是否为null
################################Start Script#######################################
1 #!/bin/bash
2 #  str-test.sh: 测试null字符串和非引用字符串,
3 #+ but not strings and sealing wax, not to mention cabbages and kings . . .
4 #+ 上边这句没看懂
5 # Using   if [ ... ]
6
7
8 # 如果一个字符串没被初始化,那么它就没有定义的值(像这种话,总感觉像屁话)
9 # 这种状态叫做"null"(与zero不同)
10
11 if [ -n $string1 ]    # $string1 没被声明和初始化
12 then
13   echo "String \"string1\" is not null."
14 else  
15   echo "String \"string1\" is null."
16 fi  
17 # 错误的结果.
18 # 显示$string1为非空,虽然他没被初始化.
19
20
21 echo
22
23
24 # 让我们再试一下.
25
26 if [ -n "$string1" ]  # 这次$string1被引用了.
27 then
28   echo "String \"string1\" is not null."
29 else  
30   echo "String \"string1\" is null."
31 fi                    # ""的字符串在[]结构中
32
33
34 echo
35
36
37 if [ $string1 ]       # 这次$string1变成"裸体"的了
38 then
39   echo "String \"string1\" is not null."
40 else  
41   echo "String \"string1\" is null."
42 fi  
43 # 这工作得很好.
44 # 这个[]test操作检测string是否为null.
45 # 然而,使用("$string1")是一种很好的习惯
46 #
47 # As Stephane Chazelas points out,
48 #    if [ $string1 ]    有1个参数 "]"
49 #    if [ "$string1" ]  有2个参数,空的"$string1"和"]"
50
51
52
53 echo
54
55
56
57 string1=initialized
58
59 if [ $string1 ]       # 再来,$string1"裸体了"
60 then
61   echo "String \"string1\" is not null."
62 else  
63   echo "String \"string1\" is null."
64 fi  
65 # 再来,给出了正确的结果.
66 # 不过怎么说("$string1")还是好很多,因为. . .
67
68
69 string1="a = b"
70
71 if [ $string1 ]       # 再来,$string1 再次裸体了.
72 then
73   echo "String \"string1\" is not null."
74 else  
75   echo "String \"string1\" is null."
76 fi  
77 # 非引用的"$string1"现在给出了一个错误的结果!
78
79 exit 0
80 # Thank you, also, Florian Wisser, for the "heads-up".
################################End Script#########################################

Example 7-7 zmore
################################Start Script#######################################
1 #!/bin/bash
2 # zmore
3
4 #使用'more'来查看gzip文件
5
6 NOARGS=65
7 NOTFOUND=66
8 NOTGZIP=67
9
10 if [ $# -eq 0 ] # 与 if [ -z "$1" ]同样的效果
11 # 应该是说前边的那句注释有问题,$1是可以存在的,比如:zmore "" arg2 arg3
12 then
13   echo "Usage: `basename $0` filename" >&2
14   # 错误消息到stderr
15   exit $NOARGS
16   # 脚本返回65作为退出码.
17 fi  
18
19 filename=$1
20
21 if [ ! -f "$filename" ]   # 将$filename ""起来,来允许可能的空白
22 then
23   echo "File $filename not found!" >&2
24    # 错误消息到stderr
25   exit $NOTFOUND
26 fi  
27
28 if [ ${filename##*.} != "gz" ]
29 # 在变量替换中使用中括号
30 then
31   echo "File $1 is not a gzipped file!"
32   exit $NOTGZIP
33 fi  
34
35 zcat $1 | more
36
37 # 使用过滤命令'more'
38 # 如果你想的话也可使用'less'
39
40
41 exit $?   # 脚本将返回pipe的结果作为退出码
42 # 事实上,不用非的有"exit $?",但是不管怎么说,有了这句,能正规一些
43 # 将最后一句命令的执行状态作为退出码返回
################################End Script#########################################

混合比较

-a        逻辑与
       exp1 -a exp2    如果exp1和exp2都为true的话,这个表达式将返回true

-o        逻辑或
       exp1 -o exp2    如果exp1和exp2中有一个为true的话,那么这个表达式就返回true

这与Bash的比较操作符&&和||很相像.在[[]]中使用它.
       1 [[ condition1 && condition2 ]]
-o和-a一般都是和test命令或者是[]一起工作.
       1 if [ "$exp1" -a "$exp2" ]

请参考Example 8-3,Example 26-16和Example A-28来查看混合比较操作的行为.

注意事项:
[1]        S.C.(这家伙是个人名)指出,在使用混合比较的时候即使"$var"也可能会产生问题.
       如果$string为空的话,[ -n "$string" -o "$a" = "$b" ]可能在某些版本的Bash中
       会有问题.为了附加一个额外的字符到可能的空变量中的一种安全的办法是,
       [ "x$string" != x -o "x$a" = "x$b" ](the "x's" cancel out)(没看懂).
       cancel out是抵消的意思.


7.4 嵌套的if/then条件test
-------------------------
可以使用if/then来进行嵌套的条件test.最终的结果和上边的使用&&混合比较操作是相同的.
   1 if [ condition1 ]
   2 then
   3   if [ condition2 ]
   4   then
   5     do-something  # 这里只有在condition1和condition2都可用的时候才行.
   6   fi  
   7 fi
具体请查看Example 34-4.


7.5 检查你的test知识
--------------------
系统范围的xinitrc文件可以用来启动X server.这个文件中包含了相当多的if/then test,
就像下边的节选一样:
1 if [ -f $HOME/.Xclients ]; then
2   exec $HOME/.Xclients
3 elif [ -f /etc/X11/xinit/Xclients ]; then
4   exec /etc/X11/xinit/Xclients
5 else
6      # 故障保险设置,虽然我们永远都不会走到这来.
7      # (我们在Xclients中也提供了相同的机制)它不会受伤的.
8      xclock -geometry 100x100-5+5 &
9      xterm -geometry 80x50-50+150 &
10      if [ -f /usr/bin/netscape -a -f /usr/share/doc/HTML/index.html ]; then
11              netscape /usr/share/doc/HTML/index.html &
12      fi
13 fi

对上边的"test"结构进行解释,然后检查整个文件,/etc/X11/xinit/xinitrc,并分析if/then
test结构.你可能需要查看一下后边才能讲解到的grep,sed和正则表达式的知识.



第8章 操作符和相关的主题
========================

8.1 操作符
----------

等号操作符

变量赋值
   初始化或者修改变量的值
=
   无论在算术运算还是字符串运算中,都是赋值语句.
   1 var=27
   2 category=minerals  # No spaces allowed after the "=".

   注意:不要和"="test操作符混淆.
       1 #    = as a test operator
       2
       3 if [ "$string1" = "$string2" ]
       4 # if [ "X$string1" = "X$string2" ] is safer,
       5 # to prevent an error message should one of the variables be empty.
       6 # (The prepended "X" characters cancel out.)
       7 then
       8    command
       9 fi

算术操作符

+        加法
-        减法
*        乘法
/        除法
**        幂运算
       1 # Bash, version 2.02, introduced the "**" exponentiation operator.
       2
       3 let "z=5**3"
       4 echo "z = $z"   # z = 125
%        取模
       bash$ expr 5 % 3
       2

       5/3=1余2
       模运算经常用在其它的事情中,比如产生特定的范围的数字(Example 9-24,
       Example 9-27)和格式化程序的输出(Example 26-15,Example A-6).它甚至可以用来
       产生质数,(Example A-16).事实上取模运算在算术运算中使用的频率惊人的高.

Example 8-1 最大公约数
################################Start Script#######################################
1 #!/bin/bash
2 # xxx.sh: 最大公约数
3 #         使用Euclid's 算法
4
5 #  最大公约数,就是2个数能够同时整除的最大的数.
6 #
7
8 #  Euclid's算法采用连续除法.
9 #  在每个循环中
10 #+ 被除数 <---  除数
11 #+ 除数 <---  余数
12 #+ 直到余数= 0.
13 #+ 在最后的循环中The xxx = 被除数
14 #
15 #  关于这个算法更精彩的讨论
16 #  见Jim Loy's site,
17
18
19 # ------------------------------------------------------
20 # 参数检查
21 ARGS=2
22 E_BADARGS=65
23
24 if [ $# -ne "$ARGS" ]
25 then
26   echo "Usage: `basename $0` first-number second-number"
27   exit $E_BADARGS
28 fi
29 # ------------------------------------------------------
30
31
32 xxx ()
33 {
34
35   dividend=$1                    #  随便给值
36   divisor=$2                     #+ 即使$2大,也没关系.
37                                  #  Why not?
38
39   remainder=1                    #  如果再循环中使用为初始化的变量.
40                                  #+ 那将在第一次循环中产生一个错误消息.
41                                  
42
43   until [ "$remainder" -eq 0 ]
44   do
45     let "remainder = $dividend % $divisor"
46     dividend=$divisor            # 现在使用2个最小的数重复.
47     divisor=$remainder
48   done                           # Euclid's algorithm
49
50 }                                # Last $dividend is the xxx.
50 }                                # 最后的$dividend就是xxx.
51
52
53 xxx$1 $2
54
55 echo; echo "XXX of $1 and $2 = $dividend"; echo
56
57
58 # 练习:
59 # --------
60 #  检查命令行参数来确定它们都是整数,
61 #+ and exit the script with an appropriate error message if not.
61 #+ 否则就选择合适的错误消息退出.
62
63 exit 0
################################End Script#########################################

+=        加等于(通过常量增加变量)
       let "var += 5"        #var将在本身值的基础上增加5
-=        减等于
*=        乘等于
       let "var *= 4"
/=        除等于
%=        取模赋值,算术操作经常使用expr或者let表达式.

Example 8-2 使用算术操作符
################################Start Script#######################################
1 #!/bin/bash
2 # Counting to 11 in 10 different ways.
3
4 n=1; echo -n "$n "
5
6 let "n = $n + 1"   # let "n = n + 1"  这么写也行
7 echo -n "$n "
8
9
10 : $((n = $n + 1))
11 #  ":" 是必须的,这是因为,如果没有":"的话,Bash将
12 #+ 尝试把"$((n = $n + 1))"解释成一个命令
13 echo -n "$n "
14
15 (( n = n + 1 ))
16 #  对于上边的方法的一个更简单的选则.
17 #  Thanks, David Lombard, for pointing this out.
18 echo -n "$n "
19
20 n=$(($n + 1))
21 echo -n "$n "
22
23 : $[ n = $n + 1 ]
24 #  ":" 是必须的,这是因为,如果没有":"的话,Bash将
25 #+ 尝试把"$[ n = $n + 1 ]" 解释成一个命令
26 #  即使"n"被初始化成为一个字符串,这句也能工作.
27 echo -n "$n "
28
29 n=$[ $n + 1 ]
30 #  即使"n"被初始化成为一个字符串,这句也能工作.
31 #* Avoid this type of construct, since it is obsolete and nonportable.
31 #* 尽量避免这种类型的结果,因为这已经被废弃了,并且不具可移植性.
32 #  Thanks, Stephane Chazelas.
33 echo -n "$n "
34
35 # 现在来个C风格的增量操作.
36 # Thanks, Frank Wang, for pointing this out.
37
38 let "n++"          # let "++n"  also works.
39 echo -n "$n "
40
41 (( n++ ))          # (( ++n )  also works.
42 echo -n "$n "
43
44 : $(( n++ ))       # : $(( ++n )) also works.
45 echo -n "$n "
46
47 : $[ n++ ]         # : $[ ++n ]] also works
48 echo -n "$n "
49
50 echo
51
52 exit 0
################################End Script#########################################

注意:在Bash中的整型变量事实上是32位的,范围是 -2147483648 到2147483647.如果超过这个
范围进行算术操作,将不会得到你期望的结果(就是溢出么).
   1 a=2147483646
   2 echo "a = $a"      # a = 2147483646
   3 let "a+=1"         # 加1 "a".
   4 echo "a = $a"      # a = 2147483647
   5 let "a+=1"         # 再加1 "a" ,将超过上限了.
   6 echo "a = $a"      # a = -2147483648
   7                    #      错误 (溢出了)
   在Bash 2.05b版本中,Bash支持64位整型了.

注意:Bash并不能理解浮点运算.它把包含的小数点看作字符串.
   1 a=1.5
   2
   3 let "b = $a + 1.3"  # 错误.
   4 # t2.sh: let: b = 1.5 + 1.3: 表达式的语义错误(错误标志为".5 + 1.3")
   5
   6 echo "b = $b"       # b=1
   如果真想做浮点运算的话,使用bc(见12.8节),bc可以进行浮点运算或调用数学库函数.

位操作符.
   (晕,有点强大过分了吧,位级操作都支持.)
   位操作符在shell脚本中极少使用.它们最主要的用途看起来就是操作和test从sockets中
   读出的变量."Bit flipping"与编译语言的联系很紧密,比如c/c++,在这种语言中它可以
   运行得足够快.(原文有处on the fly,我查了一下,好像是没事干的意思,没理解)

<<        左移1位(每次左移都将乘2)

<<=        左移几位,=号后边将给出左移几位
       let "var <<= 2"就是左移2位(就是乘4)

>>        右移1位(每次右移都将除2)

>>=        右移几位

&        按位与

&=        按位与赋值

|        按位或

|=        按位或赋值

~        按位非

!        按位否?(没理解和上边的~有什么区别?),感觉是应该放到下边的逻辑操作中

^        按位异或XOR

^=        异或赋值


逻辑操作:

&&        逻辑与
       1 if [ $condition1 ] && [ $condition2 ]
       2 # 与:  if [ $condition1 -a $condition2 ] 相同
       3 # 如果condition1和condition2都为true,那结果就为true.
       4
       5 if [[ $condition1 && $condition2 ]]    # 也可以.
       6 # 注意&&不允许出现在[ ... ]中.
       注意:&&也可以用在and list中(见25章),但是使用的时候需要依赖上下文.

||        逻辑或
       1 if [ $condition1 ] || [ $condition2 ]
       2 # 与:  if [ $condition1 -o $condition2 ] 相同
       3 # 如果condition1或condition2为true,那结果就为true.
       4
       5 if [[ $condition1 || $condition2 ]]    # 也可以
       6 # 注意||不允许出现在[ ... ]中.
       注意:Bash将test每个连接到逻辑操作的状态的退出状态(见第6章).


Example 8-3 使用&&和||进行混合状态的test
################################Start Script#######################################
1 #!/bin/bash
2
3 a=24
4 b=47
5
6 if [ "$a" -eq 24 ] && [ "$b" -eq 47 ]
7 then
8   echo "Test #1 succeeds."
9 else
10   echo "Test #1 fails."
11 fi
12
13 # 错误:   if [ "$a" -eq 24 && "$b" -eq 47 ]
14 #+         尝试执行' [ "$a" -eq 24 '
15 #+         因为没找到']'所以失败了.
16 #
17 #  注意:  如果 [[ $a -eq 24 && $b -eq 24 ]]  能够工作.
18 #  那这个[[]]的test结构就比[]结构更灵活了.
19 #
20 #    (在17行的"&&"与第6行的"&&"意义不同)
21 #    Thanks, Stephane Chazelas, for pointing this out.
22
23
24 if [ "$a" -eq 98 ] || [ "$b" -eq 47 ]
25 then
26   echo "Test #2 succeeds."
27 else
28   echo "Test #2 fails."
29 fi
30
31
32 #  -a和-o选项提供了
33 #+ 一种可选的混合test方法.
34 #  Thanks to Patrick Callahan for pointing this out.
35
36
37 if [ "$a" -eq 24 -a "$b" -eq 47 ]
38 then
39   echo "Test #3 succeeds."
40 else
41   echo "Test #3 fails."
42 fi
43
44
45 if [ "$a" -eq 98 -o "$b" -eq 47 ]
46 then
47   echo "Test #4 succeeds."
48 else
49   echo "Test #4 fails."
50 fi
51
52
53 a=rhino
54 b=crocodile
55 if [ "$a" = rhino ] && [ "$b" = crocodile ]
56 then
57   echo "Test #5 succeeds."
58 else
59   echo "Test #5 fails."
60 fi
61
62 exit 0
################################End Script#########################################
   &&和||操作也能在算术运算的上下文中找到.
       bash$ echo $(( 1 && 2 )) $((3 && 0)) $((4 || 0)) $((0 || 0))
       1 0 1 0

混杂操作:
,        逗号操作符
       逗号操作符可以连接2个或多个算术运算.所有的操作都会被执行,但是只有最后一个
       操作作为结果.
       1 let "t1 = ((5 + 3, 7 - 1, 15 - 4))"
       2 echo "t1 = $t1"               # t1 = 11
       3
       4 let "t2 = ((a = 9, 15 / 3))"  # Set "a" and calculate "t2".
       5 echo "t2 = $t2    a = $a"     # t2 = 5    a = 9
       ","主要用在for循环中,具体见Example 10-12.


8.2 数字常量
------------
shell脚本默认都是将数字作为10进制数处理,除非这个数字某种特殊的标记法或前缀开头.
以0开头就是8进制.以0x开头就是16进制数.使用BASE#NUMBER这种形式可以表示其它进制
表示法

Example 8-4 数字常量的处理
################################Start Script#######################################
1 #!/bin/bash
2 # numbers.sh: 数字常量的几种不同的表示法
3
4 # 10进制: 默认
5 let "dec = 32"
6 echo "decimal number = $dec"             # 32
7 # 一切都很正常
8
9
10 # 8进制: 以'0'(零)开头
11 let "oct = 032"
12 echo "octal number = $oct"               # 26
13 # 表达式的结果用10进制表示.
14 #
15
16 # 16进制表示:数字以'0x'或者'0X'开头
17 let "hex = 0x32"
18 echo "hexadecimal number = $hex"         # 50
19 # 表达式的结果用10进制表示.
20
21 # 其它进制: BASE#NUMBER
22 # BASE between 2 and 64.
22 # 2到64进制都可以.
23 # NUMBER必须在BASE的范围内,具体见下边.
24
25
26 let "bin = 2#111100111001101"
27 echo "binary number = $bin"              # 31181
28
29 let "b32 = 32#77"
30 echo "base-32 number = $b32"             # 231
31
32 let "b64 = 64#@_"
33 echo "base-64 number = $b64"             # 4031
34 # 这种64进制的表示法中的每位数字都必须在64进制表示法的限制字符内.
35 # 10 个数字+ 26 个小写字母+ 26 个大写字母+ @ + _
36
37
38 echo
39
40 echo $((36#zz)) $((2#10101010)) $((16#AF16)) $((53#1aA))
41                                          # 1295 170 44822 3375
42
43
44 #  重要的注意事项:
45 #  ---------------
46 #  如果使用的每位数字超出了这个进制表示法规定字符的范围的话,
47 #+ 将给出一个错误消息.
48
49 let "bad_oct = 081"
50 # (部分的) 错误消息输出:
51 #  bad_oct = 081: too great for base (error token is "081")
52 #              Octal numbers use only digits in the range 0 - 7.
53
54 exit 0       # Thanks, Rich Bartell and Stephane Chazelas, for clarification.
################################End Script#########################################



第三部分    超越基本
++++++++++++++++++++


第9章    变量重游
================
如果变量使用恰当,将会增加脚本的能量和灵活性.但是前提是这需要仔细学习变量的细节知识.


9.1 内部变量
------------
Builtin variable
   这些内建的变量,将影响bash脚本的行为.    

   $BASH
       这个变量将指向Bash的二进制执行文件的位置.
       bash$ echo $BASH
       /bin/bash

   $BASH_ENV
       这个环境变量将指向一个Bash启动文件,这个启动文件将在调用一个脚本时被读取.

   $BASH_SUBSHELL
       这个变量将提醒subshell的层次,这是一个在version3才被添加到Bash中的新特性.
       见Example 20-1.

   $BASH_VERSINFO[n]
       记录Bash安装信息的一个6元素的数组.与下边的$BASH_VERSION很像,但这个更加详细.
        1 # Bash version info:
        2
        3 for n in 0 1 2 3 4 5
        4 do
        5   echo "BASH_VERSINFO[$n] = ${BASH_VERSINFO[$n]}"
        6 done  
        7
        8 # BASH_VERSINFO[0] = 3                      # 主版本号
        9 # BASH_VERSINFO[1] = 00                     # 次版本号
       10 # BASH_VERSINFO[2] = 14                     # Patch 次数.
       11 # BASH_VERSINFO[3] = 1                      # Build version.
       12 # BASH_VERSINFO[4] = release                # Release status.
       13 # BASH_VERSINFO[5] = i386-redhat-linux-gnu  # Architecture

   $BASH_VERSION
       安装在系统上的Bash的版本号.
       bash$ echo $BASH_VERSION
       3.00.14(1)-release
       tcsh% echo $BASH_VERSION
       BASH_VERSION: Undefined variable.
       使用这个变量对于判断系统上到底运行的是那个shll来说是一种非常好的办法.$SHELL
       有时将不能给出正确的答案.

   $DIRSTACK
       在目录栈中最上边的值(将受到pushd和popd的影响).
       这个内建的变量与dirs命令是保持一致的,但是dirs命令将显示目录栈的整个内容.

   $EDITOR
       脚本调用的默认编辑器,一般是vi或者是emacs.

   $EUID
       "effective"用户ID号.
       当前用户被假定的任何id号.可能在su命令中使用.
       注意:$EUID并不一定与$UID相同.

   $FUNCNAME
       当前函数的名字.
       1 xyz23 ()
       2 {
       3   echo "$FUNCNAME now executing."  # xyz23 现在正在被执行.
       4 }
       5
       6 xyz23
       7
       8 echo "FUNCNAME = $FUNCNAME"        # FUNCNAME =
       9                                    # 出了函数就变为Null值了.

   $GLOBIGNORE
       一个文件名的模式匹配列表,如果在file globbing中匹配到的文件包含这个列表中的
       某个文件,那么这个文件将被从匹配到的文件中去掉.

   $GROUPS
       当前用户属于的组.
       这是一个当前用户的组id列表(数组),就像在/etc/passwd中记录的一样.
       root# echo $GROUPS
       0

       root# echo ${GROUPS[1]}
       1

       root# echo ${GROUPS[5]}
       6

   $HOME
       用户的home目录,一般都是/home/username(见Example 9-14)

   $HOSTNAME
       hostname命令将在一个init脚本中,在启动的时候分配一个系统名字.
       gethostname()函数将用来设置这个$HOSTNAME内部变量.(见Example 9-14)

   $HOSTTYPE
       主机类型
       就像$MACHTYPE,识别系统的硬件.
       bash$ echo $HOSTTYPE
       i686

   $IFS
       内部域分隔符.
       这个变量用来决定Bash在解释字符串时如何识别域,或者单词边界.
       $IFS默认为空白(空格,tab,和新行),但可以修改,比如在分析逗号分隔的数据文件时.
       注意:$*使用$IFS中的第一个字符,具体见Example 5-1.
       bash$ echo $IFS | cat -vte
       $

       bash$ bash -c 'set w x y z; IFS=":-;"; echo "$*"'
       w:x:y:o
       
       注意:$IFS并不像它处理其它字符一样处理空白.

Example 9-1 $IFS和空白
################################Start Script#######################################
1 #!/bin/bash
2 # $IFS 处理空白的方法,与处理其它字符不同.
3
4 output_args_one_per_line()
5 {
6   for arg
7   do echo "[$arg]"
8   done
9 }
10
11 echo; echo "IFS=\" \""
12 echo "-------"
13
14 IFS=" "
15 var=" a  b c   "
16 output_args_one_per_line $var  # output_args_one_per_line `echo " a  b c   "`
17 #
18 # [a]
19 # [b]
20 # [c]
21
22
23 echo; echo "IFS=:"
24 echo "-----"
25
26 IFS=:
27 var=":a::b:c:::"               # 与上边的一样,但是用" "替换了":"
28 output_args_one_per_line $var
29 #
30 # []
31 # [a]
32 # []
33 # [b]
34 # [c]
35 # []
36 # []
37 # []
38
39 # 同样的事情也会发生在awk中的"FS"域分隔符.
40
41 # Thank you, Stephane Chazelas.
42
43 echo
44
45 exit 0
################################End Script#########################################
       Example 12-37也是使用$IFS的另一个启发性的例子.

   $IGNOREEOF
       忽略EOF: 告诉shell在log out之前要忽略多少文件结束符(control-D).

   $LC_COLLATE
       常在.bashrc或/etc/profile中设置,这个变量用来在文件名扩展和模式匹配校对顺序.
       如果$LC_COLLATE被错误的设置,那么将会在filename globbing中引起错误的结果.
       
       注意:在2.05以后的Bash版本中,filename globbing将不在对[]中的字符区分大小写.
           比如:ls [A-M]* 将即匹配File1.txt也会匹配file1.txt.为了恢复[]的习惯用法,
           设置$LC_COLLATE的值为c,使用export LC_COLLATE=c 在/etc/profile或者是
           ~/.bashrc中.

   $LC_CTYPE
       这个内部变量用来控制globbing和模式匹配的字符串解释.

   $LINENO
       这个变量记录它所在的shell脚本中它所在行的行号.这个变量一般用于调试目的.
       1 # *** BEGIN DEBUG BLOCK ***
       2 last_cmd_arg=$_  # Save it.
       3
       4 echo "At line number $LINENO, variable \"v1\" = $v1"
       5 echo "Last command argument processed = $last_cmd_arg"
       6 # *** END DEBUG BLOCK ***

   $MACHTYPE
       系统类型
       提示系统硬件
       bash$ echo $MACHTYPE
       i686

   $OLDPWD
       老的工作目录("OLD-print-working-directory",你所在的之前的目录)

   $OSTYPE
       操作系统类型.
       bash$ echo $OSTYPE
       linux

   $PATH
       指向Bash外部命令所在的位置,一般为/usr/bin,/usr/X11R6/bin,/usr/local/bin等.
       当给出一个命令时,Bash将自动对$PATH中的目录做一张hash表.$PATH中以":"分隔的
       目录列表将被存储在环境变量中.一般的,系统存储的$PATH定义在/ect/processed或
       ~/.bashrc中(见Appendix G).

       bash$ echo $PATH
       /bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin:/sbin:/usr/sbin

       PATH=${PATH}:/opt/bin将把/opt/bin目录附加到$PATH变量中.在脚本中,这是一个
       添加目录到$PATH中的便捷方法.这样在这个脚本退出的时候,$PATH将会恢复(因为这个
       shell是个子进程,像这样的一个脚本是不会将它的父进程的环境变量修改的)

       注意:当前的工作目录"./"一般都在$PATH中被省去.

   $PIPESTATUS
       数组变量将保存最后一个运行的前台管道的退出码.有趣的是,这个退出码和最后一个命令
       运行的退出码并不一定相同.
       bash$ echo $PIPESTATUS
       0

       bash$ ls -al | bogus_command
       bash: bogus_command: command not found
       bash$ echo $PIPESTATUS
       141

       bash$ ls -al | bogus_command
       bash: bogus_command: command not found
       bash$ echo $?
       127

       $PIPESTATUS数组的每个成员都会保存一个管道命令的退出码,$PIPESTATUS[0]保存第
       一个管道命令的退出码,$PIPESTATUS[1]保存第2个,以此类推.

       注意:$PIPESTATUS变量在一个login shell中可能会包含一个错误的0值(3.0以下版本)
       tcsh% bash

       bash$ who | grep nobody | sort
       bash$ echo ${PIPESTATUS[*]}
       0
       包含在脚本中的上边这行将会产生一个期望的输出0 1 0.

       注意:在某些上下文$PIPESTATUS可能不会给出正确的结果.
       bash$ echo $BASH_VERSION
       3.00.14(1)-release

       bash$ $ ls | bogus_command | wc
       bash: bogus_command: command not found
       0       0       0

       bash$ echo ${PIPESTATUS[@]}
       141 127 0
       
       Chet Ramey把上边输出不成确原因归咎于ls的行为.因为如果把ls的结果放到管道上,
       并且这个输出没被读取,那么SIGPIPE将会kill掉它,并且退出码变为141,而不是我们期
       望的0.这种情况也会发生在tr命令中.

       注意:$PIPESTATUS是一个"volatile"变量.在任何命令插入之前,并且在pipe询问之后,
       这个变量需要立即被捕捉.
       bash$ $ ls | bogus_command | wc
       bash: bogus_command: command not found
       0       0       0

       bash$ echo ${PIPESTATUS[@]}
       0 127 0

       bash$ echo ${PIPESTATUS[@]}
       0

   $PPID
       一个进程的$PPID就是它的父进程的进程id(pid).[1]
       使用pidof命令对比一下.

   $PROMPT_COMMAND
       这个变量保存一个在主提示符($PS1)显示之前需要执行的命令.

   $PS1
       主提示符,具体见命令行上的显示.

   $PS2
       第2提示符,当你需要额外的输入的时候将会显示,默认为">".

   $PS3
       第3提示符,在一个select循环中显示(见Example 10-29).

   $PS4
       第4提示符,当使用-x选项调用脚本时,这个提示符将出现在每行的输出前边.
       默认为"+".

   $PWD
       工作目录(你当前所在的目录).
       与pwd内建命令作用相同.
################################Start Script#######################################
1 #!/bin/bash
2
3 E_WRONG_DIRECTORY=73
4
5 clear # 清屏.
6
7 TargetDirectory=/home/bozo/projects/GreatAmericanNovel
8
9 cd $TargetDirectory
10 echo "Deleting stale files in $TargetDirectory."
11
12 if [ "$PWD" != "$TargetDirectory" ]
13 then    # 防止偶然删除错误的目录
14   echo "Wrong directory!"
15   echo "In $PWD, rather than $TargetDirectory!"
16   echo "Bailing out!"
17   exit $E_WRONG_DIRECTORY
18 fi  
19
20 rm -rf *
21 rm .[A-Za-z0-9]*    # Delete dotfiles.
21 rm .[A-Za-z0-9]*    # 删除"."文件(隐含文件).
22 # rm -f .[^.]* ..?*   为了删除以多个"."开头的文件.
23 # (shopt -s dotglob; rm -f *)   也行.
24 # Thanks, S.C. for pointing this out.
25
26 # 文件名能够包含0-255范围的所有字符,除了"/".
27 # 删除以各种诡异字符开头的文件将作为一个练习留给大家.
28
29 # 这里预留给其他的必要操作.
30
31 echo
32 echo "Done."
33 echo "Old files deleted in $TargetDirectory."
34 echo
35
36
37 exit 0
################################End Script#########################################

   $REPLY
       read命令如果没有给变量,那么输入将保存在$REPLY中.在select菜单中也可用,但是只
       提供选择的变量的项数,而不是变量本身的值.
################################Start Script#######################################
1 #!/bin/bash
2 # reply.sh
3
4 # REPLY是'read'命令结果保存的默认变量.
5
6 echo
7 echo -n "What is your favorite vegetable? "
8 read
9
10 echo "Your favorite vegetable is $REPLY."
11 #  当且仅当在没有变量提供给"read"命令时,
12 #+ REPLY才保存最后一个"read"命令读入的值.
13
14 echo
15 echo -n "What is your favorite fruit? "
16 read fruit
17 echo "Your favorite fruit is $fruit."
18 echo "but..."
19 echo "Value of \$REPLY is still $REPLY."
20 #  $REPLY还是保存着上一个read命令的值,
21 #+ 因为变量$fruit被传入到了这个新的"read"命令中.
22
23 echo
24
25 exit 0
################################End Script#########################################

   $SECONDS
       这个脚本已经运行的时间(单位为秒).
################################Start Script#######################################
1 #!/bin/bash
2
3 TIME_LIMIT=10
4 INTERVAL=1
5
6 echo
7 echo "Hit Control-C to exit before $TIME_LIMIT seconds."
8 echo
9
10 while [ "$SECONDS" -le "$TIME_LIMIT" ]
11 do
12   if [ "$SECONDS" -eq 1 ]
13   then
14     units=second
15   else  
16     units=seconds
17   fi
18
19   echo "This script has been running $SECONDS $units."
20   #  在一台比较慢的或者是负载很大的机器上,这个脚本可能会跳过几次循环
21   #+ 在一个while循环中.
22   sleep $INTERVAL
23 done
24
25 echo -e "\a"  # Beep!
26
27 exit 0
################################End Script#########################################

   $SHELLOPTS
       这个变量里保存shell允许的选项,这个变量是只读的.
       bash$ echo $SHELLOPTS
       braceexpand:hashall:histexpand:monitor:history:interactive-comments:emacs

   $SHLVL
       Shell层次,就是shell层叠的层次,如果是命令行那$SHLVL就是1,如果命令行执行的脚
       本中,$SHLVL就是2,以此类推.

   $TMOUT
       如果$TMOUT环境变量被设置为一个非零的时间值,那么在过了这个指定的时间之后,
       shell提示符将会超时,这会引起一个logout.

       在2.05b版本的Bash中,已经支持在一个带有read命令的脚本中使用$TMOUT变量.
        1 # 需要使用Bash v2.05b或者以后的版本上
        2
        3 TMOUT=3    # Prompt times out at three seconds.
        3 TMOUT=3    # 设置超时的时间为3秒
        4
        5 echo "What is your favorite song?"
        6 echo "Quickly now, you only have $TMOUT seconds to answer!"
        7 read song
        8
        9 if [ -z "$song" ]
       10 then
       11   song="(no answer)"
       12   # 默认响应.
       13 fi
       14
       15 echo "Your favorite song is $song."

       这里有一个更复杂的方法来在一个脚本中实现超时功能.一种办法就是建立一个时间循
       环,在超时的时候通知脚本.不过,这也需要一个信号处理机制,在超时的时候来产生中
       断.
       (参见Example 29-5)

Example 9-2 时间输入
################################Start Script#######################################
  1 #!/bin/bash
  2 # timed-input.sh
  3
  4 # TMOUT=3    在新版本的Bash上也能工作.
  5
  6
  7 TIMELIMIT=3  # 在这个例子上是3秒,也可以设其他的值.
  8
  9 PrintAnswer()
 10 {
 11   if [ "$answer" = TIMEOUT ]
 12   then
 13     echo $answer
 14   else       # 别想混合着两个例子.
 15     echo "Your favorite veggie is $answer"
 16     kill $!  # kill将不再需要TimerOn函数运行在后台.
 17              # $! 是运行在后台的最后一个工作的PID.
 18   fi
 19
 20 }  
 21
 22
 23
 24 TimerOn()
 25 {
 26   sleep $TIMELIMIT && kill -s 14 $$ &
 27   # 等待3秒,然后发送一个信号给脚本.
 28 }  
 29
 30 Int14Vector()
 31 {
 32   answer="TIMEOUT"
 33   PrintAnswer
 34   exit 14
 35 }  
 36
 37 trap Int14Vector 14   # 为了我们的目的,时间中断(14)被破坏了.
 38
 39 echo "What is your favorite vegetable "
 40 TimerOn
 41 read answer
 42 PrintAnswer
 43
 44
 45 #  很明显的,这是一个拼凑的实现.
 46 #+ 然而使用"-t"选项来"read"的话,将会简化这个任务.
 47 #  见"t-out.sh",在下边.
 48
 49 #  如果你需要一个真正的幽雅的写法...
 50 #+ 建议你使用c/c++来写这个应用,
 51 #+ 使用合适的库来完成这个任务,比如'alarm'和'setitimer'.
 52
 53 exit 0
################################End Script#########################################
       使用stty也是一种选择.

Example 9-3 再来一个时间输入
################################Start Script#######################################
1 #!/bin/bash
2 # timeout.sh
3
4 #  Stephane Chazelas编写,
5 #+ 本书作者进行了一些修改.
6
7 INTERVAL=5                # timeout间隔
8
9 timedout_read() {
10   timeout=$1
11   varname=$2
12   old_tty_settings=`stty -g`
13   stty -icanon min 0 time ${timeout}0
14   eval read $varname      # 或者就是 read $varname
15   stty "$old_tty_settings"
16   # 察看"stty"的man页.
17 }
18
19 echo; echo -n "What's your name? Quick! "
20 timedout_read $INTERVAL your_name
21
22 #  这种方法可能不是每个终端类型都可以正常使用的.
23 #  最大的timeout依赖于具体的终端.
24 #+ (一般都是25.5秒).
25
26 echo
27
28 if [ ! -z "$your_name" ]  # If name input before timeout...
29 then
30   echo "Your name is $your_name."
31 else
32   echo "Timed out."
33 fi
34
35 echo
36
37 # 这个脚本的行为可能与"timed-input.sh"有点不同.
38 # 在每次按键的时候,计数器都会重置.
39
40 exit 0
################################End Script#########################################
       或许,最简单的办法就是使用-t选项来read了.

Example 9-4 Timed read
################################Start Script#######################################
1 #!/bin/bash
2 # t-out.sh
3 # "syngin seven"的一个很好的提议 (thanks).
4
5
6 TIMELIMIT=4         # 4 seconds
7
8 read -t $TIMELIMIT variable <&1
9 #                           ^^^
10 #  在这个例子中,对于Bash 1.x和2.x就需要使用"<&1"
11 #  但对于Bash 3.x就不需要.
12
13 echo
14
15 if [ -z "$variable" ]  # Is null?
16 then
17   echo "Timed out, variable still unset."
18 else  
19   echo "variable = $variable"
20 fi  
21
22 exit 0
################################End Script#########################################

   $UID
       用户ID号.
       当前用户的id号,在/etc/passwd中记录.
       这个值不会因为用户使用了su命令而改变.$UID是只读变量,不容易在命令行或者是脚
       本中被修改,并且和内建的id命令很相像.
Example 9-5 我是root?
################################Start Script#######################################
1 #!/bin/bash
2 # am-i-root.sh:   我是不是root用户?
3
4 ROOT_UID=0   # Root的$UID是0.
5
6 if [ "$UID" -eq "$ROOT_UID" ]  # 是否是root用户,请站出来.
7 then
8   echo "You are root."
9 else
10   echo "You are just an ordinary user (but mom loves you just the same)."
11 fi
12
13 exit 0
14
15
16 # ============================================================= #
17 # 下边的代码将不被执行,因为脚本已经退出了.
18
19 # 检验是root用户的一种可选方法:
20
21 ROOTUSER_NAME=root
22
23 username=`id -nu`              # Or...   username=`whoami`
24 if [ "$username" = "$ROOTUSER_NAME" ]
25 then
26   echo "Rooty, toot, toot. You are root."
27 else
28   echo "You are just a regular fella."
29 fi
################################End Script#########################################
       见例子Example 2-3
       注意:变量$ENV,$LOGNAME,$MAIL,$TERM,$USER,和$USERNAME并不是Bash的内建变量.它
       们经常被设置成环境变量,它们一般都放在Bash的安装文件中.$SHELL,用户登录的
       shell的名字,可能是从/etc/passwd设置的,也可能是在一个"init"脚本中设置的,同样
       的,它也不是Bash的内建变量.
       tcsh% echo $LOGNAME
       bozo
       tcsh% echo $SHELL
       /bin/tcsh
       tcsh% echo $TERM
       rxvt

       bash$ echo $LOGNAME
       bozo
       bash$ echo $SHELL
       /bin/tcsh
       bash$ echo $TERM
       rxvt

位置参数
   $0, $1, $2,等等...
       位置参数,从命令行传递给脚本,或者是传递给函数.或者赋职给一个变量.
       (具体见Example 4-5和Example 11-15)

   $#
       命令行或者是位置参数的个数.(见Example 33-2)

   $*
       所有的位置参数,被作为一个单词.
       注意:"$*"必须被""引用.
   
   $@
       与$*同义,但是每个参数都是一个独立的""引用字串,这就意味着参数被完整地传递,
       并没有被解释和扩展.这也意味着,每个参数列表中的每个参数都被当成一个独立的
       单词.
       注意:"$@"必须被引用.

Example 9-6 arglist:通过$*和$@列出所有的参数
################################Start Script#######################################
1 #!/bin/bash
2 # arglist.sh
3 # 多使用几个参数来调用这个脚本,比如"one tow three".
4
5 E_BADARGS=65
6
7 if [ ! -n "$1" ]
8 then
9   echo "Usage: `basename $0` argument1 argument2 etc."
10   exit $E_BADARGS
11 fi  
12
13 echo
14
15 index=1          # 初始化数量.
16
17 echo "Listing args with \"\$*\":"
18 for arg in "$*"  # 如果"$*"不被""引用,那么将不能正常地工作
19 do
20   echo "Arg #$index = $arg"
21   let "index+=1"
22 done             # $* sees all arguments as single word.
22 done             # $* 认为所有的参数为一个单词
23 echo "Entire arg list seen as single word."
24
25 echo
26
27 index=1          # 重置数量.
28                  # 如果你忘了这句会发生什么?
29
30 echo "Listing args with \"\$@\":"
31 for arg in "$@"
32 do
33   echo "Arg #$index = $arg"
34   let "index+=1"
35 done             # $@ 认为每个参数都一个单独的单词.
36 echo "Arg list seen as separate words."
37
38 echo
39
40 index=1          # 重置数量.
41
42 echo "Listing args with \$* (unquoted):"
43 for arg in $*
44 do
45   echo "Arg #$index = $arg"
46   let "index+=1"
47 done             # 未""引用的$*把参数作为独立的单词.
48 echo "Arg list seen as separate words."
49
50 exit 0
################################End Script#########################################

       在shift命令后边,$@将保存命令行中剩余的参数,而$1被丢掉了.
        1 #!/bin/bash
        2 # 使用 ./scriptname 1 2 3 4 5 来调用这个脚本
        3
        4 echo "$@"    # 1 2 3 4 5
        5 shift
        6 echo "$@"    # 2 3 4 5
        7 shift
        8 echo "$@"    # 3 4 5
        9
       10 # 每个"shift"都丢弃$1.
       11 # "$@" 将包含剩下的参数.
       $@也作为为工具使用,用来过滤传给脚本的输入.
       cat "$@"结构接受从stdin传来的输入,也接受从参数中指定的文件传来的输入.
       具体见Example 12-21和Example 12-22.

       注意:$*和$@的参数有时会不一致,发生令人迷惑的行为,这依赖于$IFS的设置.

Example 9-7 不一致的$*和$@行为
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 #  "$*"和"$@"的古怪行为,
 4 #+ 依赖于它们是否被""引用.
 5 #  单词拆分和换行的不一致处理.
 6
 7
 8 set -- "First one" "second" "third:one" "" "Fifth: :one"
 9 # 设置这个脚本参数,$1,$2,等等.
10
11 echo
12
13 echo 'IFS unchanged, using "$*"'
14 c=0
15 for i in "$*"               # 引用
16 do echo "$((c+=1)): [$i]"   # 这行在下边的每个例子中都一样.
17                             # Echo参数.
18 done
19 echo ---
20
21 echo 'IFS unchanged, using $*'
22 c=0
23 for i in $*                 # 未引用
24 do echo "$((c+=1)): [$i]"
25 done
26 echo ---
27
28 echo 'IFS unchanged, using "$@"'
29 c=0
30 for i in "$@"
31 do echo "$((c+=1)): [$i]"
32 done
33 echo ---
34
35 echo 'IFS unchanged, using $@'
36 c=0
37 for i in $@
38 do echo "$((c+=1)): [$i]"
39 done
40 echo ---
41
42 IFS=:
43 echo 'IFS=":", using "$*"'
44 c=0
45 for i in "$*"
46 do echo "$((c+=1)): [$i]"
47 done
48 echo ---
49
50 echo 'IFS=":", using $*'
51 c=0
52 for i in $*
53 do echo "$((c+=1)): [$i]"
54 done
55 echo ---
56
57 var=$*
58 echo 'IFS=":", using "$var" (var=$*)'
59 c=0
60 for i in "$var"
61 do echo "$((c+=1)): [$i]"
62 done
63 echo ---
64
65 echo 'IFS=":", using $var (var=$*)'
66 c=0
67 for i in $var
68 do echo "$((c+=1)): [$i]"
69 done
70 echo ---
71
72 var="$*"
73 echo 'IFS=":", using $var (var="$*")'
74 c=0
75 for i in $var
76 do echo "$((c+=1)): [$i]"
77 done
78 echo ---
79
80 echo 'IFS=":", using "$var" (var="$*")'
81 c=0
82 for i in "$var"
83 do echo "$((c+=1)): [$i]"
84 done
85 echo ---
86
87 echo 'IFS=":", using "$@"'
88 c=0
89 for i in "$@"
90 do echo "$((c+=1)): [$i]"
91 done
92 echo ---
93
94 echo 'IFS=":", using $@'
95 c=0
96 for i in $@
97 do echo "$((c+=1)): [$i]"
98 done
99 echo ---
100
101 var=$@
102 echo 'IFS=":", using $var (var=$@)'
103 c=0
104 for i in $var
105 do echo "$((c+=1)): [$i]"
106 done
107 echo ---
108
109 echo 'IFS=":", using "$var" (var=$@)'
110 c=0
111 for i in "$var"
112 do echo "$((c+=1)): [$i]"
113 done
114 echo ---
115
116 var="$@"
117 echo 'IFS=":", using "$var" (var="$@")'
118 c=0
119 for i in "$var"
120 do echo "$((c+=1)): [$i]"
121 done
122 echo ---
123
124 echo 'IFS=":", using $var (var="$@")'
125 c=0
126 for i in $var
127 do echo "$((c+=1)): [$i]"
128 done
129
130 echo
131
132 # 用ksh或者zsh -y来试试这个脚本.
133
134 exit 0
135
136 # This example script by Stephane Chazelas,
137 # and slightly modified by the document author.
################################End Script#########################################
       注意:$@和$*中的参数只有在""中才会不同.

Example 9-8 当$IFS为空时的$*和$@
################################Start Script#######################################
1 #!/bin/bash
2
3 #  如果$IFS被设置为空时,
4 #+ 那么"$*" 和"$@" 将不会象期望那样echo出位置参数.
5
6 mecho ()       # Echo 位置参数.
7 {
8 echo "$1,$2,$3";
9 }
10
11
12 IFS=""         # 设置为空.
13 set a b c      # 位置参数.
14
15 mecho "$*"     # abc,,
16 mecho $*       # a,b,c
17
18 mecho $@       # a,b,c
19 mecho "$@"     # a,b,c
20
21 #  当$IFS设置为空时,$* 和$@ 的行为依赖于
22 #+ 正在运行的Bash或者sh的版本.
23 #  所以在脚本中使用这种"feature"不是明智的行为.
24
25
26 # Thanks, Stephane Chazelas.
27
28 exit 0
################################End Script#########################################

其他的特殊参数

   $-
       传递给脚本的falg(使用set命令).参考Example 11-15.

       注意:这起初是ksh的特征,后来被引进到Bash中,但不幸的是,在Bash中它看上去也不
       能可靠的工作.使用它的一个可能的方法就是让这个脚本进行自我测试(查看是否是交
       互的).

   $!
       在后台运行的最后的工作的PID(进程ID).
        1 LOG=$0.log
        2
        3 COMMAND1="sleep 100"
        4
        5 echo "Logging PIDs background commands for script: $0" >> "$LOG"
        6 # 所以它们可以被监控,并且在必要的时候kill掉.
        7 echo >> "$LOG"
        8
        9 # Logging 命令.
       10
       11 echo -n "PID of \"$COMMAND1\":  " >> "$LOG"
       12 ${COMMAND1} &
       13 echo $! >> "$LOG"
       14 # PID of "sleep 100":  1506
       15
       16 # Thank you, Jacques Lederer, for suggesting this.


       1 possibly_hanging_job & { sleep ${TIMEOUT}; eval 'kill -9 $!' &> /dev/null; }
       2 # 强制结束一个品行不良的程序.
       3 # 很有用,比如在init脚本中.
       4
       5 # Thank you,Sylvain Fourmanoit,for this creative use of the "!" variable.

   $_
       保存之前执行的命令的最后一个参数.

Example 9-9 下划线变量
################################Start Script#######################################
1 #!/bin/bash
2
3 echo $_              # /bin/bash
4                      # 只是调用/bin/bash来运行这个脚本.
5
6 du >/dev/null        # 将没有命令的输出
7 echo $_              # du
8
9 ls -al >/dev/null    # 没有命令输出
10 echo $_              # -al  (最后的参数)
11
12 :
13 echo $_              # :
################################End Script#########################################

   $?
       命令,函数或者脚本本身的退出状态(见Example 23-7)

   $$
       脚本自身的进程ID.这个变量经常用来构造一个"unique"的临时文件名.
       (参考Example A-13,Example 29-6,Example 12-28和Example 11-25).
       这通常比调用mktemp来得简单.

注意事项:
[1]        当前运行的脚本的PID为$$.
[2]        "argument"和"parameter"这两个单词经常不加区分的使用.在这整本书中,这两个
       单词的意思完全相同.(在翻译的时候就未加区分,统统翻译成参数)


9.2 操作字符串
--------------
Bash支持超多的字符串操作,操作的种类和数量令人惊异.但不幸的是,这些工具缺乏集中性.
一些是参数替换的子集,但是另一些则属于UNIX的expr命令.这就导致了命令语法的不一致和
功能的重叠,当然也会引起混乱.

字符串长度

   ${#string}
   expr length $string
   expr "$string" : '.*'

   1 stringZ=abcABC123ABCabc
   2
   3 echo ${#stringZ}                 # 15
   4 echo `expr length $stringZ`      # 15
   5 echo `expr "$stringZ" : '.*'`    # 15

Example 9-10 在一个文本文件的段间插入空行
################################Start Script#######################################
1 #!/bin/bash
2 # paragraph-space.sh
3
4 # 在一个不空行的文本文件的段间插入空行.
5 # Usage: $0 6
7 MINLEN=45        # 可能需要修改这个值.
8 #  假定行的长度小于$MINLEN指定的长度
9 #+ $MINLEN中的值用来描述多少个字符结束一个段.
10
11 while read line  # 对于需要多行输入的文件基本都是这个样子
12 do
13   echo "$line"   # 输出line.
14
15   len=${#line}
16   if [ "$len" -lt "$MINLEN" ]
17     then echo    # 在短行后边添加一个空行
18   fi  
19 done
20
21 exit 0
################################End Script#########################################

从字符串开始的位置匹配子串的长度

   expr match "$string" '$substring'
       $substring是一个正则表达式
   
   expr "$string" : '$substring'
       $substring是一个正则表达式

   1 stringZ=abcABC123ABCabc
   2 #       |------|
   3
   4 echo `expr match "$stringZ" 'abc[A-Z]*.2'`   # 8
   5 echo `expr "$stringZ" : 'abc[A-Z]*.2'`       # 8

索引

   expr index $string $substring
       匹配到子串的第一个字符的位置.

   1 stringZ=abcABC123ABCabc
   2 echo `expr index "$stringZ" C12`             # 6
   3                                              # C position.
   4
   5 echo `expr index "$stringZ" 1c`              # 3
   6 # 'c' (in #3 position) matches before '1'.

   在C语言中最近的等价函数为strchr().

提取子串
   
   ${string:position}
       在string中从位置$position开始提取子串.
       如果$string为"*"或"@",那么将提取从位置$position开始的位置参数,[1]

   ${string:position:length}
       在string中从位置$position开始提取$length长度的子串.

################################Start Script#######################################
1 stringZ=abcABC123ABCabc
2 #       0123456789.....
3 #       0-based indexing.
4
5 echo ${stringZ:0}                            # abcABC123ABCabc
6 echo ${stringZ:1}                            # bcABC123ABCabc
7 echo ${stringZ:7}                            # 23ABCabc
8
9 echo ${stringZ:7:3}                          # 23A
10                                              # 3个字符长度的子串.
11
12
13
14 # 有没有可能从字符结尾开始,反向提取子串?
15    
16 echo ${stringZ:-4}                           # abcABC123ABCabc
17 # 以${parameter:-default}方式,默认是提取完整地字符串.
18 # 然而 . . .
19
20 echo ${stringZ:(-4)}                         # Cabc
21 echo ${stringZ: -4}                          # Cabc
22 # 现在,它可以工作了.
23 # 使用圆括号或者添加一个空格来转义这个位置参数.
24
25 # Thank you, Dan Jacobson, for pointing this out.
################################End Script#########################################
       如果$string参数为"*"或"@",那将最大的提取从$position开始的$length个位置参数.
   1 echo ${*:2}          # Echo出第2个和后边所有的位置参数.
   2 echo ${@:2}          # 与前边相同.
   3
   4 echo ${*:2:3}        # 从第2个开始,Echo出后边3个位置参数.


   expr substr $string $position $length
       在string中从位置$position开始提取$length长度的子串.
   1 stringZ=abcABC123ABCabc
   2 #       123456789......
   3 #       1-based indexing.
   4
   5 echo `expr substr $stringZ 1 2`              # ab
   6 echo `expr substr $stringZ 4 3`              # ABC

   expr match "$string" '\($substring\)'
       从$string的开始位置提取$substring,$substring是一个正则表达式.

   expr "$string" : '\($substring\)'
       从$string的开始位置提取$substring,$substring是一个正则表达式.
   1 stringZ=abcABC123ABCabc
   2 #       =======        
   3
   4 echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'`   # abcABC1
   5 echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'`       # abcABC1
   6 echo `expr "$stringZ" : '\(.......\)'`                   # abcABC1
   7 # All of the above forms give an identical result.

子串削除

   ${string#substring}
        从$string的左边截掉第一个匹配的$substring
   ${string##substring}
        从$string的左边截掉最后一个个匹配的$substring

   1 stringZ=abcABC123ABCabc
   2 #       |----|
   3 #       |----------|
   4
   5 echo ${stringZ#a*C}      # 123ABCabc
   6 # 截掉'a'和'C'之间最近的匹配.
   7
   8 echo ${stringZ##a*C}     # abc
   9 # 截掉'a'和'C'之间最远的匹配.
       
   
   ${string%substring}
        从$string的右边截掉第一个匹配的$substring
   ${string%%substring}
        从$string的右边截掉最后一个匹配的$substring

   1 stringZ=abcABC123ABCabc
   2 #                    ||
   3 #        |------------|
   4
   5 echo ${stringZ%b*c}      # abcABC123ABCa
   6 # 从$stringZ的后边开始截掉'b'和'c'之间的最近的匹配
   7
   8 echo ${stringZ%%b*c}     # a
   9 # 从$stringZ的后边开始截掉'b'和'c'之间的最远的匹配

阅读(1608) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~