魅力博客

魅力Linux|魅力空间|魅力博客|学习Linux|ubuntu日记|电脑教程|手机软件

深入学习shell脚本艺术-高级Bash脚本编程指南4



混合比较

-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 # gcd.sh: 最大公约数
 3 #         使用Euclid's 算法
 4
 5 #  最大公约数,就是2个数能够同时整除的最大的数.
 6 #
 7
 8 #  Euclid's算法采用连续除法.
 9 #  在每个循环中
10 #+ 被除数 <---  除数
11 #+ 除数 <---  余数
12 #+ 直到余数= 0.
13 #+ 在最后的循环中The gcd = 被除数
14 #
15 #  关于这个算法更精彩的讨论
16 #  见Jim Loy's site, http://www.jimloy.com/number/euclids.htm.
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 gcd ()
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 gcd.
50 }                                # 最后的$dividend就是gcd.
51
52
53 gcd $1 $2
54
55 echo; echo "GCD 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 <FILENAME
 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'之间的最远的匹配

Example 9-11 利用修改文件名,来转换图片格式
################################Start Script#######################################
 1 #!/bin/bash
 2 #  cvt.sh:
 3 #  把一个目录下的所有MacPaint格式的图片文件都转换为"pbm"格式的图片文件.
 4
 5 #  使用来自"netpbm"包的"macptopbm"程序,
 6 #+ 这个程序主要是由Brian Henderson(bryanh@giraffe-data.com)来维护的.
 7 #  Netpbm是大多数Linux发行版的标准部分.
 8
 9 OPERATION=macptopbm
10 SUFFIX=pbm          # 新的文件名后缀
11
12 if [ -n "$1" ]
13 then
14   directory=$1      # 如果目录名作为第1个参数给出...
15 else
16   directory=$PWD    # 否则使用当前的工作目录.
17 fi  
18   
19 #  假设在目标目录中的所有文件都是MacPaint格式的图片文件,
20 #+ 以".mac"为文件名的后缀.
21
22 for file in $directory/*    # Filename globbing.
23 do
24   filename=${file%.*c}      #  去掉文件名的".mac"后缀
25                             #+ ('.*c' matches everything
25                             #+ ('.*c' 将匹配'.'和'c'之间的任何字符串).
26
27   $OPERATION $file > "$filename.$SUFFIX"
28                             # 转换为新的文件名.
29   rm -f $file               # 转换完毕后删除原有的文件.
30   echo "$filename.$SUFFIX"  # 从stdout输出反馈.
31 done
32
33 exit 0
34
35 # 练习:
36 # --------
37 #  就像它现在这个样子,这个脚本把当前目录的所有文件都转换了.
38 #
39 #  修改这个脚本,让他只转换以".mac"为后缀的文件.
################################End Script#########################################
        一个简单的模拟getopt命令的办法就是使用子串提取结构.
Example 9-12 模仿getopt命令
################################Start Script#######################################
 1 #!/bin/bash
 2 # getopt-simple.sh
 3 # Author: Chris Morgan
 4 # 授权使用在ABS Guide中.
 5
 6
 7 getopt_simple()
 8 {
 9     echo "getopt_simple()"
10     echo "Parameters are '$*'"
11     until [ -z "$1" ]
12     do
13       echo "Processing parameter of: '$1'"
14       if [ ${1:0:1} = '/' ]
15       then
16           tmp=${1:1}               # 去掉开头的'/' . . .
17           parameter=${tmp%%=*}     # 提取名字.
18           value=${tmp##*=}         # 提取值.
19           echo "Parameter: '$parameter', value: '$value'"
20           eval $parameter=$value
21       fi
22       shift
23     done
24 }
25
26 # 传递所有的选项到getopt_simple().
27 getopt_simple $*
28
29 echo "test is '$test'"
30 echo "test2 is '$test2'"
31
32 exit 0
33
34 ---
35
36 sh getopt_example.sh /test=value1 /test2=value2
37
38 Parameters are '/test=value1 /test2=value2'
39 Processing parameter of: '/test=value1'
40 Parameter: 'test', value: 'value1'
41 Processing parameter of: '/test2=value2'
42 Parameter: 'test2', value: 'value2'
43 test is 'value1'
44 test2 is 'value2'
################################End Script#########################################

子串替换

    ${string/substring/replacement}
        使用$replacement来替换第一个匹配的$substring.
    ${string//substring/replacement}
        使用$replacement来替换所有匹配的$substring.

    1 stringZ=abcABC123ABCabc
    2
    3 echo ${stringZ/abc/xyz}           # xyzABC123ABCabc
    4                                   # 用'xyz'来替换第一个匹配的'abc'.
    5
    6 echo ${stringZ//abc/xyz}          # xyzABC123ABCxyz
    7                                   # 用'xyz'来替换所有匹配的'abc'.

    ${string/#substring/replacement}
        如果$substring匹配$string的开头部分,那么就用$replacement来替换$substring.
    ${string/%substring/replacement}
        如果$substring匹配$string的结尾部分,那么就用$replacement来替换$substring.
    1 stringZ=abcABC123ABCabc
    2
    3 echo ${stringZ/#abc/XYZ}          # XYZABC123ABCabc
    4                                   # 用'XYZ'替换开头的'abc'
    5
    6 echo ${stringZ/%abc/XYZ}          # abcABC123ABCXYZ
    7                                   # 用'XYZ'替换结尾的'abc'

9.2.1 使用awk来操作字符串
~~~~~~~~~~~~~~~~~~~~~~~~~
Bash脚本也可以使用awk来操作字符串.

Example 9-13 提取字符串的一种可选的方法
################################Start Script#######################################
 1 #!/bin/bash
 2 # substring-extraction.sh
 3
 4 String=23skidoo1
 5 #      012345678    Bash
 6 #      123456789    awk
 7 # 注意,对于awk和Bash来说,它们使用的是不同的string索引系统:
 8 # Bash的第一个字符是从'0'开始记录的.
 9 # Awk的第一个字符是从'1'开始记录的.
10
11 echo ${String:2:4} # 位置3 (0-1-2), 4 个字符长
12                                          # skid
13
14 # awk中等价于${string:pos:length}的命令是substr(string,pos,length).
15 echo | awk '
16 { print substr("'"${String}"'",3,4)      # skid
17 }
18 '
19 #  使用一个空的"echo"通过管道给awk一个假的输入,
20 #+ 这样可以不用提供一个文件名.
21
22 exit 0
################################End Script#########################################

9.2.2 更深的讨论
~~~~~~~~~~~~~~~~
关于在脚本中使用字符串更深的讨论,请参考 9.3节,h和expr命令列表的相关章节.
关于脚本的例子,见:

1    Example 12-9
2    Example 9-16
3    Example 9-17
4    Example 9-18
5    Example 9-20

注意事项:
[1]        这适用于命令行参数和函数参数.



9.3 参数替换
------------

操作和扩展变量

    ${parameter}
        与$parameter相同,就是parameter的值.在特定的上下文中,只有少部分会产生
        ${parameter}的混淆.可以组合起来一起赋指给字符串变量.

        1 your_id=${USER}-on-${HOSTNAME}
        2 echo "$your_id"
        3 #
        4 echo "Old \$PATH = $PATH"
        5 PATH=${PATH}:/opt/bin  #Add /opt/bin to $PATH for duration of script.
        6 echo "New \$PATH = $PATH"

    
    ${parameter-default},${parameter:-default}
        如果parameter没被set,那么就使用default.

        1 echo ${username-`whoami`}
        2 # echo `whoami`的结果,如果没set username变量的话.
        
        注意:${parameter-default}和${parameter:-default}大部分时候是相同的.
            额外的":"在parameter被声明的时候(而且被赋空值),会有一些不同.

################################Start Script#######################################
 1 #!/bin/bash
 2 # param-sub.sh
 3
 4 #  一个变量是否被声明
 5 #+ 将会影响默认选项的触发
 6 #+ 甚至于这个变量被设为空.
 7
 8 username0=
 9 echo "username0 has been declared, but is set to null."
10 echo "username0 = ${username0-`whoami`}"
11 # 将不会echo.
12
13 echo
14
15 echo username1 has not been declared.
16 echo "username1 = ${username1-`whoami`}"
17 # 将会echo.
18
19 username2=
20 echo "username2 has been declared, but is set to null."
21 echo "username2 = ${username2:-`whoami`}"
22 #                            ^
23 # 将会echo因为使用的是:-而不是 -.
24 # 和前边的第一个例子好好比较一下.
25
26
27 #
28
29 # 再来一个:
30
31 variable=
32 # 变量已经被声明了,但是被设置为空.
33
34 echo "${variable-0}"    # (no output)
35 echo "${variable:-1}"   # 1
36 #               ^
37
38 unset variable
39
40 echo "${variable-2}"    # 2
41 echo "${variable:-3}"   # 3
42
43 exit 0
################################End Script#########################################

        如果脚本中并没有传入命令行参数,那么default parameter将被使用.
        1 DEFAULT_FILENAME=generic.data
        2 filename=${1:-$DEFAULT_FILENAME}
        3 #  如果没有参数被传递进来,那么下边的命令快将操作
        4 #+ 文件"generic.data"
        5 #
        6 #  后续命令.
        
        另外参见Example 3-4,Example 28-2,和Example A-6.
        与"使用一个与列表来支持一个默认的命令行参数"的方法相比较.

    ${parameter=default},${parameter:=default}
        如果parameter未设置,那么就设置为default.
        这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
        才会有区别,[1]和上边的行为一样.
        1 echo ${username=`whoami`}
        2 # Variable "username" is now set to `whoami`.
        2 # 变量"username"被赋值为`whoami`.

    ${parameter+alt_value},${parameter:+alt_value}
        如果parameter被set了,那就使用alt_value,否则就使用null字符串.
        这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
        会有区别,见下.

################################Start Script#######################################
 1 echo "###### \${parameter+alt_value} ########"
 2 echo
 3
 4 a=${param1+xyz}
 5 echo "a = $a"      # a =
 6
 7 param2=
 8 a=${param2+xyz}
 9 echo "a = $a"      # a = xyz
10
11 param3=123
12 a=${param3+xyz}
13 echo "a = $a"      # a = xyz
14
15 echo
16 echo "###### \${parameter:+alt_value} ########"
17 echo
18
19 a=${param4:+xyz}
20 echo "a = $a"      # a =
21
22 param5=
23 a=${param5:+xyz}
24 echo "a = $a"      # a =
25 # 与a=${param5+xyz}有不同的结果.
26
27 param6=123
28 a=${param6+xyz}
29 echo "a = $a"      # a = xyz
################################End Script#########################################

    ${parameter?err_msg}, ${parameter:?err_msg}
        如果parameter被set,那就是用set的值,否则print err_msg.
        这两种办法绝大多数时候用法都一样,只有在$parameter被声明并设置为空的时候,
        会有区别,见上.

Example 9-14 使用参数替换和error messages
################################Start Script#######################################
 1 #!/bin/bash
 2
 3 #  检查一些系统的环境变量.
 4 #  这是个好习惯.
 5 #  比如,如果$USER(在console上的用户名)没被set,
 6 #+ 那么系统就不会认你.
 7
 8 : ${HOSTNAME?} ${USER?} ${HOME?} ${MAIL?}
 9   echo
10   echo "Name of the machine is $HOSTNAME."
11   echo "You are $USER."
12   echo "Your home directory is $HOME."
13   echo "Your mail INBOX is located in $MAIL."
14   echo
15   echo "If you are reading this message,"
16   echo "critical environmental variables have been set."
17   echo
18   echo
19
20 # ------------------------------------------------------
21
22 #  ${variablename?} 结果也可以用来
23 #+ 在一个脚本中检查变量是否被set.
24
25 ThisVariable=Value-of-ThisVariable
26 #  注意,顺便提一下,这个字符串变量可能在它们的名字中会被设置
27 #+ 非法字符
28 : ${ThisVariable?}
29 echo "Value of ThisVariable is $ThisVariable".
30 echo
31 echo
32
33
34 : ${ZZXy23AB?"ZZXy23AB has not been set."}
35 #  如果ZZXy23AB没被set,
36 #+ 那么这个脚本将以一个error message终止.
37
38 # 你可以指定错误消息.
39 # : ${variablename?"ERROR MESSAGE"}
40
41
42 # 同样的结果:    dummy_variable=${ZZXy23AB?}
43 #                      dummy_variable=${ZZXy23AB?"ZXy23AB has not been set."}
44 #
45 #                      echo ${ZZXy23AB?} >/dev/null
46
47 #  同"set -u"命令来比较这些检查变量是否被set的方法.
48 #
49
50
51
52 echo "You will not see this message, because script already terminated."
53
54 HERE=0
55 exit $HERE   # Will NOT exit here.
56
57 # 事实上,这个脚本将返回值1作为退出状态(echo $?).
################################End Script#########################################

Example 9-15 参数替换和"usage"messages
################################Start Script#######################################
 1 #!/bin/bash
 2 # usage-message.sh
 3
 4 : ${1?"Usage: $0 ARGUMENT"}
 5 #  如果没有命令行参数,那么脚本将在此处退出.
 6 #+ 并且打出如下的错误消息.
 7 #    usage-message.sh: 1: Usage: usage-message.sh ARGUMENT
 8
 9 echo "These two lines echo only if command-line parameter given."
10 echo "command line parameter = \"$1\""
11
12 exit 0  # 如果有命令行参数,那么将在此处退出.
13
14 # 测试这个脚本,第1次测试带参数,第2次测试不带参数.
15 # 如果有参数,那么"$?"就是0.
16 # 如果没有,那么"$?"就是1.
################################End Script#########################################

参数替换和扩展
    下边的表达式是使用expr字符串匹配操作的补充(见Example 12-9).
    这些特定的使用方法绝大多数情况都是用来分析文件目录名.

变量长度/子串删除

    ${#var}
        字符串长度($var的字符数量).对于一个数组,${#array}是数组中第一个元素的长度.

        一些例外:
            ${#*}和${#@}将给出位置参数的个数.
            对于数组来说${#array[*]}和${$#array[@]}将给出数组元素的个数.
Example 9-16 变量长度
################################Start Script#######################################
 1 #!/bin/bash
 2 # length.sh
 3
 4 E_NO_ARGS=65
 5
 6 if [ $# -eq 0 ]  # 这个demo脚本必须有命令行参数.
 7 then
 8   echo "Please invoke this script with one or more command-line arguments."
 9   exit $E_NO_ARGS
10 fi  
11
12 var01=abcdEFGH28ij
13 echo "var01 = ${var01}"
14 echo "Length of var01 = ${#var01}"
15 # 现在,让我们试试在里边嵌入一个空格.
16 var02="abcd EFGH28ij"
17 echo "var02 = ${var02}"
18 echo "Length of var02 = ${#var02}"
19
20 echo "Number of command-line arguments passed to script = ${#@}"
21 echo "Number of command-line arguments passed to script = ${#*}"
22
23 exit 0
################################End Script#########################################

    ${var#Pattern}, ${var##Pattern}
        从$var开头删除最近或最远匹配$Pattern的子串.

        来自Example A-7例子的一部分.
        1 # 来自"days-between.sh"例子的一个函数.
        2 # 去掉传递进来的参数开头的0.
        3
        4 strip_leading_zero () #  去掉开头的0
        5 {                     #+ 从传递进来的参数中.
        6   return=${1#0}       #  "1"指的是"$1" -- 传进来的参数.
        7 }                     #  "0"就是我们想从"$1"中删除的子串.

        下边是Manfred Schwarb's对上边函数的一个改版.
        1 strip_leading_zero2 () # 去掉开头的0,因为如果不去掉的话
        2 {                      # Bash将会把这个值作为8进制解释.
        3   shopt -s extglob     # 打开扩展globbing.
        4   local val=${1##+(0)} # 使用局部变量,匹配最长的连续的0.
        5   shopt -u extglob     # 打开扩展globbing.
        6   _strip_leading_zero2=${val:-0}
        7                        # 如果输入为0,那么返回0来代替"".
        8 }

        另一个例子
        1 echo `basename $PWD`        # 当前工作目录的basename.
        2 echo "${PWD##*/}"           # 当前工作目录的basename.
        3 echo
        4 echo `basename $0`          # 脚本名字.
        5 echo $0                     # 脚本名字.
        6 echo "${0##*/}"             # 脚本名字.
        7 echo
        8 filename=test.data
        9 echo "${filename##*.}"      # data

    ${var%Pattern}, ${var%%Pattern}
        从$var结尾删除最近或最远匹配$Pattern的子串.

Bash version2 添加了额外的选项.




返回顶部

发表评论:

Powered By Z-BlogPHP 1.7.3


知识共享许可协议
本作品采用知识共享署名 3.0 中国大陆许可协议进行许可。
网站备案号粤ICP备15104741号-1