Bash中的变量

2015.07.07 | Comments

变量是脚本编程中进行数据表现的一种方法。说白了,变量不过是计算机为了保留数据项,而在内存中分配的一个位置或一组位置的标识或名字。变量既可以出现在算术操作中,也可以出现在字符串分析过程中。

变量赋值

变量使用=来实现赋值操作,前后都不能有空白。例如:

a=314
echo "The value of \"a\" is $a."

也可以使用let来赋值:

let a=16+5
echo "The value of \"a\" is now $a."

使用read命令进行赋值:

echo -n "Enter \"a\" "
read a
echo "The value of \"a\" is now $a."

下面是复杂一点的赋值:

a=23            
echo $a
b=$a
echo $b

我们也可以使用命令替换来赋值:

a=`echo Hello!`   # 把'echo'命令的结果传给变量'a'
echo $a

a=`ls -l`         # 把'ls -l'的结果赋值给'a'
echo $a        # 然而, 如果没有引号的话将会删除ls结果中多余的tab和换行符.
echo "$a"      # 如果加上引号的话, 那么就会保留ls结果中的空白符.

注意: 上面的代码有无引号,在linux系统中,运行结果是不一样的;在mac系统上,结果是一致的。

也可以使用$(...)机制来进行变量赋值,这其实还是命令替换的一种形式。例如:

# From /etc/rc.d/rc.local
R=$(cat /etc/redhat-release)
arch=$(uname -m)

变量的类型

不像其他程序语言一样,Bash并不对变量区分”类型”。本质上,Bash变量都是字符串。但是依赖于具体的上下文,Bash也允许比较操作和整数操作。其中的关键因素就是,变量中的值是否只有数字。

整型的变量:

a=2334                   # 整型.
let "a += 1"
echo "a = $a "        # a = 2335

把变量编程字符串:

b=${a/23/BB}     # 将"23"替换成"BB"
echo "b = $b"     # b = BB35

对字符串类型变量计算加法:

let "b += 1"             # BB35 + 1 =
echo "b = $b"        # b = 1

将上面过程反过来操作:

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

null变量可以用在算术操作中,下面例子将null变量转换成一个整型变量:

e=""
echo "e = $e"            # e =
let "e += 1"            # 算术操作允许一个null变量?
echo "e = $e"            # e = 1

对于没有声明的变量,也可以转换成一个整型变量:

echo "f = $f"               # f =
let "f += 1"                # 算术操作能通过么?
echo "f = $f"               # f = 1

不区分变量的类型既是幸运的事情也是悲惨的事情。它允许你在编写脚本的时候更加的灵活(但是也足够把你搞晕!),并且可以让你能够更容易的编写代码。然而,这也很容易产生错误,并且让你养成糟糕的编程习惯。

另外,变量还分局部变量和环境变量。局部变量只在代码块或者函数中可见;环境变量将影响用户接口和shell行为。

在通常情况下,每个进程都有自己的”环境”,这个环境是由一组变量组成的,这些变量中存有进程可能需要引用的信息。在这种情况下,shell与一个一般的进程没什么区别。

但是,分配给环境变量的空间是有限的。创建太多环境变量,或者给一个环境变量分配太多的空间都会引起错误。

如果一个脚本要设置一个环境变量,那么需要将这些变量export出来,也就是需要通知到脚本本地的环境。但是,子进程是不能够export变量来影响产生自己的父进程的环境的。

位置参数

变量是有位置参数的,从命令行传递到脚本的参数: $0, $1, $2, $3 . . .

$0就是脚本文件自身的名字,$1是第一个参数,$2是第二个参数,$3是第三个参数,然后是第四个。$9之后的位置参数就必须用大括号括起来了,比如:${10}, ${11}, ${12}

两个比较特殊的变量$*$@表示所有的位置参数。

{}标记法提供了一种提取从命令行传递到脚本的最后一个位置参数的简单办法,但是这种方法同时还需要使用间接引用

args=$#           # 位置参数的个数.
lastarg=${!args}
# 或:  lastarg=${!#}
# 注意,不能直接使用 lastarg=${!$#} ,这会产生错误

如果脚本需要一个命令行参数,而在调用的时候,这个参数没被提供,那么这就可能造成给这个参数赋一个null变量,通常情况下,这都会产生问题。一种解决这个问题的办法就是使用添加额外字符的方法,在使用这个位置参数的变量和位置参数本身的后边全部添加同样的额外字符。

variable1_=$1_  # 而不是 variable1=$1
# 这将阻止报错, 即使在调用时没提供这个位置参数

critical_argument01=$variable1_

#使用正则表达式替换_为空,得到输入的变量
variable1=${variable1_/_/}

另外,一种方法是判断是否存在:

if [ -z $1 ]
then
    exit $E_MISSING_POS_PARAM
fi

更好的方法是使用参数替换:

DefaultVal=xxxx
${1:-$DefaultVal}

shift命令会重新分配位置参数,其实就是把所有的位置参数都向左移动一个位置。这样的话,原来的$1就消失了,但是$0(脚本名)是不会改变的。如果传递了大量的位置参数到脚本中,那么shift命令允许你访问的位置参数的数量超过10个,当然{}标记法也提供了这样的功能。

下面是使用shift命令的例子:

#!/bin/bash
# 使用'shift'来逐步存取所有的位置参数. 

#  给脚本命个名,比如shft,然后给脚本传递一些位置参数, 比如: 
#          ./shft a b c def 23 skidoo

until [ -z "$1" ]  # 直到所有的位置参数都被存取完...
do
    echo -n "$1 "
    shift
done
 
echo                # 额外的换行.
 
exit 0

当然,shift命令也可以用在函数当中。

multiply ()                     # 将乘数作为参数传递进来. 
{                                     # 可以接受多个参数. 
 
local product=1

until [ -z "$1" ]               # 直到处理完所有的参数...
do
    let "product *= $1"
    shift
done

echo $product               #  不会echo到stdout
}

变量替换

变量的名字就是变量保存值的地方,引用变量的值就叫做变量替换。

如果variable1是一个变量的名字,那么$variable1就是引用这变量的值,即这边变量所包含的数据。

没有$前缀的时候,变量可能存在如下几种情况:

  • 变量被声明或被赋值
  • 变量被unset
  • 变量被export
  • 变量代表一种信号

变量赋值可以使用=,也可以在read命令中或者循环头进行赋值,例如for var2 in 1 2 3

注意,$variable事实上只是${variable}的简写形式。在某些上下文中$variable可能会引起错误,这时候你就需要用${variable}了。

被一对双引号括起来的变量替换是不会被禁止的,所以双引号被称为部分引用,有时候又被称为”弱引用”。但是,如果使用单引号的话,那么变量替换就会被禁止了,变量名只会被解释成字面的意思,不会发生变量替换,所以单引号被称为全引用,有时候也被称为”强引用”。

变量赋值和替换,举例如下:

a=375
hello=$a

echo hello    # 没有变量引用,只是个hello字符串

echo $hello         # 375
echo ${hello}      # 375

echo "$hello"       # 375
echo "${hello}"     # 375

# 全引用的作用将会导致"$"被解释为单独的字符,而不是变量前缀
echo '$hello'          # $hello

引用一个变量将保留其中的空白,但如果是变量替换,就不会保留了

hello="A B  C   D"
echo $hello                  # A B C D
echo "$hello"               # A B  C   D

一个未初始化的变量是没有值的,但是在做算术操作的时候,这个未初始化的变量看起来值为0。这是一个未文档化(并且可能不具可移植性)的行为。

echo "$uninitialized"                # (blank line)
let "uninitialized += 5"                # Add 5 to it.
echo "$uninitialized"                # 5

变量引用

在一个双引号中通过直接使用变量名的方法来引用变量,一般情况下都是没问题的。这么做将阻止所有在引号中的特殊字符被重新解释,包括变量名,但是$、`(后置引用)、和\除外。保留$作为特殊字符的意义是为了能够在双引号中也能够正常的引用变量,也就是说,这个变量将被它的值所取代。

使用双引号还能够阻止单词分割,如果一个参数被双引号扩起来的话,那么这个参数将认为是一个单元,即使这个参数包含有空白,那里面的单词也不会被分隔开。

hello="A B  C   D"
echo $hello                  # A B C D
echo "$hello"               # A B  C   D

在echo语句中,只有在单词分割或者需要保留空白的时候,才需要把参数用双引号括起来。

谈到空白,我们可以通过IFS变量修改默认的空白符,例如:

var="'(]\\{}\$\""

# 下面输出一样
echo $var        # '(]\{}$"
echo "$var"      # '(]\{}$"

IFS='\'
# 下面输出不一样了,因为这里空白字符定义为\了,变量替换的时候,会将其替换为一个空格
echo $var        # '(] {}$"     
echo "$var"      # '(]\{}$"

单引号操作与双引号基本一样,但是不允许引用变量,因为$的特殊意义被关闭了。在单引号中,任何特殊字符都按照字面的意思进行解释,除了’,所以说单引号是一种比双引号更严格的引用方法。

因为即使是转义符在单引号中也是按照字面意思解释的,所以如果想在一对单引号中显示一个单引号是不行的。对于下面的例子:

echo "Why can't I write 's between single quotes"

使用单引号来引用,可以这样做:

echo 'Why can'\''t I write '"'"'s between single quotes'   # Why can't I write 's between single quotes

三个被单引号引用的字符串,在这三个字符串之间有一个用转义符转义的单引号,和一个用双引号括起来的单引号。

参考文章


原创文章,转载请注明: 转载自JavaChen Blog,作者:JavaChen
本文链接地址:http://blog.javachen.com/2015/07/07/bash-variable.html
本文基于署名2.5中国大陆许可协议发布,欢迎转载、演绎或用于商业目的,但是必须保留本文署名和文章链接。 如您有任何疑问或者授权方面的协商,请邮件联系我。