高级shell编程技巧4(转载)
时间:2006-06-02 来源:lin_lin13
第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 [email protected];;
26 "wh-ripe") whois [email protected];;
27 "wh-radb") whois [email protected];;
28 "wh-cw" ) whois [email protected];;
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页.