分类:
2010-01-03 22:25:21
摘自《高级bash脚本编程指南》
变量是脚本编程中进行数据表现的一种方法. 说白了, 变量不过是计算机为了保留数据项, 而在内存中分配的一个位置或一组位置的标识或名字.变量既可以出现在算术操作中, 也可以出现在字符串分析过程中.
变量的名字就是变量保存值的地方. 引用变量的值就叫做变量替换.
$
让我们仔细的区别变量的名字和变量的值. 如果variable1是一个变量的名字, 那么$variable1就是引用这变量的值, 即这边变量所包含的数据.
bash$ variable=123 bash$ echo variable variable bash$ echo $variable 123
当变量”裸体”出现的时候 — 也就是说没有 $ 前缀的时候 — 那么变量可能存在如下几种情况. 变量被声明或被赋值, 变量被unset, 变量被exporte, 或者是变量处在一种特殊的情况 — 变量代表一种信号. 变量赋值可以使用=(比如 var1=27), 也可以在read命令中或者循环头进行赋值 (for var2 in 1 2 3).
被一对双引号(” “)括起来的变量替换是不会被阻止的. 所以双引号被称为部分引用, 有时候又被称为”弱引用”. 但是如果使用单引号的话(‘ ‘), 那么变量替换就会被禁止了, 变量名只会被解释成字面的意思, 不会发生变量替换. 所以单引号被称为全引用, 有时候也被称为”强引用”.
注意 $variable 事实上只是 ${variable} 的简写形式. 在某些上下文中$variable可能会引起错误, 这时候你就需要用${variable}了.
例子 1. 变量赋值和替换
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 #% 脚本将尝试运行一个"value"的命令, 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 # 引用(引号括住)一个变量将保留其中的空白, 当然, 如果是变量替换就不会保留了. 39 # =============================================================== 40 41 echo 42 43 echo '$hello' # $hello 44 # ^ ^ 45 # 全引用的作用将会导致"$"被解释为单独的字符, 46 #+ 而不是变量前缀. 47 48 # 注意这两种引用所产生的不同的效果. 49 50 51 hello= # 设置为空值. 52 echo "\$hello (null value) = $hello" 53 # 注意设置一个变量为null, 与unset这个变量, 并不是一回事 54 #+ 虽然最终的结果相同(具体见下边). 55 56 # -------------------------------------------------------------- 57 58 # 可以在同一行上设置多个变量, 59 #+ 但是必须以空白进行分隔. 60 # 慎用, 这么做会降低可读性, 并且不可移植. 61 62 var1=21 var2=22 var3=$V3 63 echo 64 echo "var1=$var1 var2=$var2 var3=$var3" 65 66 # 在老版本的"sh"上可能会引起问题. 67 68 # -------------------------------------------------------------- 69 70 echo; echo 71 72 numbers="one two three" 73 # ^ ^ 74 other_numbers="1 2 3" 75 # ^ ^ 76 # 如果在变量中存在空白, If there is whitespace embedded within a variable, 77 #+ 那么就必须加上引用(引号). 78 # other_numbers=1 2 3 # 给出一个错误消息. 79 echo "numbers = $numbers" 80 echo "other_numbers = $other_numbers" # other_numbers = 1 2 3 81 # 不过也可以采用将空白转义的方法. 82 mixed_bag=2\ ---\ Whatever 83 # ^ ^ 在转义符后边的空格(\). 84 85 echo "$mixed_bag" # 2 --- Whatever 86 87 echo; echo 88 89 echo "uninitialized_variable = $uninitialized_variable" 90 # Uninitialized变量为null(就是没有值). 91 uninitialized_variable= # 声明, 但是没有初始化这个变量, 92 #+ 其实和前边设置为空值的作用是一样的. 93 echo "uninitialized_variable = $uninitialized_variable" 94 # 还是一个空值. 95 96 uninitialized_variable=23 # 赋值. 97 unset uninitialized_variable # Unset这个变量. 98 echo "uninitialized_variable = $uninitialized_variable" 99 # 还是一个空值. 100 echo 101 102 exit 0
一个未初始化的变量将会是”null”值 – 就是未赋值(但并不是代表值是0!). 在给变量赋值之前就使用这个变量通常都会引起问题.但是在执行算术操作的时候, 仍然有可能使用未初始化过的变量.
1 echo "$uninitialized" # (blank line) 2 let "uninitialized += 5" # Add 5 to it. 3 echo "$uninitialized" # 5 4 5 # 结论: 6 # 一个未初始化的变量是没有值的, 7 #+ 但是在做算术操作的时候, 这个未初始化的变量看起来值为0. 8 # 这是一个未文档化(并且可能不具可移植性)的行为.
=
赋值操作(前后都不能有空白)
因为 = 和 -eq 都可以用做条件测试操作, 所以不要与这里的赋值操作相混淆.
注意: = 既可以用做条件测试操作, 也可以用于赋值操作, 这需要视具体的上下文而定.(在测试结构中,= 用做条件测试操作)
例子 2. 简单的变量赋值
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
例子 3. 简单和复杂, 两种类型的变量赋值
1 #!/bin/bash 2 3 a=23 # 简单的赋值 4 echo $a 5 b=$a 6 echo $b 7 8 # 现在让我们来点小变化(命令替换). 9 10 a=`echo Hello!` # 把'echo'命令的结果传给变量'a' 11 echo $a 12 # 注意, 如果在#+的命令替换结构中包含一个(!)的话, 13 #+ 那么在命令行下将无法工作. 14 #+ 因为这触发了Bash的"历史机制." 15 # 但是, 在脚本中使用的话, 历史功能是被禁用的, 所以就能够正常的运行. 16 17 a=`ls -l` # 把'ls -l'的结果赋值给'a' 18 echo $a # 然而, 如果没有引号的话将会删除ls结果中多余的tab和换行符. 19 echo 20 echo "$a" # 如果加上引号的话, 那么就会保留ls结果中的空白符. 21 # (具体请参阅"引用"的相关章节.) 22 23 exit 0
使用$(…)机制来进行变量赋值(这是一种比后置引用(反引号`)更新的一种方法). 事实上这两种方法都是命令替换的一种形式.
1 # From /etc/rc.d/rc.local 2 R=$(cat /etc/redhat-release) 3 arch=$(uname -m)
Bash变量是不区分类型的
不像其他程序语言一样, Bash并不对变量区分”类型”. 本质上, Bash变量都是字符串. 但是依赖于具体的上下文, Bash也允许比较操作和整数操作. 其中的关键因素就是, 变量中的值是否只有数字.
不区分变量的类型既是幸运的事情也是悲惨的事情. 它允许你在编写脚本的时候更加的灵活(但是也足够把你搞晕!), 并且可以让你能够更容易的编写代码. 然而, 这也很容易产生错误, 并且让你养成糟糕的编程习惯.这样的话, 程序员就承担了区分脚本中变量类型的责任. Bash是不会为你区分变量类型的.
局部变量
这种变量只有在代码块或者函数中才可见.
环境变量
这种变量将影响用户接口和shell的行为
在通常情况下, 每个进程都有自己的”环境”, 这个环境是由一组变量组成的, 这些变量中存有进程可能需要引用的信息. 在这种情况下, shell与一个一般的进程没什么区别.
每次当一个shell启动时, 它都将创建适合于自己环境变量的shell变量. 更新或者添加一个新的环境变量的话, 这个shell都会立刻更新它自己的环境(译者注: 换句话说, 更改或增加的变量会立即生效), 并且所有的shell子进程(即这个shell所执行的命令)都会继承这个环境. (译者注: 准确地说, 应该是后继生成的子进程才会继承Shell的新环境变量, 已经运行的子进程并不会得到它的新环境变量).
注:分配给环境变量的空间是有限的. 创建太多环境变量, 或者给一个环境变量分配太多的空间都会引起错误.
如果一个脚本要设置一个环境变量, 那么需要将这些变量”export”出来, 也就是需要通知到脚本本地的环境. 这是 export 命令的功能.
一个脚本只能够export变量到这个脚本所产生的子进程, 也就是说只能够对这个脚本所产生的命令和进程起作用. 如果脚本是从命令行中调用的, 那么这个脚本所 export 的变量是不能影响命令行环境的. 也就是说, 子进程是不能够export变量来影响产生自己的父进程的环境的.
位置参数
从命令行传递到脚本的参数: $0, $1, $2, $3 . . .
$0就是脚本文件自身的名字, $1 是第一个参数, $2是第二个参数, $3是第三个参数, 然后是第四个. $9之后的位置参数就必须用大括号括起来了({}标记法), 比如, ${10}, ${11}, ${12}.
两个比较特殊的变量$*和$@ 表示所有的位置参数.
$* 所有的位置参数都被看作为一个单词( $* 以标量字符串(借用perl概念)的形式来存储位置参数)。 “$*” 必须被引用起来.
$@ 与 $* 相同, 但是每个参数都是一个独立的引用字符串, 这就意味着, 参数是被完整传递的, 并没有被解释或扩展. 这也意味着, 参数列表中每个参数都被看作为单独的单词。($@ 以类似于数组的形式来存储位置参数)当然, “$@” 应该被引用起来.
注:$@与$*中的参数只有在被双引号引用起来的时候才会不同.
例子 4. arglist: 通过$*和$@列出所有的参数
1 #!/bin/bash 2 # arglist.sh 3 # 多使用几个参数来调用这个脚本, 比如"one two 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 # $* 将所有的参数看成一个单词. 23 echo "Entire arg list seen as single word." 24 25 echo 26 27 index=1 # 重置计数(译者注: 从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 # 重置计数(译者注: 从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
例子 5. 位置参数
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
{}标记法提供了一种提取从命令行传递到脚本的最后一个位置参数的简单办法. 但是这种方法同时还需要使用间接引用.
1 args=$# # 位置参数的个数. 2 lastarg=${!args} 3 # 或: lastarg=${!#} 4 # (感谢, Chris Monson.) 5 # 注意, 不能直接使用 lastarg=${!$#} , 这会产生错误.
一些脚本可能会依赖于使用不同的调用名字, 来表现出不同的行为. 如果想要达到这种目的, 一般都需要在脚本中检查$0. 因为脚本只能够有一个真正的文件名, 如果要产生多个名字, 必须使用符号链接.
例子 6. wh, whois 节点名字查询
1 #!/bin/bash 2 # ex18.sh 3 4 # 是否'whois domain-name'能够找到如下3个服务之一: 5 # ripe.net, cw.net, radb.net 6 7 # 把这个脚本重命名为'wh', 然后放到/usr/local/bin目录下. 8 9 # 需要符号链接: 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 # 检查脚本名字, 然后调用合适的服务. 24 case `basename $0` in # Or: case ${0##*/} in 25 "wh" ) whois $1@whois.ripe.net;; 26 "wh-ripe") whois $1@whois.ripe.net;; 27 "wh-radb") whois $1@whois.radb.net;; 28 "wh-cw" ) whois $1@whois.cw.net;; 29 * ) echo "Usage: `basename $0` [domain-name]";; 30 esac 31 32 exit $?
shift 命令会重新分配位置参数, 其实就是把所有的位置参数都向左移动一个位置.
$1 <--- $2, $2 <--- $3, $3 <--- $4, 等等.
原来的$1就消失了, 但是$0 (脚本名)是不会改变的. 如果传递了大量的位置参数到脚本中, 那么shift命令允许你访问的位置参数的数量超过10个, 当然{}标记法也提供了这样的功能.
例子 7. 使用 shift 命令
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
其他特殊参数
$! 运行在后台的最后一个作业的PID(进程ID)
$_ 这个变量保存之前执行的命令的最后一个参数的值.
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 $_ # :
$? 命令, 函数, 或者是脚本本身的退出状态码
$$ 脚本自身的进程ID. $$变量在脚本中经常用来构造”唯一的”临时文件名.