第四章:变量和参数介绍
变量 是脚本编程中的如何进行数据表现的办法. 它们可以在算术计算中作为操作数,在一个字符串表达式中作为符号表达抽象的意义或是其他的其它意义。变量是表示计算机内存中保存一种数据需要占的一个位置或一组的位置的标识。
4.1. 变量替换
-
变量的名字是它的值保存的地方。引用它的值称为变量替换(variable substitution)。.
- $
-
让我们仔细地区别变量和变量的值。如果variable1是一个变量的名字,那么$variable1就是引用这个变量的值――即这个变量它包含的数据。如果只有变量名出现(即指没有前缀$),那就可能是在1)声明一个变量或是在给这个变量赋值。2)声明废弃这个变量,3)导出(exported)变量,4)或是在变量指示的是一种信号的特殊情况。(参考例子 29-5)。变量赋值可以使用等于号(=),比如:var1=27。也可在read命令和在一个循环的情况下赋值,比如:for var2 in 1 2 3。
在一个双引号(" ")里的变量引用不会禁止变量替换。所以双引号被称为部分引用,有时也称为"弱引用"。
而在一个单引号里(' ')的变量替换是被禁止的,变量名只被解释为普通的字面意思。所以单引号被称为"全局引用",有时也被称为强引用。从第五章可得到更多的细节讨论。.
注意$variable实际上只是${variable}的简单的简写形式。在某些场合使用$variable形式会引起错误,这时你可能需要使用${variable}的形式了。(参考下面的9.3节).
例子 4-1. 变量赋值与替换
1 #!/bin/bash 2 3 # Variables: 赋值和替换 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 echo 41 42 echo '$hello' # $hello 43 # ^ ^ 44 # 在单引号中的变量引用会被禁止, 45 #+ 字符"$"会仅仅被认为是一个普通的字符,而不是变量的前缀. 46 47 # 注意不同引用的不同效果. 48 49 50 hello= # Setting it to a null value. 51 echo "\$hello (null value) = $hello" 52 # 注意具有null值的变量不等同于废弃(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 # 未初始化的变量具有null值 (即是没有值). 83 uninitialized_variable= # 声明,但没有初始化它 -- 84 #+ 这就好像上面一样给它设置一个null 值 85 echo "uninitialized_variable = $uninitialized_variable" 86 # 它仍然是null值. 87 88 uninitialized_variable=23 # 赋值 89 unset uninitialized_variable # 销毁变量. 90 echo "uninitialized_variable = $uninitialized_variable" 91 # 结果仍然是null值. 92 echo 93 94 exit 0
一个未初始化的变量有一个”null”值――表示从没有被赋值过(注意null值不等于零)。在一个变量从未赋值之前就使用它通常会引起问题。
然而,仍然有可能在执行算术计算时使用一个未初始化的变量。
1 echo "$uninitialized" # (blank line) 2 let "uninitialized += 5" # Add 5 to it. 3 echo "$uninitialized" # 5 4 5 # 结论: 6 # 一个未初始化的变量没有值, 7 #+ 但是似乎它在算术计算中的值是零。 8 # 这个无法证实(也可能是不可移植)的行为。
参考例子 11-21.
4.2. 变量赋值
- =
-
赋值操作符(它的左右两边不能有空白符)
不要搞混了=和-eq,-eq是比赋值操作更高级的测试。
注意:等于号(=)根据环境的不同它可能是赋值操作符也可能是一个测试操作符。
例子 4-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
例子 4-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 # 如果没有引号,则会删除多余tab键和空白符 19 echo 20 echo "$a" # 加了双引号,则能够原样保留空白符 21 # (参考"引用"章节) 22 23 exit 0
变量赋值也可以使用$(...) 机制(它是比斜引号更新的方法). 它实际是命令替换的一种形式.
1 # 摘自/etc/rc.d/rc.local 2 R=$(cat /etc/redhat-release) 3 arch=$(uname -m)
4.3. Bash变量是无类型的
不同与许多其他的编程语言,Bash不以"类型"来区分变量。本质上来说,Bash变量是字符串,但是根据环境的不同,Bash允许变量有整数计算和比较。其中的决定因素是变量的值是不是只含有数字.
例子 4-4. 整数还是字符串?
1 #!/bin/bash 2 # int-or-string.sh: Integer or string? 3 4 a=2334 # 整数. 5 let "a += 1" 6 echo "a = $a " # a = 2335 7 echo # 仍然是整数. 8 9 10 b=${a/23/BB} # 把变量a中的"23"替换为"BB"并赋给变量b 11 # 这使变量$b成为字符串 12 echo "b = $b" # b = BB35 13 declare -i b # 即使明确地声明它是整数也没有用 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} # 把"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 # What about null variables? 30 e="" 31 echo "e = $e" # e = 32 let "e += 1" # 数值计算允许有null值操作? 33 echo "e = $e" # e = 1 34 echo # 空值(null)变量变成了整数 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
变量没有类型既是幸运的也是悲惨的。它使脚本编程时有更多的弹性(但也可能把你弄晕)并能很容易地写出代码。但是,这也很容易不小心犯错误和养成坏的编程习惯。
程序员的负担就是要清楚地知道脚本中变量的类型。Bash不会帮你检查。
4.4. 特殊变量类型
- 局部变量
- 环境变量
-
这种变量会影响Shell的行为和用户接口
在大多数情况下,每个进程都会有一个"环境表", 它由一组由进程使用的环境变量组成。这样看来,Shell看起来和其他的进程一样。
每次一个Shell启动时,它都会创建新的合适的环境变量。如果它增加或是更新一个环境变量,都会使这个Shell的环境表得到更新(译者注:换句话说,更改或增加的变量会立即生效),并且这个Shell所有的子进程(即它执行的命令)能继承它的环境变量。(译者注:准确地说,应该是后继生成的子进程才会继承Shell的新环境变量,已经运行的子进程并不会得到它的新环境变量)。
分配给环境变量的总空间是有限的,如果创建太多的环境变量或有些环境变量的值太长而占用太多空间会出错。
如果一个脚本要设置一个环境变量,则需要将它导出(”exported”),也就是说要通知到脚本的环境表。这就是export命令的功能。
一个脚本只能导出(export)变量到子进程,也就是说只能导出到由此脚本生成的命令或进程中。在一个命令行中运行的脚本不能导出一个变量影响到命令行的环境。子进程不能导出变量到生成它的父进程中。
位置参数
命令行传递给脚本的参数是: $0, $1, $2, $3 . . .
1是第一个参数, 3是第三个,以此类推。[1] After $9, 在位置参数$9之后的参数必须用括号括起来,例如:${10}, ${11}, ${12}.特殊变量
@ 表示所有的位置参数。例子 4-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 5 # 注意lastarg=${!$#}是不会工作的.
由不同的执行名字来调用脚本,一些脚本能够以不同的操作来执行。如果要能办到这一点,脚本需要检查变量$0来确定脚本是如何被调用的。也有可能存在符号链接的路径来调用脚本的情况。参考例子 12-2.
如果一个脚本需要提供一个命令行参数来执行,但是这个参数没有提供,这可能引起一个空值(null值)被赋给一个脚本中使用的变量,从而使脚本产生非预期的效果. 防止这种情况的一种方法是使用预期的位置参数在赋值语句的两侧附加一个额外的字符。
1 variable1_=$1_ # 而不是 variable1=$1 2 # 这将防止错误,即使位置参数不存在。 3 4 critical_argument01=$variable1_ 5 6 # 之后可以去掉多余的字符,像这样。 7 variable1=${variable1_/_/} 8 # 仅当 $variable1_ 以下划线开头时才有副作用。 9 # 这使用后面讨论的参数替换模板之一。 10 # (省略替换模式会导致删除。) 11 12 # 更直接的处理方法是 13 #+ 简单测试预期的位置参数是否已经通过。 14 if [ -z $1 ] 15 then 16 exit $E_MISSING_POS_PARAM 17 fi 18 19 20 # 然而,正如 Fabian Kreutz 指出的那样, 21 #+ 上述方法可能会有意想不到的副作用。 22 # 更好的方法是参数替换: 23 # ${1:-$DefaultVal} 24 # 见“参数替换”部分 25 #+ 在“变量替换”一章中。
例子 4-6. wh,whois 查询服务器信息
1 #!/bin/bash 2#ex18.sh 3 4 # 在 3 个备用服务器中的任何一个上进行 'whois domain-name' 查找: 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的位置参数, 虽然这个功能也能由{bracket}花括号 做到.
例子 4-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
同样,shift命令也可以在需要传递一些参数的函数上以类似的方式工作.参考例子 33-15.
注:
[1]$0参数由调用脚本的进程设置。依照习惯,这个参数是脚本的名字。详细的细节可以查看C函数execv的手册页。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律