高级Bash脚本编程指南

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 "ID 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().



提取子串



${stringosition}

在string中从位置$position开始提取子串.

如果$string为"*"或"@",那么将提取从位置$position开始的位置参数,[1]



${stringosition: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 "arameters are '$*'"

11 until [ -z "$1" ]

12 do

13 echo "rocessing parameter of: '$1'"

14 if [ ${1:0:1} = '/' ]

15 then

16 tmp=${1:1} # 去掉开头的'/' . . .

17 parameter=${tmp%%=*} # 提取名字.

18 value=${tmp##*=} # 提取值.

19 echo "arameter: '$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中等价于${stringos: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 "lease 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 添加了额外的选项.



    Example 9-17 参数替换中的模式匹配

    ################################Start Script#######################################

    1 #!/bin/bash

    2 # patt-matching.sh

    3

    4 # 使用# ## % %%来进行参数替换操作的模式匹配.

    5

    6 var1=abcd12345abc6789

    7 pattern1=a*c # * (通配符) 匹配a - c之间的任何字符.

    8

    9 echo

    10 echo "var1 = $var1" # abcd12345abc6789

    11 echo "var1 = ${var1}" # abcd12345abc6789

    12 # (alternate form)

    13 echo "Number of characters in ${var1} = ${#var1}"

    14 echo

    15

    16 echo "pattern1 = $pattern1" # a*c (everything between 'a' and 'c')

    17 echo "--------------"

    18 echo '${var1#$pattern1} =' "${var1#$pattern1}" # d12345abc6789

    19 # 最短的可能匹配, 去掉abcd12345abc6789的前3个字符

    20 # |-| ^^^

    21 echo '${var1##$pattern1} =' "${var1##$pattern1}" # 6789

    22 # 最远的匹配,去掉abcd12345abc6789的前12个字符.

    23 # |----------| ^^^^

    24

    25 echo; echo; echo

    26

    27 pattern2=b*9 # 'b' 到'9'之间的任何字符

    28 echo "var1 = $var1" # 还是 abcd12345abc6789

    29 echo

    30 echo "pattern2 = $pattern2"

    31 echo "--------------"

    32 echo '${var1%pattern2} =' "${var1%$pattern2}" # abcd12345a

    33 # 最近的匹配, 去掉abcd12345abc6789的最后6个字符

    34 # |----| ^^^^

    35 echo '${var1%%pattern2} =' "${var1%%$pattern2}" # a

    36 # 最远匹配, 去掉abcd12345abc6789的最后12个字符

    37 # |-------------| ^^^^^^

    38

    39 # 记住, # 和## 从字符串的左边开始,并且去掉左边的字符串,

    40 # % 和 %% 从字符串的右边开始,并且去掉右边的子串.

    41

    42 echo

    43

    44 exit 0

    ################################End Script#########################################      
  • Example 9-18 重命名文件扩展名



    ################################Start Script#######################################



    1 #!/bin/bash



    2 # rfe.sh: 重命名文件扩展名.



    3 #



    4 # 用法: rfe old_extension new_extension



    5 #



    6 # 例子:



    7 # 将指定目录的所有 *.gif 文件都重命名为 *.jpg,



    8 # 用法: rfe gif jpg



    9



    10



    11 E_BADARGS=65



    12



    13 case $# in



    14 0|1) # "|" 在这里的意思是或操作.



    15 echo "Usage: `basename $0` old_file_suffix new_file_suffix"



    16 exit $E_BADARGS # 如果只有0个或1个参数,那么就退出.



    17 ;;



    18 esac



    19



    20



    21 for filename in *.$1



    22 # 以第一个参数为扩展名的全部文件的列表



    23 do



    24 mv $filename ${filename%$1}$2



    25 # 从筛选出的文件中先去掉以第一参数结尾的扩展名部门,



    26 #+ 然后作为扩展名把第2个参数添加上.



    27 done



    28



    29 exit 0



    ################################End Script#########################################







    变量扩展/子串替换



    这些结构都是从ksh中吸收来的.







    ${varos}



    变量var从位置pos开始扩展.







    ${varos:len}



    从位置pos开始,并扩展len长度个字符.见Example A-14(这个例子里有这种操作的一个



    创造性用法)







    ${var/Pattern/Replacement}



    使用Replacement来替换var中的第一个Pattern的匹配.







    ${var//Pattern/Replacement}



    全局替换.在var中所有的匹配,都会用Replacement来替换.







    向上边所说,如果Replacement被忽略的话,那么所有匹配到的Pattern都会被删除.







    Example 9-19 使用模式匹配来分析比较特殊的字符串



    ################################Start Script#######################################



    1 #!/bin/bash



    2



    3 var1=abcd-1234-defg



    4 echo "var1 = $var1"



    5



    6 t=${var1#*-*}



    7 echo "var1 (with everything, up to and including first - stripped out) = $t"



    8 # t=${var1#*-} 在这个例子中作用是一样的,



    9 #+ 因为 # 匹配这个最近的字符串,



    10 #+ 并且 * 匹配前边的任何字符串,包括一个空字符.



    11 # (Thanks, Stephane Chazelas, for pointing this out.)



    12



    13 t=${var1##*-*}



    14 echo "If var1 contains a \"-\", returns empty string... var1 = $t"



    15



    16



    17 t=${var1%*-*}



    18 echo "var1 (with everything from the last - on stripped out) = $t"



    19



    20 echo



    21



    22 # -------------------------------------------



    23 path_name=/home/bozo/ideas/thoughts.for.today



    24 # -------------------------------------------



    25 echo "path_name = $path_name"



    26 t=${path_name##/*/}



    27 echo "path_name, stripped of prefixes = $t"



    28 # 在这个特定的例子中,与 t=`basename $path_name` 的作用一致.



    29 # t=${path_name%/}; t=${t##*/} 是一个更一般的解决办法,



    30 #+ 但有时还是不行.



    31 # 如果 $path_name 以一个新行结束, 那么`basename $path_name` 将不能工作,



    32 #+ 但是上边这个表达式可以.



    33 # (Thanks, S.C.)



    34



    35 t=${path_name%/*.*}



    36 # 与 t=`dirname $path_name` 效果相同.



    37 echo "path_name, stripped of suffixes = $t"



    38 # 在某些情况下将失效,比如 "../", "/foo////", # "foo/", "/".



    39 # 删除后缀,尤其是在basename没有后缀的时候,



    40 #+ 但是dirname还是会使问题复杂化.



    41 # (Thanks, S.C.)



    42



    43 echo



    44



    45 t=${path_name:11}



    46 echo "$path_name, with first 11 chars stripped off = $t"



    47 t=${path_name:11:5}



    48 echo "$path_name, with first 11 chars stripped off, length 5 = $t"



    49



    50 echo



    51



    52 t=${path_name/bozo/clown}



    53 echo "$path_name with \"bozo\" replaced by \"clown\" = $t"



    54 t=${path_name/today/}



    55 echo "$path_name with \"today\" deleted = $t"



    56 t=${path_name//o/O}



    57 echo "$path_name with all o's capitalized = $t"



    58 t=${path_name//o/}



    59 echo "$path_name with all o's deleted = $t"



    60



    61 exit 0



    ################################End Script#########################################







    ${var/#Pattern/Replacement}



    如果var的前缀匹配到了Pattern,那么就用Replacement来替换Pattern.







    ${var/%Pattern/Replacement}



    如果var的后缀匹配到了Pattern,那么就用Replacement来替换Pattern.







    Example 9-20 对字符串的前缀或后缀使用匹配模式



    ################################Start Script#######################################



    1 #!/bin/bash



    2 # var-match.sh:



    3 # 对字符串的前后缀使用匹配替换的一个样本



    4



    5 v0=abc1234zip1234abc # 原始变量.



    6 echo "v0 = $v0" # abc1234zip1234abc



    7 echo



    8



    9 # 匹配字符串的前缀



    10 v1=${v0/#abc/ABCDEF} # abc1234zip1234abc



    11 # |-|



    12 echo "v1 = $v1" # ABCDEF1234zip1234abc



    13 # |----|



    14



    15 # 匹配字符串的后缀



    16 v2=${v0/%abc/ABCDEF} # abc1234zip123abc



    17 # |-|



    18 echo "v2 = $v2" # abc1234zip1234ABCDEF



    19 # |----|



    20



    21 echo



    22



    23 # ----------------------------------------------------



    24 # 必须在开头或结尾匹配,否则,



    25 #+ 将不会产生替换结果.



    26 # ----------------------------------------------------



    27 v3=${v0/#123/000} # 匹配上了,但不是在字符串的开头



    28 echo "v3 = $v3" # abc1234zip1234abc



    29 # 没替换.



    30 v4=${v0/%123/000} # 匹配上了,但不是在字符串结尾.



    31 echo "v4 = $v4" # abc1234zip1234abc



    32 # 没替换.



    33



    34 exit 0



    ################################End Script#########################################







    ${!varprefix*}, ${!varprefix@}



    使用变量的前缀来匹配前边所有声明过的变量.



    1 xyz23=whatever



    2 xyz24=



    3



    4 a=${!xyz*} # 以"xyz"作为前缀,匹配所有前边声明过的变量.



    5 echo "a = $a" # a = xyz23 xyz24



    6 a=${!xyz@} # 同上.



    7 echo "a = $a" # a = xyz23 xyz24



    8



    9 # Bash, version 2.04, 添加了这个特征.







    注意事项:



    [1] 如果在一个非交互脚本中,$parameter为空的话,那么这个脚本将以127返回.



    (127退出码对应的Bash错误码为"command not found").















    9.4 指定类型的变量:declare或者typeset



    -------------------------------------



    declare或者typeset内建命令(这两个命令是完全一样的)允许指定变量的具体类型.在某些特



    定的语言中,这是一种指定类型的很弱的形式.declare命令是在Bash版本2或之后的版本才被



    加入的.typeset命令也可以工作在ksh脚本中.







    declare/typeset 选项







    -r 只读



    1 declare -r var1



    (declare -r var1与readonly var1是完全一样的)



    这和C语言中的const关键字一样,都是强制指定只读.如果你尝试修改一个只读变量



    的值,那么你将得到一个错误消息.







    -i 整形



    1 declare -i number



    2 # 这个脚本将把变量"number"后边的赋值视为一个整形.



    3



    4 number=3



    5 echo "Number = $number" # Number = 3



    6



    7 number=three



    8 echo "Number = $number" # Number = 0



    9 # 尝试把"three"解释为整形.







    如果把一个变量指定为整形,那么即使没有expr和let命令,也允许使用特定的算术运算



    1 n=6/3



    2 echo "n = $n" # n = 6/3



    3



    4 declare -i n



    5 n=6/3



    6 echo "n = $n" # n = 2







    -a 数组



    1 declae -a indices



    变量indices将被视为数组.







    -f 函数



    1 declare -f



    如果使用declare -f而不带参数的话,将会列出这个脚本中之前定义的所有函数.



    1 declare -f function_name



    如果使用declare -f function_name这种形式的话,将只会列出这个函数的名字.







    -x export



    1 declare -x var3



    这种使用方式,将会把var3 export出来.







    Example 9-21 使用declare来指定变量的类型



    ################################Start Script#######################################



    1 #!/bin/bash



    2



    3 func1 ()



    4 {



    5 echo This is a function.



    6 }



    7



    8 declare -f # 列出之前的所有函数.



    9



    10 echo



    11



    12 declare -i var1 # var1 是个整形.



    13 var1=2367



    14 echo "var1 declared as $var1"



    15 var1=var1+1 # 变量声明不需使用'let'命令.



    16 echo "var1 incremented by 1 is $var1."



    17 # 尝试将变量修改为整形.



    18 echo "Attempting to change var1 to floating point value, 2367.1."



    19 var1=2367.1 # 结果将是一个错误消息,并且变量并没有被修改.



    20 echo "var1 is still $var1"



    21



    22 echo



    23



    24 declare -r var2=13.36 # 'declare' 允许设置变量的属性,



    25 #+ 并且同时分配变量的值.



    26 echo "var2 declared as $var2" # 尝试修改只读变量.



    27 var2=13.37 # 产生一个错误消息,并且从脚本退出了.



    28



    29 echo "var2 is still $var2" # 这行将不会被执行.



    30



    31 exit 0 # 脚本将不会在此处退出.



    ################################End Script#########################################







    注意:使用declare内建命令将会限制变量的作用域.



    1 foo ()



    2 {



    3 FOO="bar"



    4 }



    5



    6 bar ()



    7 {



    8 foo



    9 echo $FOO



    10 }



    11



    12 bar # Prints bar.







    然而...



    1 foo (){



    2 declare FOO="bar"



    3 }



    4



    5 bar ()



    6 {



    7 foo



    8 echo $FOO



    9 }



    10



    11 bar # Prints nothing.



    12



    13



    14 # Thank you, Michael Iatrou, for pointing this out.















    9.5 变量的间接引用



    ------------------



    假设一个变量的值是另一个变量的名字.我们有可能从第一个变量中取得第2个变量的值么?



    比如,如果a=letter_of_alphabet接着letter_of_alphabet=z,那么我们能从a中得到z么?



    答案是:当然可以,并且这被称为间接引用.它使用一个不常用的符号eval var1=\$$var2.











    Example 9-22 间接引用



    ################################Start Script#######################################



    1 #!/bin/bash



    2 # ind-ref.sh: 间接变量引用



    3 # 存取一个变量的值的值(这里翻译得有点拗口,不过凑合吧)



    4



    5 a=letter_of_alphabet # 变量"a"的值是另一个变量的名字.



    6 letter_of_alphabet=z



    7



    8 echo



    9



    10 # 直接引用.



    11 echo "a = $a" # a = letter_of_alphabet



    12



    13 # 间接引用.



    14 eval a=\$$a



    15 echo "Now a = $a" # Now a = z



    16



    17 echo



    18



    19



    20 # 现在,让我们试试修改第2个引用的值.



    21



    22 t=table_cell_3



    23 table_cell_3=24



    24 echo "\"table_cell_3\" = $table_cell_3" # "table_cell_3" = 24



    25 echo -n "dereferenced \"t\" = "; eval echo \$$t # 解引用 "t" = 24



    26 # 在这个简单的例子中,下边的表达式也能正常工作(为什么?).



    27 # eval t=\$$t; echo "\"t\" = $t"



    28



    29 echo



    30



    31 t=table_cell_3



    32 NEW_VAL=387



    33 table_cell_3=$NEW_VAL



    34 echo "Changing value of \"table_cell_3\" to $NEW_VAL."



    35 echo "\"table_cell_3\" now $table_cell_3"



    36 echo -n "dereferenced \"t\" now "; eval echo \$$t



    37 # "eval" 将获得两个参数 "echo" 和 "\$$t" (与$table_cell_3等价)



    38



    39 echo



    40



    41 # (Thanks, Stephane Chazelas, 澄清了上边的行为.)



    42



    43



    44 # 另一个方法是使用${!t}符号,见"Bash, 版本2"小节.



    45 # 也请参阅ex78.sh.



    46



    47 exit 0



    ################################End Script#########################################



    间接应用到底有什么应用价值?它给Bash添加了一种类似于C语言指针的功能,在Example 34-3



    中有例子.并且,还有一些其它的有趣的应用....







    Nils Radtke展示了如何建立一个"dynamic"变量名字并且取出其中的值.当sourcing(包含)配置



    文件时,这很有用.



    ################################Start Script#######################################



    1 #!/bin/bash



    2



    3



    4 # ---------------------------------------------



    5 # 这部分内容可能来自于单独的文件.



    6 isdnMyProviderRemoteNet=172.16.0.100



    7 isdnYourProviderRemoteNet=10.0.0.10



    8 isdnOnlineService="MyProvider"



    9 # ---------------------------------------------



    10



    11



    12 remoteNet=$(eval "echo \$$(echo isdn${isdnOnlineService}RemoteNet)")



    13 remoteNet=$(eval "echo \$$(echo isdnMyProviderRemoteNet)")



    14 remoteNet=$(eval "echo \$isdnMyProviderRemoteNet")



    15 remoteNet=$(eval "echo $isdnMyProviderRemoteNet")



    16



    17 echo "$remoteNet" # 172.16.0.100



    18



    19 # ================================================================



    20



    21 # 同时,它甚至能更好.



    21 #



    22



    23 # 考虑下边的脚本,给出了一个变量getSparc,



    24 #+ 但是没给出变量getIa64:



    25



    26 chkMirrorArchs () {



    27 arch="$1";



    28 if [ "$(eval "echo \${$(echo get$(echo -ne $arch |



    29 sed 's/^\(.\).*/\1/g' | tr 'a-z' 'A-Z'; echo $arch |



    30 sed 's/^.\(.*\)/\1/g')):-false}")" = true ]



    31 then



    32 return 0;



    33 else



    34 return 1;



    35 fi;



    36 }



    37



    38 getSparc="true"



    39 unset getIa64



    40 chkMirrorArchs sparc



    41 echo $? # 0



    42 # True



    43



    44 chkMirrorArchs Ia64



    45 echo $? # 1



    46 # False



    47



    48 # 注意:



    49 # -----



    50 # Even the to-be-substituted variable name part is built explicitly.



    51 # The parameters to the chkMirrorArchs calls are all lower case.



    52 # The variable name is composed of two parts: "get" and "Sparc" . . .



    ################################End Script#########################################







    Example 9-23 传递一个间接引用给awk



    ################################Start Script#######################################



    1 #!/bin/bash



    2



    3 # "column totaler"脚本的另一个版本



    4 #+ 这个版本在目标文件中添加了一个特殊的列(数字的).



    5 # 这个脚本使用了间接引用.



    6



    7 ARGS=2



    8 E_WRONGARGS=65



    9



    10 if [ $# -ne "$ARGS" ] # 检查命令行参数是否是合适的个数.



    11 then



    12 echo "Usage: `basename $0` filename column-number"



    13 exit $E_WRONGARGS



    14 fi



    15



    16 filename=$1



    17 column_number=$2



    18



    19 #===== 上边的这部分,与原来的脚本一样 =====#



    20



    21



    22 # 一个多行的awk脚本被调用,通过 ' ..... '



    23



    24



    25 # awk 脚本开始.



    26 # ------------------------------------------------



    27 awk "



    28



    29 { total += \$${column_number} # 间接引用.



    30 }



    31 END {



    32 print total



    33 }



    34



    35 " "$filename"



    36 # ------------------------------------------------



    37 # awk 脚本结束.



    38



    39 # 间接的变量引用避免了在一个内嵌的awk脚本中引用



    40 #+ 一个shell变量的问题.



    41 # Thanks, Stephane Chazelas.



    42



    43



    44 exit 0



    ################################End Script#########################################



    注意: 这个脚本有些狡猾.如果第2个变量修改了它的值,那么第一个变量必须被适当的解引用



    (像上边的例子一样).幸运的是,在Bash版本2中引入的${!variable}(参见Example 34-2)



    是的间接引用更加直观了.







    注意: Bash并不支持指针的算术运算,并且这严格的限制了间接引用的使用.事实上,在脚本语言



    中,间接引用本来就是丑陋的部分.















    9.6 $RANDOM: 产生随机整数



    -------------------------



    $RANDOM是Bash的内部函数(并不是常量),这个函数将返回一个范围在0 - 32767之间的一个伪



    随机整数.它不应该被用来产生密匙.







    Example 9-24 产生随机数



    ################################Start Script#######################################



    1 #!/bin/bash



    2



    3 # $RANDOM 在每次调用的时候,返回一个不同的随机整数.



    4 # 指定的范围是: 0 - 32767 (有符号的16-bit 整数).



    5



    6 MAXCOUNT=10



    7 count=1



    8



    9 echo



    10 echo "$MAXCOUNT random numbers:"



    11 echo "-----------------"



    12 while [ "$count" -le $MAXCOUNT ] # 产生10 ($MAXCOUNT) 个随机整数.



    13 do



    14 number=$RANDOM



    15 echo $number



    16 let "count += 1" # 数量加1.



    17 done



    18 echo "-----------------"



    19



    20 # 如果你需要在一个特定范围内产生一个随机int,那么使用'modulo'(模)操作.



    21 # 这将返回一个除法操作的余数.



    22



    23 RANGE=500



    24



    25 echo



    26



    27 number=$RANDOM



    28 let "number %= $RANGE"



    29 # ^^



    30 echo "Random number less than $RANGE --- $number"



    31



    32 echo



    33



    34



    35



    36 # 如果你需要产生一个比你指定的最小边界大的随机数,



    37 #+ 那么建立一个test循环,来丢弃所有产生对比这个数小的随机数.



    38



    39 FLOOR=200



    40



    41 number=0 #initialize



    42 while [ "$number" -le $FLOOR ]



    43 do



    44 number=$RANDOM



    45 done



    46 echo "Random number greater than $FLOOR --- $number"



    47 echo



    48



    49 # 让我们对上边的循环尝试一个小改动,也就是



    50 # 让"number = $RANDOM + $FLOOR"



    51 # 这将不再需要那个while循环,并且能够运行得更快.



    52 # 但是, 这可能会产生一个问题.那么这个问题是什么呢?(译者:这很简单,有可能溢出)



    53



    54



    55



    56 # 结合上边两个例子的技术,来达到获得在指定的上下限之间来产生随机数.



    57 number=0 #initialize



    58 while [ "$number" -le $FLOOR ]



    59 do



    60 number=$RANDOM



    61 let "number %= $RANGE" # 让$number依比例落在$RANGE范围内.



    62 done



    63 echo "Random number between $FLOOR and $RANGE --- $number"



    64 echo



    65



    66



    67



    68 # 产生一个二元选择,就是"true"和"false"两个值.



    69 BINARY=2



    70 T=1



    71 number=$RANDOM



    72



    73 let "number %= $BINARY"



    74 # 注意,让"number >>= 14" 将给出一个更好的随机分配



    75 #+ (右移14位将把所有为全部清空,除了第15位,因为有符号,所以第16位是符号位).



    76 if [ "$number" -eq $T ]



    77 then



    78 echo "TRUE"



    79 else



    80 echo "FALSE"



    81 fi



    82



    83 echo



    84



    85



    86 # 抛骰子



    87 SPOTS=6 # 模6给出的范围就是0-5.



    88 # 加1就会得到期望的范围1 - 6.



    89 # Thanks, Paulo Marcel Coelho Aragao, for the simplification.



    90 die1=0



    91 die2=0



    92 # 是否让SPOTS=7比加1更好呢?解释行或者不行的原因?



    93



    94 # 每次抛骰子,都会给出均等的机会.



    95



    96 let "die1 = $RANDOM % $SPOTS +1" # 抛第一次.



    97 let "die2 = $RANDOM % $SPOTS +1" # 抛第二次.



    98 # 上边的那个算术操作,具有更高的优先级呢 --



    99 #+ 模操作(%)还是加法操作(+)?



    100



    101



    102 let "throw = $die1 + $die2"



    103 echo "Throw of the dice = $throw"



    104 echo



    105



    106



    107 exit 0



    ################################End Script#########################################      
    楼主辛苦了
    感谢那些热心的人

    不过PDF做得不是很好
    好像是简单地把txt转换了
    如果能像原版那样有目录,索引和超链也许会方便些?      
    出汉化了,方便了不少人~~