0%

高级 Bash 脚本编程指南(03):变量和参数的介绍

变量在编程语言中用来表示数据,它本身只是一个标记,指向数据在计算机内存中的一个或一组地址。变量通常出现在算术运算、数量操作以及字符串解析中。

变量替换

变量名是其所指向值的一个 占位符,而引用变量值的过程则被称为 变量替换。使用 $var 来获取变量 var 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/bin/bash

# no space
a=375
hello=$a
echo hello
echo $hello
echo ${hello}
echo "$hello"
echo "${hello}"
echo


hello="A B C D"
# ouput A B C D
echo $hello
# ouput A B C D
echo "$hello"
# oupput $hello
echo '$hello'
echo

hello=
echo "\$hello (null value) = $hello"
echo

var1=21 var2=22 var3=33
echo "var1=$var1 var2=$var2 var3=$var3"
echo


numbers="one two three"
other_numbers="1 2 3"
# error
# other_numbers=1 2 3
mixed_bas=2\ ---\ Whatever

echo "numbers=$numbers"
echo "other_numbers=$other_numbers"
echo "$mixed_bas"

echo "uninit_var=$uninit_var"
uninit_var=
echo "uninit_var=$uninit_var"
uninit_var=23
unset uninit_var
echo "uninit_var=$uninit_var"
echo

exit 0
  • 变量仅仅在声明、赋值、unset(删除)、export(导出)、(()) 算术运算、或者代表一个信号时,才不需要 $ 前缀
  • 强烈注意,在变量赋值的前后一定不要有空格
  • 注意 $hello"$hello" 的区别,在 "" 内引用变量可以保留变量内的空白字符
  • $hello${hello} 也是有区别,前者在某些上下文中将引起错误,为了安全,推荐使用后者
  • 单引号内会禁用掉变量的引用,$ 不再具有特殊含义
  • 使用空白符分隔,可以在一行内对多个变量进行赋值,但不建议这么做
  • 如果变量值中包含有空白字符,变量值必须通过引号进行应用,或者也可以通过 \ 转义空白字符
  • 未初始的变量、只声明而不初始化的变量、unset 的变量,其值都为空值

一个未初始化或者未被赋值的变量为空值(null value),注意 null value 并不是 0

1
2
3
4
5
6
7
8
# if [ -z "$unit_var" ]; then echo "null"; else echo "not null"; fi
null
# unit_var=10
# if [ -z "$unit_var" ]; then echo "null"; else echo "not null"; fi
not null
# unset unit_var
# if [ -z "$unit_var" ]; then echo "null"; else echo "not null"; fi
null

在赋值前使用变量可能会导致错误,但是在算术运算中使用未赋值变量是可行的。

1
2
3
4
5
# echo "$unit_var"

# let "unit_var += 5"
# echo "$unit_var"
5

变量赋值

最常见的变量赋值是通过 = 赋值操作符来实现(= 前后没有空格)。当然在某些上下文中,= 也可以用作比较操作符,此时需要注意区分。除此之外,也可以在 letread for 循环等中进行变量赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

echo

a=789
echo "The value of \"a\" is $a"

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

for a in 7 8 9 11
do
echo -n "$a "
done
echo


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

exit 0

下面演示了比较特殊的变量赋值,即将命令的执行结果赋值给变量:

1
2
3
4
5
6
7
8
9
10
11
12
# a=`echo hello`
# echo $a
hello

# a=`ls -l`
# echo $a
total 8 -rw-r--r-- 1 root root 226 May 25 10:36 assign_var.sh -rw-r--r-- 1 root root 637 May 25 10:11 var.sh

# echo "$a"
total 8
-rw-r--r-- 1 root root 226 May 25 10:36 assign_var.sh
-rw-r--r-- 1 root root 637 May 25 10:11 var.sh

还是要注意这里 $a"$a" 的区别,前者会将多余的空白符都移除,而后者则会保留所有的空白符。

出了使用反引号进行命令替换,也可以使用 $(...) 来进行命令替换,它是一种更新的形式:

1
2
3
4
5
6
7
8
# a=$(ls -l)
# echo $a
total 8 -rw-r--r-- 1 root root 226 May 25 10:36 assign_var.sh -rw-r--r-- 1 root root 637 May 25 10:11 var.sh

# echo "$a"
total 8
-rw-r--r-- 1 root root 226 May 25 10:36 assign_var.sh
-rw-r--r-- 1 root root 637 May 25 10:11 var.sh

Bash 弱类型变量

不同于其他编程语言,Bash 并不区分变量的类型。本质上,Bash 变量都是字符串。但是依赖于上下文,Bash 也允许对变量执行比较和算术操作。决定这些的关键因素是:变量值是否只包含数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash

a=12345
let "a+=1"
echo "a=$a"

b=${a/23/BB}
declare -i b
echo "b=$b"
# can't calculate
let "b+=1"
echo "b=$b"

c=BB34
echo "c=$c"
d=${c/BB/23}
echo "d=$d"
# calculate ok
let "d+=1"
echo "d=$d"
echo


e=''
echo "e=$e"
let "e+=1"
# output 1
echo "e=$e"

echo "f=$f"
let "f+=1"
# output 1
echo "f=$f"

# error
let "f/=$unit_var"
# error
let "f/=0"

exit $?
  • 对于一个包含非数字的变量,即使使用 declare -i 将它声明为整型,也无法进行算术操作
  • 对于一个空变量或者未声明的变量,可以对其进行算术操作,其行为就如果该变量的初始值为 0 一样(但是有些情况也不是这样,例如上面的 f/=$unit_var$unit_var 并没有当做 0)

Bash 的弱变量使得编程更加灵活、更加容易,但也容易造成一些错误,需要额外注意。

特殊的变量类型

局部变量

局部变量是指在代码块或者函数内才可见的变量,关于局部变量,后面在讲解函数时还会继续介绍。

1
2
3
4
# a=1; f() { a=2; }; f; echo $a
2
# a=1; f() { local a=2; }; f; echo $a
1

环境变量

环境变量可以改变用户接口和 shell 的行为。一般情况下,每个进程都有自己的环境,也就是一组该进程可以访问到的变量,shell 也无不例外

  • 每次当 shell 启动时,都会创建它自己的环境变量。改变或添加环境变量,将导致 shell 更新它的环境
  • 子进程会继承父进程的环境变量
  • 分配给环境变量的空间是有限的

如果一个脚本设置了环境变量,那么这些环境变量需要被 导出,即通知脚本所在的环境做出相应的更新,可以通过 export 命令进行 导出。脚本只能将变量导出到子进程,子进程不能将变量传递会给父进程。

位置参数

位置参数 就是从命令行中传递给脚本的参数:$0, $1, $2...

  • $0:脚本文件的名字;
  • $1:第一个参数;
  • $2:第二个参数;
  • 以此类推; 如果是 $9 以后,就需要使用花括号了:${10}, ${11}, ${12}...
  • $*$@ 表示所有的位置参数
  • $#:表示传到脚本中的位置参数的个数(不包含脚本名)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# cat pos_para.sh
#!/bin/bash

MIN_PARAS=10

echo
echo "The name of script is $0"
echo "The script is `basename $0`"

if [ -n "$1" ]; then
echo "The #1 is $1"
fi

if [ -n "$2" ]; then
echo "The #2 is $2"
fi

# .....
if [ -n "${10}" ]; then
echo "The #10 is ${10}"
fi

echo "-----------"
echo "All the command-line parameters are: "$*""


if [ $# -lt "$MIN_PARAS" ]; then
echo
echo "The script needs at least $MIN_PARAS command-line arguments, current is $#!"
fi

echo

exit 0
1
2
3
4
5
6
7
8
9
10
# ./pos_para.sh 1 2 3

The name of script is ./pos_para.sh
The script is pos_para.sh
The #1 is 1
The #2 is 2
-----------
All the command-line parameters are: 1 2 3

The script needs at least 10 command-line arguments, current is 3!

在位置参数中,使用大括号助记符,可以以一种简单的方式来访问传入脚本的最后一个参数:

1
2
3
4
5
6
args=$#
last_arg=${!args}
echo $last_arg

last_arg=${!#}
echo $last_arg

有时候脚本可以通过调用时的文件名来执行不同的操作,要实现该效果,需要检查 $0,同时通过符号链接产生多个符号链接。如下是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash

# need below symbol links
# 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=95

if [ -z $1 ]; then
echo "Usage: `basename $0` [domain-name]"
exit $E_NOARGS
fi


case `basename $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

shift 可以将全体位置参数向左移动一位,重新赋值。例如 $1 <--- $2$2 <--- $3,以此类推。注意 $0 始终保持不变,而原先的 $1 值则消失了。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash

until [ -z $1 ]; do
echo -n "$1"
shift
done

echo


echo "after shift, the #2 is $2"
exit

shift 命令也可以带一个参数来指明一次移动多少位:

1
2
3
4
5
6
7
8
#!/bin/bash
# shift_3.sh

shift 3

echo "$1"

exit 0
1
2
# ./shift_3.sh 1 2 3 4 5
4

shift 命令也可以应用于函数中,使用方法是类似的。