本文转载自 Shell 脚本深入教程:Bash 变量
变量赋值
等号 =
左右两边必须不能出现空白。当包含了特殊符号时,需要使用引号包围。使用单引号还是双引号,后面再详细解释。
1
2
3
|
var1=22 # 数值
var2="hello world" # 字符串
var3=hello world # 错误
|
问题:var3=hello world
为什么报错的是没有 world 命令,而不是赋值错误?为什么 echo = 3
不是赋值?
var=value CMD
是shell的一种用法,在命令最前面赋值的变量var只在当前所执行的命令CMD中有效,其他任何地方都无法使用var变量,CMD退出后,var也消失。
其实经常会看到这种用法,比如下面的命令模式,表示 cmd 命令执行时设置 locale 环境为 C,但非 cmd 命令则不受影响:
使用 unset
内置命令可以注销变量:
1
2
3
4
|
var="hello world"
echo $var
unset var
echo $var
|
使用 readonly
内置命令可以定义只读变量,只读变量不可修改、不可注销。
1
2
3
|
readonly xyz="helloworld"
abc="hello world"
readonley abc
|
变量引用
使用 $VAR
或 ${VAR}
,前者是简写,后者是规范引用。
例如:
1
2
|
var='hello world'
echo $var
|
一定注意:变量的名称是 var
,而不是 $var
,$var
是在引用、访问变量在内存中保存的值。
使用 ${#VAR}
获取变量 VAR 保存的字符长度。
1
2
|
a="hello world"
echo ${#a}
|
当 $VAR
引用方式会产生歧义时,就用 ${VAR}
,例如:
1
2
3
|
VAR="hello"
echo $VARa # 访问变量VARa,但它不存在
echo ${VAR}a # 访问变量VAR,并将结果和a串联起来
|
环境变量
在 Shell 脚本中偶尔会考虑用环境变量,但是环境的概念在 Shell 中很重要。
环境变量可以看作是一个全局变量,但这说法并不正确。后面我会详细解释何为 Shell 环境,这是 Shell 中非常重要的概念,到时候大家就会对环境变量有一个非常清晰的认识。
使用 bash 内置命令 export
可以定义一个环境变量:
1
2
3
4
5
6
|
# 直接定义一个新的环境变量
$ export ENV_VAR="hello world"
# 将一个已存在的普通变量导出为环境变量
$ ENV_VAR1="HELLOWORLD"
$ export ENV_VAR1
|
环境变量一般以大写字母命名。
子 Shell 进程可以继承父 Shell 中的环境变量:
1
2
3
4
|
x=1000
y=10000
export y
bash -c 'echo $x;echo $y'
|
使用 env
命令可以查看所有环境变量。
可以在某个命令行前设置变量,便表示设置该命令的专属环境变量,只有该命令进程中可访问该环境变量,其它任何地方都无法访问,且命令退出后专属环境变量消失。
1
2
3
4
5
6
7
8
9
10
|
# 下面等价
xyz=10 bash -c 'echo $xyz'
env xyz=100 bash -c 'echo $xyz'
# 环境变量只对cmd1有效
xyz=10 cmd1 | cmd2
# 环境变量对cmd1和cmd2都有效
xyz=10 cmd1 | xyz=10 cmd2
xyz=55 bash -c 'cmd1 | cmd2'
|
常见的环境变量:
1
2
3
4
5
6
7
8
9
10
|
HOSTNAME=control_node
USER=root
HOME=/root
SHELL=/bin/bash
HISTSIZE=1000
SSH_TTY=/dev/pts/2
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
MAIL=/var/spool/mail/root
PWD=/root
LANG=en_US.UTF-8
|
特别要注意的是 PATH 环境变量,它决定了 Shell 调用命令时的搜索路径。经常会设置 PATH 环境变量,特别是应用在定时任务的 Shell 脚本。
1
|
PATH=/usr/local/mysql/bin:$PATH
|
比如,编译安装或直接解压安装程序时,通常会将 PATH 写入到 /etc/profile.d/*.sh 下:
1
2
|
echo 'PATH=/usr/local/mysql/bin:$PATH' >/etc/profile.d/mysql.sh
source /etc/profile.d/mysql.sh
|
位置参数和特殊变量
位置参数
Shell 脚本运行时可能需要一些选项或参数。
比如,某脚本 test.sh 内容:
1
2
3
|
#!/bin/bash
cat $1
touch $2
|
这表示先读取并输出第一个参数表示文件内容,然后 touch 第二个参数表示的文件。执行时:
1
2
|
chmod +x test.sh
./test.sh /etc/fstab /tmp/touchme.log
|
脚本执行时的选项 (本例没有选项) 和参数都是脚本的位置参数,位置参数使用 $1
,$2
,$3
,… 引用。
$0
比较特殊,表示脚本名或当前 Shell 名称。
位置参数是相对于每个 Shell 进程而言的
。对于 Shell 脚本来说,位置参数就是执行脚本时的参数部分。当处于 Shell 环境内时,也可以使用 set --
设置当前 Shell 环境位置参数:
1
2
3
4
5
6
|
set -- a b c
echo $1
echo $@
echo $#
set -- # 清空当前Shell的位置参数
echo $#
|
特殊变量
Shell 有一些特殊变量,这些变量由 Shell 自身动态维护,不允许用户手动修改。
为了让这些特殊变量更直观,我使用变量引用而不直接使用特殊变量名。
1
2
3
4
5
6
7
8
9
10
|
$1,$2,...,$N:脚本的位置参数
$0:shell或shell脚本的名称
$*:扩展为位置参数,"$*"会将所有位置参数一次性包围引起来,"$*"等价于"$1_$2_$3..."
$@:扩展为位置参数,"$@"会将每个位置参数单独引起来,"$@"等价于"$1" "$2" "$3"...
$#:位置参数的个数
$$:当前Shell的进程PID,在某些子Shell(如小括号()开启的子Shell)下,会被继承。如果可以,建议使用$BASHPID替代$$
$?:最近一个前台命令的退出状态码
$!:最近一个后台命令的进程PID
$-:当前Shell环境的一些特殊设置,比如是否交互式
$_:最近一个前台命令的最后一个参数(还有其它情况,该变量用的不多,所以不追究了)
|
关于 $* "$*" $@ "$@"
的区别,参见如下 shell 脚本测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
#!/bin/bash
echo '$*----------':
for i in $*;do echo "<$i>";done
echo '"$*--------"':
for i in "$*";do echo "<$i>";done
echo '$@----------':
for i in $@;do echo "<$i>";done
echo '"$@--------"':
for i in "$@";do echo "<$i>";done
|
执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$ chmod +x position_parameters.sh
$ ./position_parameters.sh a b 'c d'
$*---------:
<a>
<b>
<c>
<d>
"$*--------":
<a b c d>
$@----------:
<a>
<b>
<c>
<d>
"$@--------":
<a>
<b>
<c d>
|
shift踢掉位置参数
Bash 内置命令 shift 专门用来踢位置参数。在为 Shell 脚本设计选项、参数时,都会用到 shift。
踢掉前 N 个位置参数,如果没有指定 N 参数,则默认 N=1,即一次踢掉一个位置参数。
踢掉位置参数后,后面的位置参数会向前移动。
1
2
3
4
5
6
7
8
9
|
$ set -- a b c d e f
$ echo ${#@}
$ echo $1
a
$ shift 2
$ echo ${@}
c d e f
$ echo ${1}
c
|
例如,自定义 ping 命令的选项、参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
#!/bin/bash
while [ ${#@} -gt 0 ];do
case "$1" in
-c|--count)
count=$2
shift 2
;;
-t|--timeout)
timeout=$2
shift 2
;;
-a|--ip)
ip=$2
shift 2
;;
*)
echo "wrong options or arguments"
exit 1
esac
done
ping -c $count -W timeout $ip
|