Bash 变量

Bash 变量定义、使用、注意事项

本文转载自 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 命令则不受影响:

1
LC_ALL=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
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。

1
shift [N]

踢掉前 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
本博客已稳定运行 小时 分钟
共发表 31 篇文章 · 总计 82.93 k 字
本站总访问量
Built with Hugo
主题 StackJimmy 设计