Shell基础脚本-变量与参数
1、变量替换
变量名是其所指向值的一个占位符(placeholder)。引用变量值的过程我们称之为变量替换(variable substitution)
$
接下来我们仔细区分一下变量名与变量值。如果变量名是 variable1, 那么 $variable1 就是对变量值的引用。
bash$ variable1=23
bash$ echo variable1
variable1
bash$ echo $variable1
23
变量仅仅在声明时、赋值时、被删除时(unset)、被导出时(export),算术运算中使用双括号结构((...))时或在代表信号时(signal,查看样例 32-5)才不需要有 $ 前缀。赋值可以是使用 =(比如 var1=27),可以是在 read 语句中,也可以是在循环的头部(for var2 in 1 2 3)
在双引号""字符串中可以使用变量替换。
我们称之为部分引用,有时候也称弱引用。
而使用单引号''引用时,变量只会作为字符串显示,变量替换不会发生。
我们称之为全引用,有时也称强引用
实际上, $variable 这种写法是 ${variable} 的简化形式。
在某些特殊情况下,使用 $variable 写法会造成语法错误,使用完整形式会更好
样例-1. 变量赋值与替换
#!/bin/bash
# ex9.sh
# 变量赋值与替换
a=375
hello=$a
# ^ ^
#----------------------------------------------------
# 初始化变量时,赋值号 = 的两侧绝不允许有空格出现。
# 如果有空格会发生什么?
# "VARIABLE =value"
# ^
#% 脚本将会尝试运行带参数 "=value" 的 "VARIABLE " 命令。
# "VARIABLE= value"
# ^
#% 脚本将会尝试运行 "value" 命令,
#+ 同时设置环境变量 "VARIABLE" 为 ""。
#----------------------------------------------------
echo hello # hello
# 没有引用变量,"hello" 只是一个字符串...
echo $hello # 375
# ^ 这是变量引用。
echo ${hello} # 375
# 与上面的类似,变量引用。
# 字符串内引用变量
echo "$hello" # 375
echo "${hello}" # 375
echo
hello="A B C D"
echo $hello # A B C D
echo "$hello" # A B C D
# 正如我们所见,echo $hello 与 echo "$hello" 的结果不同。
# ====================================
# 字符串内引用变量将会保留变量的空白符。
# ====================================
echo
echo '$hello' # $hello
# ^ ^
# 单引号会禁用掉(转义)变量引用,这导致 "$" 将以普通字符形式被解析。
# 注意单双引号字符串引用效果的不同。
hello= # 将其设置为空值
echo "\$hello (null value) = $hello" # $hello (null value) =
# 注意
# 将一个变量设置为空与删除(unset)它不同,尽管它们的表现形式相同。
# -----------------------------------------------
# 使用空白符分隔,可以在一行内对多个变量进行赋值。
# 但是这会降低程序的可读性,并且可能会导致部分程序不兼容的问题。
var1=21 var2=22 var3=$V3
echo
echo "var1=$var1 var2=$var2 var3=$var3"
# 在一些老版本的 shell 中这样写可能会有问题。
# -----------------------------------------------
echo; echo
numbers="one two three"
# ^ ^
other_numbers="1 2 3"
# ^ ^
# 如果变量中有空白符号,那么必须用引号进行引用。
# other_numbers=1 2 3 # 出错
echo "numbers = $numbers"
echo "other_numbers = $other_numbers" # other_numbers = 1 2 3
# 也可以转义空白符。
mixed_bag=2\ ---\ Whatever
# ^ ^ 使用 \ 转义空格
echo "$mixed_bag" # 2 --- Whatever
echo; echo
echo "uninitialized_variable = $uninitialized_variable"
# 未初始化的变量是空值(null表示不含有任何值)。
uninitialized_variable= # 只声明而不初始化,等同于设为空值。
echo "uninitialized_variable = $uninitialized_variable" # 仍旧为空
uninitialized_variable=23 # 设置变量
unset uninitialized_variable # 删除变量
echo "uninitialized_variable = $uninitialized_variable"
# uninitialized_variable =
# 变量值为空
echo
exit 0
一个未被赋值或未初始化的变量拥有空值(null value)
注意:null值不等同于0。
if [ -z "$unassigned" ]
then
echo "\$unassigned is NULL."
fi # $unassigned is NULL.
在赋值前使用变量可能会导致错误。但在算术运算中使用未赋值变量是可行的
echo "$uninitialized" # 空行
let "uninitialized += 5" # 加5
echo "$uninitialized" # 5
# 结论:
# 一个未初始化的变量不含值(null),但在算术运算中会被作为0处理。
2、变量赋值
( = )
赋值操作符(在其前后没有空白符)
不要混淆 = 与 -eq,后者用来进行比较而非赋值。
同时也要注意 = 根据使用场景既可作赋值操作符,也可作比较操作符
样例-2. 变量赋值
#!/bin/bash
# 非引用形式变量
echo
# 什么时候变量是非引用形式,即变量名前没有 '$' 符号的呢?
# 当变量在被赋值而不是被引用时。
# 赋值
a=879
echo "The value of \"a\" is $a."
# 使用 'let' 进行赋值
let a=16+5
echo "The value of \"a\" is now $a."
echo
# 在 'for' 循环中赋值(隐式赋值)
echo -n "Values of \"a\" in the loop are: "
for a in 7 8 9 11
do
echo -n "$a "
done
echo
echo
# 在 'read' 表达式中(另一种赋值形式)
echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."
echo
exit 0
样例-3. 奇妙的变量赋值
#!/bin/bash
a=23 # 简单形式
echo $a
b=$a
echo $b
# 来我们玩点炫的(命令替换)。
a=`echo Hello!` # 将 'echo' 命令的结果赋值给 'a'
echo $a
# 注意在命令替换结构中包含感叹号(!)在命令行中使用将会失效,
#+ 因为它将会触发 Bash 的历史(history)机制。
# 在shell脚本内,Bash 的历史机制默认关闭。
a=`ls -l` # 将 'ls -l' 命令的结果赋值给 'a'
echo $a # 不带引号引用,将会移除所有的制表符与分行符
echo
echo "$a" # 引号引用变量将会保留空白符
# 查看 "引用" 章节。
exit 0
使用 $(...) 形式进行赋值(与反引号不同的新形式),与命令替换形式相似
# 摘自 /etc/rc.d/rc.local
R=$(cat /etc/redhat-release)
arch=$(uname -m)
3、Bash弱类型变量
不同于许多其他编程语言,Bash 并不区分变量的类型。本质上说,Bash 变量是字符串,但在某些情况下,Bash 允许对变量进行算术运算和比较。决定因素则是变量值是否只含有数字。
样例-1. 整数还是字符串?
#!/bin/bash
# int-or-string.sh
a=2334 # 整数。
let "a += 1"
echo "a = $a " # a = 2335
echo # 依旧是整数。
b=${a/23/BB} # 将 "23" 替换为 "BB"。
# $b 变成了字符串。
echo "b = $b" # b = BB35
declare -i b # 将其声明为整数并没有什么卵用。
echo "b = $b" # b = BB35
let "b += 1" # BB35 + 1
echo "b = $b" # b = 1
echo # Bash 认为字符串的"整数值"为0。
c=BB34
echo "c = $c" # c = BB34
d=${c/BB/23} # 将 "BB" 替换为 "23"。
# $d 变为了一个整数。
echo "d = $d" # d = 2334
let "d += 1" # 2334 + 1
echo "d = $d" # d = 2335
echo
# 如果是空值会怎样呢?
e='' # ...也可以是 e="" 或 e=
echo "e = $e" # e =
let "e += 1" # 空值是否允许进行算术运算?
echo "e = $e" # e = 1
echo # 空值变为了一个整数。
# 如果时未声明的变量呢?
echo "f = $f" # f =
let "f += 1" # 是否允许进行算术运算?
echo "f = $f" # f = 1
echo # 未声明变量变为了一个整数。
#
# 然而……
let "f /= $undecl_var" # 可以除以0么?
# let: f /= : syntax error: operand expected (error token is " ")
# 语法错误!在这里 $undecl_var 并没有被设置为0!
#
# 但是,仍旧……
let "f /= 0"
# let: f /= 0: division by 0 (error token is "0")
# 预期之中。
# 在执行算术运算时,Bash 通常将其空值的整数值设为0。
# 但是不要做这种事情!
# 因为这可能会导致一些意外的后果。
# 结论:上面的结果都表明 Bash 中的变量是弱类型的。
exit $?
弱类型变量有利有弊。它可以使编程更加灵活、更加容易(给与你足够的想象空间)。但它也同样的容易造成一些小错误,容易养成粗心大意的编程习惯。
为了减轻脚本持续跟踪变量类型的负担,Bash 不允许变量声明。
4、特殊变量类型
局部变量
仅在代码块或函数中才可见的变量(参考函数章节的局部变量部分)
环境变量
会影响用户及shell行为的变量
一般情况下,每一个进程都有自己的“环境”(environment),也就是一组该进程可以访问到的变量。
从这个意义上来说,shell表现出与其他进程一样的行为。
每当shell启动时,都会创建出与其环境对应的shell环境变量。
改变或增加shell环境变量会使shell更新其自身的环境。
*子进程*(由父进程执行产生)会继承*父进程*的环境变量。
分配给环境变量的空间是有限的。
创建过多环境变量或占用空间过大的环境变量有可能会造成问题。
bash$ eval "`seq 10000 | sed -e 's/.*/export var&=ZZZZZZZZZZZZZZ/'`"
bash$ du
bash: /usr/bin/du: Argument list too long
注意,上面的"错误"已经在Linux内核版本号为2.6.23的系统中修复了。
如果在脚本中设置了环境变量,那么这些环境变量需要被“导出”,也就是通知脚本所在的环境做出相应的更新。这个“导出”操作就是 export 命令。
脚本只能将变量导出到子进程,即在这个脚本中所调用的命令或程序。
在命令行中调用的脚本不能够将变量回传给命令行环境,即子进程不能将变量回传给父进程。
定义: 子进程(child process)是由另一个进程,即其父进程(parent process)所启动的子程序。
位置参数
从命令行中传递给脚本的参数:$0, $1, $2, $3 ... 即命令行参数
$0 代表脚本名称,$1 代表第一个参数,$2 代表第二个,$3 代表第三个,以此类推。
在 $9 之后的参数必须被包含在大括号中,如 ${10}, ${11}, ${12}
特殊变量 $* 与 $@ 代表所有位置参数
样例-1 位置参数
#!/bin/bash
# 调用脚本时使用至少10个参数,例如
# ./scriptname 1 2 3 4 5 6 7 8 9 10
MINPARAMS=10
echo
echo "The name of this script is \"$0\"."
# 附带 ./ 代表当前目录
echo "The name of this script is \"`basename $0`\"."
# 除去路径信息(查看 'basename')
echo
if [ -n "$1" ] # 测试变量是否存在
then
echo "Parameter #1 is $1" # 使用引号转义#
fi
if [ -n "$2" ]
then
echo "Parameter #2 is $2"
fi
if [ -n "$3" ]
then
echo "Parameter #3 is $3"
fi
# ...
if [ -n "${10}" ] # 大于 $9 的参数必须被放在大括号中
then
echo "Parameter #10 is ${10}"
fi
echo "-----------------------------------"
echo "All the command-line parameters are: "$*""
if [ $# -lt "$MINPARAMS" ]
then
echo
echo "This script needs at least $MINPARAMS command-line arguments!"
fi
echo
exit 0
在位置参数中使用大括号助记符提供了一种非常简单的方式来访问传入脚本的最后一个参数。在其中会使用到间接引用
args=$# # 传入参数的个数
lastarg=${!args}
# 这是 $args 的一种间接引用方式
# 也可以使用: lastarg=${!#} (感谢 Chris Monson.)
# 这是 $# 的一种间接引用方式。
# 注意 lastarg=${!$#} 是无效的。
一些脚本能够根据调用时文件名的不同来执行不同的操作。
要达到这样的效果,脚本需要检测 $0,也就是调用时的文件名。
同时,也必须存在指向这个脚本所有别名的符号链接文件(symbolic links)
如果一个脚本需要一个命令行参数但是在调用的时候却没有传入,那么这将会造成一个空变量赋值。
这通常不是我们想要的。
[一种避免的方法是,在使用期望的位置参数时候,在赋值语句两侧添加一个额外的字符
variable1_=$1_ # 而不是 variable1=$1
# 使用这种方法可以在没有位置参数的情况下避免产生错误。
critical_argument01=$variable1_
# 多余的字符可以被去掉,就像下面这样:
variable1=${variable1_/_/}
# 仅仅当 $variable1_ 是以下划线开头时候才会有一些副作用。
# 这里使用了我们稍后会介绍的参数替换模板中的一种。
# (将替换模式设为空等价于删除。)
# 更直接的处理方法就是先检测预期的位置参数是否被传入。
if [ -z $1 ]
then
exit $E_MISSING_POS_PARAM
fi
# 但是,正如 Fabin Kreutz 指出的,
#+ 上面的方法会有一些意想不到的副作用。
# 更好的方法是使用参数替换:
# ${1:-$DefaultVal}
# 详情查看第十章“操作变量”的第二节“变量替换”。
样例-2. wh, whois 域名查询
#!/bin/bash
# ex18.sh
# 在下面三个可选的服务器中进行 whois 域名查询:
# ripe.net, cw.net, radb.net
# 将这个脚本重命名为 'wh' 后放在 /usr/local/bin 目录下
# 这个脚本需要进行符号链接:
# ln -s /usr/local/bin/wh /usr/local/bin/wh-ripe
# ln -s /usr/local/bin/wh /usr/local/bin/wh-apnic
# ln -s /usr/local/bin/wh /usr/local/bin/wh-tucows
E_NOARGS=75
if [ -z "$1" ]
then
echo "Usage: `basename $0` [domain-name]"
exit $E_NOARGS
fi
# 检查脚本名,访问对应服务器进行查询。
case `basename $0` in # 也可以写: case ${0##*/} in
"wh" ) whois $1@whois.tucows.com;;
"wh-ripe" ) whois $1@whois.ripe.net;;
"wh-apnic" ) whois $1@whois.apnic.net;;
"wh-cw" ) whois $1@whois.cw.net;;
* ) echo "Usage: `basename $0` [domain-name]";;
esac
exit $?
使用 shift 命令可以将全体位置参数向左移一位, 重新赋值
$1 <--- $2, $2 <--- $3, $3 <--- $4,以此类推
原先的 $1 将会消失,而 $0(脚本名称)不会有任何改变。
如果你在脚本中使用了大量的位置参数,
shift 可以让你不使用{大括号}助记法也可以访问超过10个的位置参数
样例-3. 使用 shift 命令
#!/bin/bash
# shft.sh: 使用 `shift` 命令步进访问所有的位置参数。
# 将这个脚本命名为 shft.sh,然后在调用时跟上一些参数。
# 例如:
# sh shft.sh a b c def 83 barndoor
until [ -z "$1" ] # 直到访问完所有的参数
do
echo -n "$1 "
shift
done
echo # 换行。
# 那些被访问完的参数又会怎样呢?
echo "$2"
# 什么都不会被打印出来。
# 当 $2 被移动到 $1 且没有 $3 时,$2 将会保持空。
# 因此 shift 是移动参数而非复制参数。
exit
shift 命令也可以带一个参数来指明一次移动多少位
#!/bin/bash
# shift-past.sh
shift 3 # 移动3位。
# 与 n=3; shift $n 效果相同。
echo "$1"
exit 0
# ======================== #
$ sh shift-past.sh 1 2 3 4 5
4
# 但是就像 Eleni Fragkiadaki 指出的那样,
# 如果尝试将位置参数($#)传给 'shift',
# 将会导致脚本错误的结束,同时位置参数也不会发送改变。
# 这也许是因为陷入了一个死循环...
# 比如:
# until [ -z "$1" ]
# do
# echo -n "$1 "
# shift 20 # 如果少于20个位置参数,
# done #+ 那么循环将永远不会结束。
#
# 当你不确定是否有这么多的参数时,你可以加入一个测试:
# shift 20 || break
# ^^^^^^^^
使用 shift 命令同给函数传参相类似